加载网络图片
来源:互联网 发布:视频管理系统php 编辑:程序博客网 时间:2024/06/06 19:55
一.概述
图片异步加载有2种方式: (多线程/线程池) 或者 用其实AsyncTask , 其实AsyncTask底层也是用的多线程.
使用缓存的好处是 , 提高流畅度, 节约流量.
图片加载原理 使用网络请求 得到一个 输入流 然后把输入流转化为Bimp 显示到控件上 或者储存到内存中 或数据库
二.代码
1.先看图片加载工具类
public class ImageLoader { private ImageView mImageview; private String mUrl; //创建缓存 private LruCache<String, Bitmap> mCaches; private ListView mListView; private Set<NewsAsyncTask> mTask; public ImageLoader(ListView listView) { mListView = listView; mTask = new HashSet<>(); //获得最大的缓存空间 int maxMemory = (int) Runtime.getRuntime().maxMemory(); //赋予缓存区最大缓存的四分之一进行缓存 int cacheSize = maxMemory / 4; mCaches = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { //在每次存入缓存的时候调用 return value.getByteCount(); } }; } //将图片通过url与bitmap的键值对形式添加到缓存中 public void addBitmapToCache(String url, Bitmap bitmap) { if (getBitmapFromCache(url) == null) { mCaches.put(url, bitmap); } } //通过缓存得到图片 public Bitmap getBitmapFromCache(String url) { return mCaches.get(url); } private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (mImageview.getTag().equals(mUrl)) mImageview.setImageBitmap((Bitmap) msg.obj); } }; //通过线程的方式去展示图片 public void showImageByThread(ImageView imageView, String url) { mImageview = imageView; mUrl = url; new Thread() { @Override public void run() { super.run(); Bitmap bitmap = getBitmapFromUrl(mUrl); Message message = Message.obtain(); message.obj = bitmap; mHandler.sendMessage(message); } }.start(); } //通过异步任务的方式去加载图片 public void showImageByAsyncTask(ImageView imageView, String url) { //先从缓存中获取图片 Bitmap bitmap = getBitmapFromCache(url); if (bitmap == null) { imageView.setImageResource(R.mipmap.ic_launcher); } else { imageView.setImageBitmap(bitmap); } } private class NewsAsyncTask extends AsyncTask<String, Void, Bitmap> { // private ImageView mImageView; private String mUrl; public NewsAsyncTask( String url) { // mImageview = imageView; mUrl = url; } @Override protected Bitmap doInBackground(String... params) { String url = params[0]; //从网络获取图片 Bitmap bitmap = getBitmapFromUrl(url); //将图片加入缓存中 if (bitmap != null) { addBitmapToCache(url, bitmap); } return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl); if (imageView!=null&&bitmap!=null){ imageView.setImageBitmap(bitmap); } mTask.remove(this); } } //滑动时加载图片 public void loadImages(int start, int end) { for (int i = start; i < end; i++) { String url = NewsAdapter.URLS[i]; //先从缓存中获取图片 Bitmap bitmap = getBitmapFromCache(url); if (bitmap == null) { NewsAsyncTask task = new NewsAsyncTask(url); task.execute(url); mTask.add(task); } else { ImageView imageView = (ImageView) mListView.findViewWithTag(url); imageView.setImageBitmap(bitmap); } } } //停止时取消所有任务加载 public void cancelAllTasks(){ if (mTask!=null){ for (NewsAsyncTask task :mTask){ task.cancel(false); } } } //网络获取图片 private Bitmap getBitmapFromUrl(String urlString) { Bitmap bitmap; InputStream is = null; try { URL url = new URL(urlString); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); is = new BufferedInputStream(connection.getInputStream()); bitmap = BitmapFactory.decodeStream(is); connection.disconnect(); return bitmap; } catch (IOException e) { e.printStackTrace(); } finally { try { assert is != null; is.close(); } catch (IOException e) { e.printStackTrace(); } } return null; }}
需要注意的几个部分:
<1
LruCache<String, Bitmap> mCaches 这是创建一个集合去存储缓存的图片,底层是HashMap实现的,其实和我们之前java中用到HashMap 弱引用/软引用比较类似, 但是
自2.3以后Android将更频繁的调用GC,导致软引用缓存的数据极易被释放。所以不能用之前的方式来缓存图片了,
LruCache使用一个LinkedHashMap简单的实现内存的缓存,没有软引用,都是强引用。如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。我们可以在构造方法中,先得到当前应用所占总缓存大小,然后分出1/4用于存储图片,对应代码如下:
//获得当前应用最大的缓存空间 int maxMemory = (int) Runtime.getRuntime().maxMemory(); //赋予缓存区最大缓存的四分之一进行缓存 int cacheSize = maxMemory / 4; mCaches = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { //在每次存入缓存的时候调用 return value.getByteCount(); } };
<2
Set<NewsAsyncTask> mTask定义一个Task任务集合,每个任务对应一个图片,当该图片被加载后要是否这个对应的task,对应代码如下:
ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl); if (imageView!=null&&bitmap!=null){ imageView.setImageBitmap(bitmap); } mTask.remove(this);
<3代码中根据 adapter 给每个图片设置 Tag 标识来获取图片,作用是: 避免 listview滚动时,由于convertView缓存造成图片错位显示, 对应代码如下: ----------> adapter代码后面给出
private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (mImageview.getTag().equals(mUrl))//--------------根据adapter设置的tag获取 mImageview.setImageBitmap((Bitmap) msg.obj); } };
<4
当listview一边滚动,一边加载图片会造成一个问题,可能会出现暂时的卡顿现象,尽管这个现象是偶尔发生,如果网络不好情况下,会加重这种情况,这是为什么呢?
因为listview滚动时,对画面流畅度要求比较高
虽然异步加载是在新线程中执行的,并未阻塞UI线程,当加载好图片后,去更新UI线程
就会导致UI线程发生一次重绘,如果这次重绘正好发生在listview滚动的时候
就会导致这个listview滚动过程中卡顿一下, 这样用户体验大大滴不好
为解决该问题:
我们可以在 listview滚动停止后 才去加载可见项, listview滚动过程中,取消加载项(滚动过程中不加载图片数据)
就能解决这个问题, 因为我们在滚动过程中,其实我并不关心 滚动的内容,我只会关心 滚动停止后要显示的内容,所以这么做是 完全OK的. 对应代码如下:
//滑动时加载图片 , 这里的 start 和end是 listview第一个和最后一个可见项 // adapter代码中会有详述 public void loadImages(int start, int end) { for (int i = start; i < end; i++) { String url = NewsAdapter.URLS[i]; //先从缓存中获取图片 Bitmap bitmap = getBitmapFromCache(url); if (bitmap == null) { NewsAsyncTask task = new NewsAsyncTask(url); task.execute(url); mTask.add(task); } else { ImageView imageView = (ImageView) mListView.findViewWithTag(url); imageView.setImageBitmap(bitmap); } } }
以上就是图片工具类比较重点的部分 ,下面介绍adapter
常常的分割线-------------------------------------------------------------------------------------------------------------
public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener{ private List<NewsBeans> mList; private LayoutInflater mInflater; private ImageLoader mImageLoader; private int mStart; private int mEnd; //创建静态数组保存图片的url地址 public static String[] URLS; private boolean mFirstIn; public NewsAdapter(Context context, List<NewsBeans> data,ListView listView) { mList = data; mInflater = LayoutInflater.from(context); mImageLoader = new ImageLoader(listView); URLS = new String[data.size()]; for(int i=0;i<data.size();i++){ URLS[i] = data.get(i).iv_title; } listView.setOnScrollListener(this); mFirstIn = true; } @Override public int getCount() { return mList.size(); } @Override public Object getItem(int position) { return mList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { viewHolder = new ViewHolder(); convertView = mInflater.inflate(R.layout.list_item, null); viewHolder.iv_title = (ImageView) convertView.findViewById(R.id.iv_icon); viewHolder.tv_title = (TextView) convertView.findViewById(R.id.tv_title); viewHolder.tv_content = (TextView) convertView.findViewById(R.id.tv_content); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } //设置默认显示的图片 viewHolder.iv_title.setImageResource(R.mipmap.ic_launcher); //避免缓存影响使同一位置图片加载多次混乱 String url = mList.get(position).iv_title; viewHolder.iv_title.setTag(url); // new ImageLoader().showImageByThread(viewHolder.iv_title, url); mImageLoader.showImageByAsyncTask(viewHolder.iv_title, url); viewHolder.tv_content.setText(mList.get(position).tv_content); viewHolder.tv_title.setText(mList.get(position).tv_title); return convertView; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if(scrollState==SCROLL_STATE_IDLE){ //加载可见项 mImageLoader.loadImages(mStart,mEnd); }else{ //停止加载 mImageLoader.cancelAllTasks(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mStart = firstVisibleItem; mEnd = firstVisibleItem + visibleItemCount; if (mFirstIn && visibleItemCount>0){ mImageLoader.loadImages(mStart,mEnd); } } class ViewHolder { private ImageView iv_title; private TextView tv_title; private TextView tv_content; }}
这里我们只需要注意3点
1.设置图片唯一标识Tag,避免图片错位显示
viewHolder.iv_title.setTag(url);
2.滚动过程中不加载图片,只有滚动停止后加载,下面重点分析2个方法
@Override public void onScrollStateChanged(AbsListView view, int scrollState) { if(scrollState==SCROLL_STATE_IDLE){ //加载可见项 mImageLoader.loadImages(mStart,mEnd); }else{ //停止加载 mImageLoader.cancelAllTasks(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mStart = firstVisibleItem; mEnd = firstVisibleItem + visibleItemCount; if (mFirstIn && visibleItemCount>0){ mImageLoader.loadImages(mStart,mEnd); } }
onScrollStateChanged()该方法,在listview第一次出现的时候,并不会执行,注意"并不会执行".oncroll()该方法在listview创建的时候就会执行,所以我们定义一个标志mFirstIn,在构造方法中初始为true,表示我们是第一次启动listview对 mFirstIn && visibleItemCount>0 判断的解释:"当前列表时第一次显示,并且listview的item已经展示出来",然后mFirstIn =false ,保证此段代码只有listview第一次显示的时候才会执行,之后滚动过程中不再执行这里为什么要判断visibleItemCount>0 呢?
其实 oncroll会被多次回调的, 但是初次调用 visibleItemCount 是 等于0的,也就是说此时item还未被加载所以我们要判断 >0 跳过==0的情况,因为==0 item未被加载,当然 也就不会显示网络图片了
分割线--------------------------------------------------------------------------------------------------------------------------------------最后是 MainActivity代码,比较简单不再分析
public class MainActivity extends AppCompatActivity { private static String URL = "http://www.imooc.com/api/teacher?type=4&num=30"; private ListView mListView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView = (ListView) findViewById(R.id.list_main); new LoadImageAsync().execute(URL); } //异步加载所有的网络数据 class LoadImageAsync extends AsyncTask<String, Void, List<NewsBeans>> { @Override protected List<NewsBeans> doInBackground(String... params) { return getJsonData(params[0]); } @Override protected void onPostExecute(List<NewsBeans> newsBeans) { super.onPostExecute(newsBeans); NewsAdapter adapter = new NewsAdapter(MainActivity.this, newsBeans,mListView); mListView.setAdapter(adapter); } } //得到JSON数据 private List<NewsBeans> getJsonData(String url) { List<NewsBeans> data = new ArrayList<>(); try { //读取流得到json数据 String jsonList = readStream(new URL(url).openStream()); JSONObject jsonObject; NewsBeans newsBeans; try { //解析JSON数据 jsonObject = new JSONObject(jsonList); JSONArray jsonArray = jsonObject.getJSONArray("data"); for (int i = 0; i <= jsonArray.length(); i++) { jsonObject = jsonArray.getJSONObject(i); newsBeans = new NewsBeans(); newsBeans.iv_title = jsonObject.getString("picSmall"); newsBeans.tv_title = jsonObject.getString("name"); newsBeans.tv_content = jsonObject.getString("description"); data.add(newsBeans); } } catch (JSONException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } return data; } //读取输入流 private String readStream(InputStream is) { InputStreamReader isr; String result = ""; try { String line; //读取输入流 isr = new InputStreamReader(is, "utf-8"); //输入流转换成字节流 BufferedReader br = new BufferedReader(isr); //逐行读取 while ((line = br.readLine()) != null) { result += line; } } catch (IOException e) { e.printStackTrace(); } return result; }}
- Delphi加载网络图片
- android加载网络图片
- 网络加载图片
- 加载网络图片
- 异步加载网络图片
- SDWebImage 加载网络图片
- 网络图片异步加载
- 异步加载网络图片
- Android加载网络图片
- android加载网络图片
- android 网络加载图片
- 异步加载网络图片
- 网络加载图片
- Flex加载网络图片
- swift 加载网络图片
- 网络加载图片
- ImageView加载网络图片
- 加载显示网络图片
- 洛谷P1092 虫食算(回溯法)
- 学习笔记 Linux 启动logo修改
- 替换空格 (剑指Offer 第 2 题)
- 移动端效果之swiper
- WEB服务器、应用程序服务器、HTTP服务器有何区别
- 加载网络图片
- Window10企业版激活
- qbxt国庆水题记day5
- libxxx.so- text relocations问题的终极解决方案
- D
- matlab从excel读取数据作岭回归使用范例
- Android通过WebView在线打开PDF文件
- mybatis简单案例源码详细【注释全面】——Utils层(MybatisUtils.java)
- Laravel-validation-验证错误信息中文