Android必学-异步加载(三阶段:优化ListView加载可见项)

ListView的滚动对画面的流畅度要求是非常高的,当作异步加载的时候,虽然在加载的过程中是用新的线程去执行的,并没有阻塞UI线程,但当加载好之后,去更新UI线程,就会导致UI线程发生一次重绘,而如果这次重绘如果刚好正好发生在ListView滚动的时候,就会导致ListView滚动的过程中卡顿一下

改善方法
ListView滑动停止后才加载可见项
ListView滑动时,取消加载可见项,也就是在滚动的时候停止加载任务
(也可以运用到GridView中)

下面是修改后的代码


1.LoaderImage

public class ImageLoader {  
    private LruCache<String, Bitmap> mLruCache;

    private ListView mListView;
    private Set<LoadingAsyncTask> mAsyncTasks;//用来管理所有的LoadingAsyncTask实例

    public ImageLoader(ListView listView) {
        mListView=listView;
        mAsyncTasks=new HashSet<ImageLoader.LoadingAsyncTask>();

        int maxMemory=(int) Runtime.getRuntime().maxMemory();
        int cacheSize=maxMemory/5;
        mLruCache=new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                //在每次存入缓存的时候调用
                return value.getByteCount();
            };
        };
    }

    public void addBitmapToCache(String url,Bitmap bitmap) {
        if(mLruCache.get(url)==null) {
            mLruCache.put(url, bitmap);
        }
    }

    public Bitmap getBitmapFromCache(String url) {
        return mLruCache.get(url);
    }

    /**
     * 使用AsyncTask实现异步加载,showImageByAsyncTask自身是在主线程当中的
     * @param iv
     * @param url
     */
    public void showImageByAsyncTask(final ImageView iv,final String url) {
        Bitmap bitmap=getBitmapFromCache(url);
        if (bitmap == null) {
            iv.setImageResource(drawable.ic_launcher);
        }
        else {
            iv.setImageBitmap(bitmap);
        }
    }

    private class LoadingAsyncTask extends AsyncTask<String, Void, Bitmap> {
        private String url;

        public LoadingAsyncTask(String url) {
            this.url=url;
        }

        @Override
        protected Bitmap doInBackground(String... params) {
            Bitmap bm=getBitmapFromURL(params[0]);
            if(bm!=null) {
                addBitmapToCache(params[0], bm);
            }
            return bm;
        }

        @Override
        protected void onPostExecute(Bitmap result) {   
            ImageView iv = (ImageView) mListView.findViewWithTag(url);
            if (iv != null && result != null) {
                iv.setImageBitmap(result);
            }
            mAsyncTasks.remove(this);
        }
    }

    public Bitmap getBitmapFromURL(String urlString) {
        Bitmap bm=null;
        InputStream is=null;
        try {
            URL url=new URL(urlString);
            HttpURLConnection connection=(HttpURLConnection) url.openConnection();
            is=new BufferedInputStream(connection.getInputStream());
            bm=BitmapFactory.decodeStream(is);
            connection.disconnect();//释放资源
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return bm;
    }

    /**
     * 将显示图片的控制权从showImageByAsyncTask方法转移到了loadImages方法
     * 不再让getView的时候去触发LoadingAsyncTask的加载
     * 而使得ListView滚动的时候去触发加载任务
     * 
     * 加载指定的从start到end项的图片
     * @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) {
                LoadingAsyncTask task=new LoadingAsyncTask(url);
                task.execute(url);
                mAsyncTasks.add(task);
            }
            else {
                ImageView iv=(ImageView) mListView.findViewWithTag(url);
                iv.setImageBitmap(bitmap);
            }
        }
    }

    public void cancelAllTasks() {
        if(mAsyncTasks!=null) {
            for(LoadingAsyncTask task:mAsyncTasks) {
                task.cancel(true);
            }
        }
    }
}

这里,就只实现了AsyncTask的,至于多线程的,也可以实现,只不过有点麻烦,有兴趣的可以尝试一下。

2.NewsAdapter

//注意要为AbsListView的OnScrollListener
public class NewsAdapter extends BaseAdapter implements android.widget.AbsListView.OnScrollListener{
    private List<NewsBean> mList;
    private LayoutInflater mInflater;

    private ImageLoader mImageLoader;

    private int mStart,mEnd;//可见的item的起始项
    private boolean mFirstIn;//用于判断是不是刚开始运行程序

    public static String[] URLS;//保存当前所有获取到的图片的URL,然后通过mStart,mEnd就从数组中得到需要的图片的url

    public NewsAdapter(Context context,List<NewsBean> data,ListView listView) {
        mList=data;
        mInflater=LayoutInflater.from(context);
        mImageLoader=new ImageLoader(listView);

        URLS=new String[data.size()];
        for(int i=0;i<URLS.length;i++) {
            URLS[i]=data.get(i).newsIconUrl;
        }

        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=null;
        if(convertView==null) {
            viewHolder=new ViewHolder();
            convertView=mInflater.inflate(R.layout.item_layout, null);
            viewHolder.icon=(ImageView) convertView.findViewById(R.id.id_icon);
            viewHolder.title=(TextView) convertView.findViewById(R.id.id_tv_title);
            viewHolder.content=(TextView) convertView.findViewById(R.id.id_tv_content);
            convertView.setTag(viewHolder);
        }
        else {
            viewHolder=(ViewHolder) convertView.getTag();
        }
        viewHolder.icon.setImageResource(R.drawable.ic_launcher);
        String url=mList.get(position).newsIconUrl;
        viewHolder.icon.setTag(url);

//      new ImageLoader().showImageByAsyncTask(viewHolder.icon, url);
//      new ImageLoader().showImageByThread(viewHolder.icon, url);
        mImageLoader.showImageByAsyncTask(viewHolder.icon,url);
//      mImageLoader.showImageByThread(viewHolder.icon, url);
        viewHolder.title.setText(mList.get(position).newsTitle);
        viewHolder.content.setText(mList.get(position).newsContent);
        return convertView;
    }

    class ViewHolder {
        public ImageView icon;
        public TextView title,content;
    }

    //实现OnScrollListener接口涉及的两个方法
    /**
     * 只有在ListView滑动的状态切换的时候才会调用
     * 初始化的时候不会调用该方法,所以程序在首次打开的时候不会去加载图片
     * 而标题和内容是在getView方法中设置的
     * 所以还需要进行开始运行的程序时的预加载(在onScroll中设置)
     */
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {

        //如果当前状态是停止状态,就加载可见项
        if(scrollState==SCROLL_STATE_IDLE) {
            mImageLoader.loadImages(mStart, mEnd);
        }
        //否则,停止加载任务
        else {
            mImageLoader.cancelAllTasks();
        }
    }

    /**
     * 在整个滑动过程中都会去调用
     * firstVisibleItem 代表第一个可见的元素(即item)
     * visibleItemCount 代表当前可见元素的长度
     */
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem,
            int visibleItemCount, int totalItemCount) {
        mStart=firstVisibleItem;
        mEnd=firstVisibleItem+visibleItemCount-1;
        //这里要注意,因为起始的基数为0,所以这里要减1,不然在ImageLoader的loadImages方法中会发生数组越界异常

        //启动程序的时候调用,预加载
        if (mFirstIn&&visibleItemCount>0) {//还要判断visibleItemCount是否大于0,当item还没有加载的时候,visibleItemCount是等于0的
            mImageLoader.loadImages(mStart, mEnd);
            mFirstIn=false;
        }
    }
}

至于为什么是在onScroll方法中实现预加载,可以参考一下这里:
http://www.tuicool.com/articles/mEneeq
(每次开始运行程序时,listview明明没有滚动,那为什么系统会调用onScroll方法呢?)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值