Android 异步加载 发表于 2018-04-11 | 更新于 2018-04-27 newsBean类:1234567public class NewsBean {public String newsIconUrl;public String newsTitle;public String newsContent;} NewsAdapter适配器:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170 /** Android异步加载 总结 通过异步加载,避免阻塞UI线程 通过LruCache,将已经下载的图片放到内存中=========》也叫一级缓存 通过判断ListView滑动状态,决定何时加载图片 不仅仅是ListView,任何控件都可以使用异步加载 */ /** 适配器实现 AbsListView.OnScrollListener接口,监听listView的滚动 让ListView滑动时取消所有加载项,滑动停止后才加载所有可见项,以此来提高listView的加载效率 */ public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener{ private List mList; private LayoutInflater mInflater; private Context context; /** 记录LitView的可见item的起始项下标和可见终止项下标 */ private int mStart; private int mEnd; //保存当前获得的图片的URL地址 public static String[] URLS; /** 判断是不是第一次打开应用 */ private boolean mFirstIn; //用于使用缓存加载图片的ImageLoader,只能new一次 private ImageLoader mImageLoader; public NewsAdapter(Context context, List mList, ListView listView){ this.context=context; this.mList=mList; mInflater=LayoutInflater.from(context); mImageLoader=new ImageLoader(listView); URLS=new String[mList.size()]; for (int i = 0; i <mList.size();i++) { URLS[i]=mList.get(i).newsIconUrl; } /** * true代表第一次启动 */ mFirstIn=true; /** * 给listView绑定滚动监听事件 */ listView.setOnScrollListener(this); } @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; } /** 在这里ListView存在缓存机制,可能导致图片加载混乱、跳动,而加载之前缓存在contentView里的图片 解决方法:让URL与指定的ImageView绑定 @param position @param convertView @param parent @return */ @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder=null; if(convertView==null){ viewHolder=new ViewHolder(); convertView=mInflater.inflate(R.layout.item_layout,null); viewHolder.mImageView=convertView.findViewById(R.id.image_view); viewHolder.mTitle=convertView.findViewById(R.id.news_title); viewHolder.mContent=convertView.findViewById(R.id.news_content); convertView.setTag(viewHolder); }else{ viewHolder=(ViewHolder)convertView.getTag(); } viewHolder.mImageView.setImageResource(R.mipmap.ic_launcher); /** 让URL与指定的ImageView绑定,防止因为convertView的缓存而造成的图片加载混乱 */ String url=mList.get(position).newsIconUrl; viewHolder.mImageView.setTag(url); /** *========>用多线程的方法加载图片 这里没有用缓存 / //new ImageLoader().showImageByThread(viewHolder.mImageView, url); /* <=========通过AsyncTask的方法加载图片 ==== 这里用了缓存 =====》用了缓存,ImageLoader不能每次都new一个,这样每次都会new一个LruCache对象,达不到缓存效果 */ mImageLoader.showImageByAsyncTask(viewHolder.mImageView,url); viewHolder.mTitle.setText(mList.get(position).newsTitle); viewHolder.mContent.setText(mList.get(position).newsContent); return convertView; } class ViewHolder{ public TextView mTitle; public TextView mContent; public ImageView mImageView; } /** listView状态切换时调用 @param view @param scrollState 当前ListView的滚动状态 *scrollState:即滑动的状态。分为三种 0,1,2 =0 表示停止滑动的状态 SCROLL_STATE_IDLE =1表示正在滚动,用户手指在屏幕上 SCROLL_STATE_TOUCH_SCROLL =2表示正在滑动。用户手指已经离开屏幕 SCROLL_STATE_FLING scrollState 回调顺序如下: 手指触屏拉动准备滚动,只触发一次 顺序: 1 scrollState = SCROLL_STATE_TOUCH_SCROLL(1):表示正在滚动。当屏幕滚动且用户使用的触碰或手指还在屏幕上时为1 持续滚动开始,只触发一次 顺序: 2 scrollState =SCROLL_STATE_FLING(2) :表示手指做了抛的动作(手指离开屏幕前,用力滑了一下,屏幕产生惯性滑动)。 整个滚动事件结束,只触发一次 顺序: 4 scrollState =SCROLL_STATE_IDLE(0) :表示屏幕已停止。屏幕停止滚动时为0。 */ //onScrollStateChanged方法在初始化时不会被调用,所以第一次打开应用, //不会加载网络图片,只会加载默认本地图片,所以设置一个tag,判断是否是第一次访问,mFirst,如果是就在onScroll方法中加载网络数据 //第一次打开,listView默认状态没有改变 @Override public void onScrollStateChanged(AbsListView view, int scrollState) { //当前listView处于停止状态 idle:闲置的 if(scrollState==SCROLL_STATE_IDLE){ /** * 把图片从网络的加载控制权从getView方法转移到这里 */ mImageLoader.loadImages(mStart,mEnd); //加载数据可见项 }else { //处于其他状态,停止任务 mImageLoader.cancelAllTasks(); } } /** listView的整个滑动过程中都会调用 @param view @param firstVisibleItem 当前窗口中能看见的第一个列表项ID @param visibleItemCount 当前窗口中能看见的列表项的个数(小半个也算) @param totalItemCount 列表项的总数 */ //一直在滚动中,多次触发 顺序: 3 //这个方法一直会调用,打开listView就会调用 @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { mStart=firstVisibleItem; mEnd=firstVisibleItem+visibleItemCount; /** 判断是不是第一次启动,并且item绘制完成,即可见item大于0,就加载网络数据 */ if (mFirstIn && visibleItemCount>0){ mImageLoader.loadImages(mStart,mEnd); //设置为false,表示不是第一次访问 mFirstIn=false; } } } ImageLoader类:123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265 public class ImageLoader { private ImageView mImageView; private String mUrl; /** *使用缓存,增加用户体验,缓解网络加载压力,以内存换效率 * key-value的形式 * * key:缓存对象的名字 * value:缓存的对象 * * LruCache底层通过LinkHashMap实现 */ //创建caches private LruCache<String,Bitmap> mCaches; private ListView mListView; private Set<NewsAsyncTask> mTask; public ImageLoader(ListView listView){ this.mListView=listView; this.mTask=new HashSet<>(); /** * 获取当前可用的最大内存 */ int maxMemory=(int)Runtime.getRuntime().maxMemory(); /** * 缓存的大小 */ int cacheSize=maxMemory/4; mCaches=new LruCache<String,Bitmap>(cacheSize){ /** * 这个方法默认返回元素的个数 * 这里要返回每个存进去的对象的大小 * @param key * @param value * @return */ //这个方法在每次存入内存缓存时调用 @Override protected int sizeOf(String key, Bitmap value) { /** * 返回每个存入内存缓存的bitmap的大小 */ return value.getByteCount(); } }; }/** * 把Bitmap加入缓存 * @param url * @param bitmap */public void addBitmapToCache(String url,Bitmap bitmap){ if(getBitmapFromCache(url)==null){ mCaches.put(url,bitmap); }}/*** 把Bitmap从缓存中取出来*/public Bitmap getBitmapFromCache(String url){ return mCaches.get(url);}private Handler handler=new Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); Bitmap bitmap=(Bitmap)msg.obj; /** * 让URL与指定的ImageView绑定,防止因为convertView的缓存而造成的图片加载混乱 */ if(mImageView.getTag().equals(mUrl)){ mImageView.setImageBitmap(bitmap); } }};/** *===下载图片的方式一====>用多线程的方法加载图片 * >>>>>>>>这个方法没用缓存<<<<<<<<<< * @param imageView * @param urlIcon */public void showImageByThread(ImageView imageView,String urlIcon){ mImageView=imageView; mUrl=urlIcon; new Thread(new Runnable() { @Override public void run() { Bitmap bitmap=getBitmapFromURL(mUrl); /** * 通过这种方式可以获得已经回收的Message,提高Message的使用效率 */ Message message=Message.obtain(); message.obj=bitmap; handler.sendMessage(message); } }).start();}/** * 获取图片从URL中 * @param urlIcon * @return */public Bitmap getBitmapFromURL(String urlIcon){ Bitmap bitmap; InputStream is=null; BufferedInputStream bis=null; try { URL url=new URL(urlIcon); HttpURLConnection connection= (HttpURLConnection)url.openConnection(); is=connection.getInputStream(); bis=new BufferedInputStream(is); bitmap= BitmapFactory.decodeStream(bis); connection.disconnect(); Thread.sleep(1000); is.close(); bis.close(); return bitmap; }catch (MalformedURLException e){ e.printStackTrace(); }catch(IOException e){ }catch(InterruptedException e){ } return null;}/** *===下载图片的方式二=====>通过AsyncTask的方法加载图片 * 》》》》》》这个方法用了缓存《《《《《《《 * @param imageView * @param url */public void showImageByAsyncTask(ImageView imageView,String url){ /** * =====判断缓存中是否有bitmap,有就直接使用,设置给imageView,没有再去下载 */ //从缓存中取出对应哪个图片 Bitmap bitmap=getBitmapFromCache(url); if (bitmap==null){ //如果缓存中没有对应图片,就去下载 //new NewsAsyncTask(url).execute(url); imageView.setImageResource(R.mipmap.ic_launcher); }else { imageView.setImageBitmap(bitmap); }} private class NewsAsyncTask extends AsyncTask<String,String,Bitmap>{ //private ImageView mImageView; private String url; public NewsAsyncTask(String url){ //this.mImageView=imageView; this.url=url; } @Override protected Bitmap doInBackground(String... strings) { /** * 第一次下载后就把Bitmap保存到缓存中 */ String url=strings[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(url); if(imageView!=null&&bitmap!=null){ imageView.setImageBitmap(bitmap); } /** * 移除自己,因为任务已经执行完了 */ mTask.remove(this); }}/** * 加载listView停止滚动时的 可见起始项 到 可见终止项 之间的所有item * @param start * @param end */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) { /** * cancel(true),还是 cancel(false),调用之后 isCancelled() 都返回 true * AsyncTask 的 cancel 方法需要一个布尔值的参数,参数名为 mayInterruptIfRunning, * 意思是如果正在执行是否可以打断,如果这个值设置为 true , * 表示这个任务可以被打断,否则,正在执行的程序会继续执行直到完成。 * 如果在 doInBackground()方法中有一个循环操作, * 我们应该在循环中使用 isCancelled()来判断,如果返回为 true ,我们应该避免执行后续无用的循环操作。 */ task.cancel(false); } }}}