基本流程:
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;
}
}