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

基本流程:
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地址的字符串作为该标识。


img.setTag(url);

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

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

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

    //添加缓存,
LruCache cache;// 缓存,本质相当于一个map

public ImageLoader() {
    int maxMemory = (int) Runtime.getRuntime().maxMemory();
    int cacheSize = maxMemory / 4;
    cache = new LruCache(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 cache;// 缓存,本质相当于一个map

public ImageLoader() {
    int maxMemory = (int) Runtime.getRuntime().maxMemory();
    int cacheSize = maxMemory / 4;
    cache = new LruCache(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 {
    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 cache;// 缓存,本质相当于一个map
int ImgStart,ImgEnd;
//存储URL数组
List URLS=MyAdapter.URLS;

ListView mlistView;
Set taskSet;
public ImageLoader_srcoll_better(ListView listView) {
    this.mlistView=listView;
    taskSet = new HashSet();
    
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 4;
        cache = new LruCache(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 {
    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 list;
Context context;
LayoutInflater inflater;
//ViewHolder 不能放在外面
//ViewHolder holder;

int ImgStart ,ImgEnd;
ListView listView;
ImageLoader_srcoll_better loader_scroll;
//首次加载预处理
int first_flag = 1;

static List URLS =new ArrayList();

ImageLoader loader;

public MyAdapter(Context context,Listlist,ListView listView){
    this.context=context;
    this.list=list;
    inflater = LayoutInflater.from(context);
    loader = new ImageLoader();
    //初始化URLS列表
    for(int i=0;i0)
    {//首次加载预处理
        loader_scroll.LoadImageByAsyncTask(ImgStart,ImgEnd);
    }
}
}

MainActivity.java

public class MainActivity extends Activity {
ListView listview;
List 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 parseData(String json)
{
    List list = new ArrayList();
    //String title,img;
    try {
        JSONObject jb= new JSONObject(json);
        JSONArray array=jb.getJSONArray("data");
        for (int i=0;i>{

    @Override
    protected List doInBackground(String... params) {
        // TODO Auto-generated method stub
        List 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 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异步加载图片方法和滚动优化)