美文网首页图片处理面试精选案例
ListView异步加载图片方法和滚动优化

ListView异步加载图片方法和滚动优化

作者: 爱你会吃醋 | 来源:发表于2015-10-05 16:49 被阅读3113次

    基本流程:
    1.异步任务从指定的网页中获取JSON信息,解析JSON数据,自定义JAVA BEAN对象封装所需要的数据项(标题、摘要、图片URL地址等信息),并将BEAN对象组织成变长数组ArrayList。

    2.自定义BaseAdapter,通过内部类ViewHolder提高ListView 的Item复用效率。

    注意:
    在getView方法中, 对于inflate方法的第三个参数attachToRoot应该使用false,否则将会导致UnsupportedOperationException

    convertView convertView = inflater.inflate(R.layout.item_layout,parent,false);
    

    注意,不要写成

    convertView convertView = inflater.inflate(R.layout.item_layout,parent);
    

    因为这个函数,默认第三个参数为true,也将导致发生错误。

    3.BaseAdapter中为ImageView 设置图片及其优化。
    因为我们解析得到的数据只包括图片的URL地址,并没有得到实际的图片,所以需要从网络上获取实际的图片,得到对应的bitmap。

    图片的获取有两个思路,一个是通过多线程的方法,另一个是通过异步任务AsyncTask的方法。

    优化点:
    1.防止图片的错乱
    这个现象主要是因为ListView的重用机制,当一个可见的item划出屏幕外时,将会放入一个回收池,新进入屏幕中的item将从回收池中取出一个item复用,而不再是重新生成一个item。这样无论一个ListView中有多少个item,在显示的过程中最多只需要生成n个item(n为一屏中可以同时显示的item的数目)。

    现象分析:
    当我们滚动屏幕时,假设item n新进入屏幕内,复用了item m,此时item n所要显示的图片开始下载,如果正好item m的图片下载完毕,那么就会更新item, 导致item n显示了item m的图片,当item n的图片下载完毕后,又会再次更新item,导致图片再次发生变化。这样就会导致图片的错位和闪烁(多次更新现象),其原因就是因为item m虽然不在屏幕内,但是item n复用了item m,即两者对应的item实际是内存中的同一块区域。

    优化方案:
    在getView方法中为ImageView绑定一个Tag标志(该标志应该能够作为标识该ImageView的唯一标识),比较简单常见的方法就是将存有图片URL地址的字符串作为该标识。

    <pre><code>
    img.setTag(url);</code></pre>

    同时在进行异步任务下载图片时,当图片下载完毕(doInBackground方法执行完毕),执行onPostExecute方法时,不再是直接为img设置图片,而是要增加一个判断,只有当下载好的图片的URL地址和img的tag标识相同时,才对图片进行更新。

            if (img.getTag() != null && img.getTag().equals(url)) {
                img.setImageBitmap(result);
            }
    

    2.避免图片的多次下载
    如果每次滚动重新显示item时,都需要重新从网上下载图片资源,显得非常不友好。
    解决方式:
    为了避免每次显示item都要重新从网上下载对应的图片资源,可以引入缓存。包括一级缓存(内存缓存)LruCache,以及二级缓存(硬盘缓存)DiskCache.

        //添加缓存,
    LruCache<String, Bitmap> cache;// 缓存,本质相当于一个map
    
    public ImageLoader() {
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 4;
        cache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                // TODO Auto-generated method stub
                // 每次存入时调用,返回Bitmap 的实际大小
                return value.getByteCount();
            }
        };
    }
    
    //从缓存中获取图片
    Bitmap getFromCache(String url)
    {
        return cache.get(url);
    }
    
    //将图片存入缓存
    void addIntoCache(Bitmap bitmap, String url)
    {
        if (getFromCache(url) == null) {
            cache.put(url, bitmap);
        }
    }
    

    这样,每次在加载图片时,首先判断缓存中是否已经存在该图片资源,只有当不存在时,才会从网络上去获取资源。


    滚动优化:(防止滚动时因为加载而导致卡顿)
    1.ListView滑动停止后才加载可见项
    2.滑动时,取消所有加载项
    怎么通过tag来获取对应的ImageView?
    通过findViewWithTag方法。

    滚动优化实现思路:
    1.既然要针对滚动过程进行优化,就需要实现OnScrollListener接口,因为需要控制item的显示,所以可以在Adapter中实现该接口。(需要在构造方法中对listview注册该接口)

    listView.setOnScrollListener(this);//在Adapter中实现该接口
    

    实现该接口需要覆写两个函数

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // TODO Auto-generated method stub
        if(scrollState==SCROLL_STATE_IDLE)
        {//正常状态(没有滚动)时,开始加载任务
            loader_scroll.LoadImageByAsyncTask(ImgStart,ImgEnd);
        }else{
            //停止任务
            loader_scroll.cancelAllTask();
        }
        
    }
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        // TODO Auto-generated method stub
        ImgStart=firstVisibleItem;
        ImgEnd= ImgStart+visibleItemCount;
        
        if(first_flag==1 && visibleItemCount>0)
        {//首次加载预处理
            loader_scroll.LoadImageByAsyncTask(ImgStart,ImgEnd);
        }
    }
    

    当处于滑动状态时,停止任务(不会执行onPostExecute方法,即不会导致界面的更新重绘,滚动将更为流畅);
    当滚动停止时,加载当前可以显示的所有item,通过维护ImgStart和ImgEnd这两个变量(当前可见的第一个Item和可见的最后一个Item),可以控制当前可以显示的item的加载。

    因为需要控制多个Item的加载,所以传入的参数不再是ImageView,而是ImgStart和ImgEnd,但是又有一个问题,怎么传递URL?我们可以在Adapter中创建一个静态数组,用以存储所有图片的URL,这样就可以在ImageLoader类中获取到URL数组,从而执行对图片的异步加载。

    为了让ListView实现正常的功能,我们还需要进行一个首次加载的预处理,否则只有当listView滚动一次以后才会执行加载任务。

    取消加载项时,
    task.cancel(false)传参数要传false,为什么?
    因为调用该方法就可以保证不执行UI线程上的onPostExecute更新界面的函数,滚动的优化关键在于不要在滚动过程中重绘界面,既不更新UI即可,并不需要强行停止图片的缓存。传入false,不会强制中断图片的下载缓存,同时保证不会因为图片下载完毕而导致UI重绘。


    ImageLoader示例

    1.通过多线程下载(未进行滚动优化版本)
    使用说明,因为ImageView和图片的url信息是由ImageLoader_thread类封装的,所以每次使用该类加载图片时都需要使用一个新的实例。

    public class ImageLoader_thread {
    ImageView img;
    String url;
    
    public ImageLoader_thread(ImageView img,String url)
    {
        this.img=img;
        this.url=url;
    }
    
    Handler handler= new Handler(){
    
        Bitmap bitmap=null;
        @Override
        public void handleMessage(Message msg) {
            // TODO Auto-generated method stub
            super.handleMessage(msg);
            bitmap = (Bitmap) msg.obj;
            if(img.getTag().equals(url))
            {
                img.setImageBitmap(bitmap);
            }
        }
        
    };
    
    
    void LoadImageByThread()
    {
        new Thread(){
    
            @Override
            public void run() {
                // TODO Auto-generated method stub
                super.run();
                try {
                    URL _url =new URL(url);
                    InputStream is = _url.openStream();
                    Bitmap bitmap = BitmapFactory.decodeStream(is);
                    Message msg=Message.obtain();
                    msg.obj=bitmap;
                    handler.sendMessage(msg);
                } catch (MalformedURLException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }   
        }.start();
    }
    }
    

    2.使用异步任务AsyncTask下载(未进行滚动优化版本)

    public class ImageLoader {
    //添加缓存,
    LruCache<String, Bitmap> cache;// 缓存,本质相当于一个map
    
    public ImageLoader() {
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 4;
        cache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap value) {
                // TODO Auto-generated method stub
                // 每次存入时调用,返回Bitmap 的实际大小
                return value.getByteCount();
            }
        };
    }
    
    //从缓存中获取图片
    Bitmap getFromCache(String url)
    {
        return cache.get(url);
    }
    
    //将图片存入缓存
    void addIntoCache(Bitmap bitmap, String url)
    {
        if (getFromCache(url) == null) {
            cache.put(url, bitmap);
        }
    }
    
    void LoadImageByAsyncTask(ImageView img, String url) {
        new MyAsyncTask(img, url).execute(url);
    }
    
    class MyAsyncTask extends AsyncTask<String, Void, Bitmap> {
        ImageView img;
        String url;
    
        public MyAsyncTask(ImageView img, String url) {
            this.img = img;
            this.url = url;
        }
    
        @Override
        protected Bitmap doInBackground(String... params) {
            // TODO Auto-generated method stub
            Bitmap bitmap = null;
            //每次准备从网上获取资源前先判断缓存中是否存在该图片
            bitmap = getFromCache(url);
            if(bitmap!=null)
            {
                return bitmap;
            }
            // url=params[0];
            try {
                URL _url = new URL(url);
                InputStream is = _url.openStream();
                bitmap = BitmapFactory.decodeStream(is);
                //每次下载完后将图片存入缓存中
                if(bitmap!=null)
                {
                    addIntoCache(bitmap,url);
                }
                return bitmap;
            } catch (MalformedURLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return null;
        }
    
        @Override
        protected void onPostExecute(Bitmap result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            if (img.getTag() != null && img.getTag().equals(url)) {
                img.setImageBitmap(result);
            }
            
        }
    }
    }
    

    3.异步下载图片(滚动优化版,仅指定起始Item号和结束item号)

    //对listView滚动时进行更进一步的优化
    public class ImageLoader_srcoll_better {

    // 添加缓存,
    LruCache<String, Bitmap> cache;// 缓存,本质相当于一个map
    int ImgStart,ImgEnd;
    //存储URL数组
    List<String> URLS=MyAdapter.URLS;
    
    ListView mlistView;
    Set<MyAsyncTask> taskSet;
    public ImageLoader_srcoll_better(ListView listView) {
        this.mlistView=listView;
        taskSet = new HashSet<MyAsyncTask>();
        
            int maxMemory = (int) Runtime.getRuntime().maxMemory();
            int cacheSize = maxMemory / 4;
            cache = new LruCache<String, Bitmap>(cacheSize) {
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    // TODO Auto-generated method stub
                    // 每次存入时调用,返回Bitmap 的实际大小
                    return value.getByteCount();
                }
            };
        }
    
    // 从缓存中获取图片
    Bitmap getFromCache(String url) {
        return cache.get(url);
    }
    
    // 将图片存入缓存
    void addIntoCache(Bitmap bitmap, String url) {
        if (getFromCache(url) == null) {
            cache.put(url, bitmap);
        }
    }
    
    //更改为为从ImgStart开始到ImgEnd(包括ImgStart但不包括ImgEnd)的ImageView设置图像,而不是
    //针对特定的某个ImageView
    void LoadImageByAsyncTask(int ImgStart , int ImgEnd) {
        String url;
        for (int i=ImgStart; i<ImgEnd; i++)
        {
            url=URLS.get(i);
            MyAsyncTask task = new MyAsyncTask(url);
            task.execute(URLS.get(i));
            taskSet.add(task);
            
        }
    }
    
    class MyAsyncTask extends AsyncTask<String, Void, Bitmap> {
        ImageView img;
        String url;
    
        public MyAsyncTask(String url) {
            this.url = url;
        }
    
        @Override
        protected Bitmap doInBackground(String... params) {
            // TODO Auto-generated method stub
            //怎么通过tag来获取对应的ImageView
            Bitmap bitmap = null;
            // 每次准备从网上获取资源前先判断缓存中是否存在该图片
            bitmap = getFromCache(url);
            if (bitmap != null) {
                return bitmap;
            }
            // url=params[0];
            try {
                URL _url = new URL(url);
                InputStream is = _url.openStream();
                bitmap = BitmapFactory.decodeStream(is);
                // 每次下载完后将图片存入缓存中
                if (bitmap != null) {
                    addIntoCache(bitmap, url);
                }
                return bitmap;
            } catch (MalformedURLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return null;
        }
    
        @Override
        protected void onPostExecute(Bitmap result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            img = (ImageView)mlistView.findViewWithTag(url);
            if (img != null && result!=null) {
                img.setImageBitmap(result);
            }
            taskSet.remove(this);
        }
    }
    
    public void cancelAllTask()
    {
        if(taskSet!=null)
        {
            for(MyAsyncTask task:taskSet)
            {
                //滚动时保证不重绘即可(不执行UI线程的onPostExecute),但是允许继续下载缓存图片
                task.cancel(false);//>?????
            }
        }
    }
    }
    

    下面给出一个完整的滚动优化后的示例代码:
    MyAdapter.java

    public class MyAdapter extends BaseAdapter implements OnScrollListener{
    
    //不要忘记注册Listener!!!!!
    List<Beans> list;
    Context context;
    LayoutInflater inflater;
    //ViewHolder 不能放在外面
    //ViewHolder holder;
    
    int ImgStart ,ImgEnd;
    ListView listView;
    ImageLoader_srcoll_better loader_scroll;
    //首次加载预处理
    int first_flag = 1;
    
    static List<String> URLS =new ArrayList<String>();
    
    ImageLoader loader;
    
    public MyAdapter(Context context,List<Beans>list,ListView listView){
        this.context=context;
        this.list=list;
        inflater = LayoutInflater.from(context);
        loader = new ImageLoader();
        //初始化URLS列表
        for(int i=0;i<list.size();i++)
        {
            URLS.add(list.get(i).img);
        }
        this.listView=listView;
        listView.setOnScrollListener(this);
        loader_scroll=new ImageLoader_srcoll_better(listView);
    }
    @Override
    public int getCount() {
        // TODO Auto-generated method stub
        return list.size();
    }
    
    @Override
    public Object getItem(int position) {
        // TODO Auto-generated method stub
        return list.get(position);
    }
    
    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }
    
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        ViewHolder holder;
        if(convertView==null)
        {
            holder = new ViewHolder();
            //必须加flase否则出错,为什么???!!!
            convertView = inflater.inflate(R.layout.item_layout, parent,false);
            //也可以使用null,但不能用parent
            //convertView = inflater.inflate(R.layout.item_layout, null);
            holder.img= (ImageView)convertView.findViewById(R.id.item_img);
            holder.txt= (TextView)convertView.findViewById(R.id.item_txt);
            convertView.setTag(holder);
        }else{
            holder=(ViewHolder)convertView.getTag();
        }
        
        holder.txt.setText(list.get(position).title);
        holder.img.setImageResource(R.drawable.ic_launcher);
        holder.img.setTag(list.get(position).img);
        //loader.LoadImageByAsyncTask(holder.img, list.get(position).img);
        //new ImageLoader_thread(holder.img,list.get(position).img).LoadImageByThread();
        return convertView;
    }
    
    class ViewHolder{
        ImageView img;
        TextView txt;
    }
    
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // TODO Auto-generated method stub
        if(scrollState==SCROLL_STATE_IDLE)
        {//正常状态(没有滚动)时,开始加载任务
            loader_scroll.LoadImageByAsyncTask(ImgStart,ImgEnd);
        }else{
            //停止任务
            loader_scroll.cancelAllTask();
        }
        
    }
    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        // TODO Auto-generated method stub
        ImgStart=firstVisibleItem;
        ImgEnd= ImgStart+visibleItemCount;
        
        if(first_flag==1 && visibleItemCount>0)
        {//首次加载预处理
            loader_scroll.LoadImageByAsyncTask(ImgStart,ImgEnd);
        }
    }
    }
    

    MainActivity.java

    public class MainActivity extends Activity {
    ListView listview;
    List<Beans> list;
    String url="http://www.imooc.com/api/teacher?type=4&num=30";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        listview = (ListView)findViewById(R.id.listview);
        getBeansList();
    }
    
    void getBeansList(){
        new MyAsyncTask().execute(url);
        Log.i("logcat", "zhixingle!");
    }
    
    List<Beans> parseData(String json)
    {
        List<Beans> list = new ArrayList<Beans>();
        //String title,img;
        try {
            JSONObject jb= new JSONObject(json);
            JSONArray array=jb.getJSONArray("data");
            for (int i=0;i<array.length();i++)
            {
                jb=array.getJSONObject(i);
                list.add(new Beans(jb.getString("name"),jb.getString("picSmall")));
            }
            return list;
        } catch (JSONException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        return null;
        
    }
    
    class MyAsyncTask extends AsyncTask<String, Void, List<Beans>>{
    
        @Override
        protected List<Beans> doInBackground(String... params) {
            // TODO Auto-generated method stub
            List<Beans> list=null;
            String line="",json="";
            try {
                URL _url = new URL(params[0]);
                InputStream is = _url.openStream();
                BufferedReader br = new BufferedReader(new InputStreamReader(is));
                while((line=br.readLine())!=null)
                {
                    json+=line;//获取到了json数据
                }
                list=parseData(json);
                return list;
            } catch (MalformedURLException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            return list;
        }
    
        @Override
        protected void onPreExecute() {
            // TODO Auto-generated method stub
            super.onPreExecute();
        }
    
        @Override
        protected void onPostExecute(List<Beans> result) {
            // TODO Auto-generated method stub
            super.onPostExecute(result);
            
            MyAdapter adapter = new MyAdapter(MainActivity.this, result, listview);
            listview.setAdapter(adapter);
            
        }
    
        @Override
        protected void onProgressUpdate(Void... values) {
            // TODO Auto-generated method stub
            super.onProgressUpdate(values);
        }
        
    }
    
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
    }
    

    Beans.java

    public class Beans {
    
    public String title;
    public String img;
    
    public Beans(String title,String img)
    {
        this.title=title;
        this.img=img;
    }
    }
    

    相关文章

      网友评论

      本文标题:ListView异步加载图片方法和滚动优化

      本文链接:https://www.haomeiwen.com/subject/yivgcttx.html