Ace教你一步一步做Android新闻客户端(五) 优化Listview
今天写存货了 调试一些动画参数花了些时间 ,嘿嘿存货不多了就没法做教程了,今天来教大家优化listview,等下我把代码编辑下 这次代码有些多 所以我把条理给大家理清楚。思路就是把加载图片的权利交给OnScrollListener 。
1 首先来到 NewsAdapter这个类 ,我们给他实现了一个 AbsListView.OnScrollListener 这个接口,这个接口有两个方法:
@Override public void onScrollStateChanged(AbsListView view, int scrollState) {//Listview状态改变完才执行这个方法(比如说滑动-----》停止滑动) if (scrollState == SCROLL_STATE_IDLE){ //IDLE是定制flying是滑动 //滚动状态=停止 加载可见项 mImageLoader.loadImages(startX,endX); }else{ //其他状态我们就需要停止任务 我们的异步线程集合mTask就起到作用了 mImageLoader.cancelAllTask(); //给我们的ImageLoader创建一个方法来停止所有的异步加载任务 } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {//listview滑动过程中一直执行,传进来的第一个参数是listview,第二个是起始位置,第三个是可见项的数量,第四个是可见元素的总数 startX = firstVisibleItem; endX = firstVisibleItem + visibleItemCount; }
这一步我们把加载图片的控制权从adapter的getview方法 挪到了我们的滑动状态监听器 AbsListView.OnScrollListener 上 只有在滚动完毕后我们才加载 大大节省了内存 和不必要消耗的流量,提升了listview的
流畅度 哈哈哈哈哈哈 这样它就可以流畅的滚了
2 视角转入ImageLoader , 我们创建一个方法 loadImages 用来加载start -------end 的图片
public void loadImages (int start ,int end ){ //通过这个循环我们拿到对应的循环和URL for (int i =start ; i < end ; i++){ String url = NewsAdapter.URLS[i]; Bitmap bitmap = getBitmapFromCache(url); if (bitmap == null){ MyIconSyncTask task = new MyIconSyncTask(url);//创建 myIconSyncTask对象 task.execute(url); mTask.add(task);//加入到我们创建的mTask集合 }else{ ImageView imageView = (ImageView)mListView.findViewWithTag(url);//找到url对应的ListView imageView.setImageBitmap(bitmap); } } }
3 再创建一个取消所有线程的方法
public void cancelAllTask(){ if (mTask != null) { for (MyIconSyncTask task : mTask) { //遍历mTask中的任务, 并执行cancer方法取消掉 task.cancel(false); } } }
4 修改 ImageLoader方法的参数 ,因为我现在加载的是 start ------- end 的整体 所以只传入Imageview单个条目的控件就不太合适了,我们需要加载一整块ListView 所以我们先要通过
ImageView imageView = (ImageView)mListView.findViewWithTag(mUrl);//找到url对应的ListView
然后
imageView.setImageBitmap(bitmap);
5 最后不要忘记给listview设置监听哟~~~~~~~Ace友情提醒 不行了 太困了 要睡了 把整体代码提交给大家! github做好整体代码会放给大家~
MainActivity
package asynctask.zb.com.asynctask_02; import android.os.AsyncTask; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.widget.ListView; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.URL; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { //初始化 String TAG = "zbace";//日志TAG private ListView listView; private 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); new NewsAsyncTask().execute(URL); } /** Ace in 2016/1/20 * 创建getJsonData(传入URL地址), 把从流中读取的JSON数据封装进NewsBean中放入List集合 * 1 调用readString方法获取到jason格式的字符串, openStream与url.openConnection().geTinpuStream() 一样; * 获取到jsonString Log.d(TAG, jsonString);打印下是否可以获取到JSON数据 * 2 然后创建JSONObject对象,传入jsonString。 * 3 getJSONArray("data")方法 从中取出JSONArray, * 在创建个for循环遍历JSONArray并取出newsicon,title,content,等信息 * 最后把信息放入NewsBean,再添加进数组 * */ private List<NewsBean> getJsonData(String url){ List<NewsBean> nesBeanList = new ArrayList<>(); try { String jsonString = readStream(new URL(url).openStream()); Log.d(TAG, jsonString); JSONObject jsonObject; NewsBean newsBean; try { jsonObject = new JSONObject(jsonString); JSONArray jsonArray = jsonObject.getJSONArray("data"); for (int i = 0 ; i <jsonArray.length(); i++ ){ //每个JSONArray 的元素都是一个JSONObject jsonObject = jsonArray.getJSONObject(i); //把得到的jsonObject, 放入NewsBean newsBean = new NewsBean(); newsBean.newsIconUrl = jsonObject.getString("picSmall"); newsBean.newsTitle = jsonObject.getString("name"); newsBean.newsContent = jsonObject.getString("description"); nesBeanList.add(newsBean); } } catch (JSONException e) { e.printStackTrace(); } } catch (IOException e) { e.printStackTrace(); } return nesBeanList; //记得返回list } /** Ace in 2016/1/20 * readStream方法是为了读取流中的数据从而获得流里的JSONString * * */ private String readStream(InputStream is){ InputStreamReader isr; String result = ""; try { String line = ""; //用把字节流转换为字符流(不转字符流无法显示中文),并设置编码为UTF-8; isr = new InputStreamReader(is,"utf-8"); //套上缓冲流 BufferedReader br = new BufferedReader(isr); //创建一个while循环 while ((line=br.readLine()) != null ){ result += line;//这就得到了我们需要的JSON字符串,从JSON字符串中就可以得到我们想要数据 } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return result; } /** Ace in 2016/1/20 * 异步获取JSON数据 * * */ class NewsAsyncTask extends AsyncTask<String,Void,List<NewsBean>>{ @Override protected List<NewsBean> doInBackground(String... params) { return getJsonData(params[0]);//params就是我们传进来的String URL 网址 只传进来了一个 就输入[0] } @Override protected void onPostExecute(List<NewsBean>newsBeanList) { super.onPostExecute(newsBeanList); NewsAdapter newsAdapter = new NewsAdapter(MainActivity.this ,newsBeanList,listView); listView.setAdapter(newsAdapter); } } }
NewsAdapter:
package asynctask.zb.com.asynctask_02; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import java.net.URL; import java.util.List; /** * Created by Ace on 2016/1/20. */ public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener { private List<NewsBean> mlist; private LayoutInflater mInflater; private ImageLoader mImageLoader; private int startX; private int endX; public static String URLS[];//创建一个变量 并把权限设置成public public NewsAdapter(Context context,List<NewsBean>data,ListView listView){ //映射下 把data传给mlist mlist = data; //从一个上下文中(这里的上下文是MainActivity),获得一个布局填充器,这样你就可以使用这个填充器的inflater.inflate()来把xml布局文件转为View对象了,然后利用view对象,findViewById就可以找到布局中的组件 mInflater = LayoutInflater.from(context); mImageLoader = new ImageLoader(listView); //在适配器初始化ImageLoader URLS = new String[data.size()];//初始化URLS数组 把data里面的icon的url信息放到里面来,方便取用 for (int i = 0 ; i <data.size(); i++){ URLS[i] = data.get(i).newsIconUrl; } listView.setOnScrollListener(this); } @Override public Object getItem(int position) { return mlist.get(position); } @Override public int getCount() { return mlist.size(); } @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.adapter_item,null); viewHolder.iconimage = (ImageView)convertView.findViewById(R.id.tvimage); viewHolder.title = (TextView)convertView.findViewById(R.id.tvtitle); viewHolder.content = (TextView)convertView.findViewById(R.id.tvcontent); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder)convertView.getTag(); viewHolder.iconimage.setImageResource(R.mipmap.ic_launcher); String url = mlist.get(position).newsIconUrl; viewHolder.iconimage.setTag(url);//给imageview设置标签 是为了增加一个判断的标准(再imageloader类里),只有URL地址和当前位置的Item的图片相匹配才显示 // new ImageLoader().showImageByThread(viewHolder.iconimage, mlist.get(position).newsIconUrl); mImageLoader.showImageByAsyncTask(viewHolder.iconimage, mlist.get(position).newsIconUrl);//不能使用 new ImageLoader(). 因为每new一次都创建一个ImageLoader()这样就会有很多的lru viewHolder.title.setText(mlist.get(position).newsTitle); viewHolder.content.setText(mlist.get(position).newsContent); } return convertView; } @Override public void onScrollStateChanged(AbsListView view, int scrollState) {//Listview状态改变完才执行这个方法(比如说滑动-----》停止滑动) if (scrollState == SCROLL_STATE_IDLE){ //IDLE是定制flying是滑动 //滚动状态=停止 加载可见项 mImageLoader.loadImages(startX,endX); }else{ //其他状态我们就需要停止任务 我们的异步线程集合mTask就起到作用了 mImageLoader.cancelAllTask(); //给我们的ImageLoader创建一个方法来停止所有的异步加载任务 } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {//listview滑动过程中一直执行,传进来的第一个参数是listview,第二个是起始位置,第三个是可见项的数量,第四个是可见元素的总数 startX = firstVisibleItem; endX = firstVisibleItem + visibleItemCount; } class ViewHolder{ public TextView title; public ImageView iconimage; public TextView content; } }
ImageLoader
package asynctask.zb.com.asynctask_02; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.os.Message; import android.util.Log; import android.util.LruCache; import android.widget.ImageView; import android.widget.ListView; import java.io.BufferedInputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.util.HashSet; import java.util.Set; /** * Created by Administrator on 2016/1/20. */ public class ImageLoader { private ImageView mImageView; private String mUrl; public LruCache<String,Bitmap> mCache; private ListView mListView;//因为我们要加载start - end 的所有图片 是个整体而不是单个条目,所以创建一个listview 通过listview的findViewWithTag方法来找到对应的ImageView private Set<MyIconSyncTask> mTask;//创建一个Set 集合 用于装我们所有的AsyncTask public ImageLoader (ListView listView){//给构造方法传入listview 然后初始化 mListView = listView;//初始化listview mTask = new HashSet<>();//初始化mTask int maxMemory = (int) Runtime.getRuntime().maxMemory(); //获取虚拟机可用内存(内存占用超过该值的时候,将报OOM异常导致程序崩溃) int cacheSzie = maxMemory/4; //使用可用内存的1/4来作为Memory Cache mCache = new LruCache<String,Bitmap>(cacheSzie) { @Override protected int sizeOf(String key, Bitmap value) { return value.getByteCount(); //返回Bitmap占用的空间,告诉系统我这张图片要用多少内存 } }; } //把bitmap加入到缓存 public void addBitmapToCache(String mUrl, Bitmap bitmap) { if (getBitmapFromCache(mUrl) == null) { mCache.put(mUrl, bitmap); } } //从缓存中获取数据 public Bitmap getBitmapFromCache(String mUrl) { return mCache.get(mUrl); } android.os.Handler mHandler = new android.os.Handler(){ @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (mImageView.getTag().equals(mUrl)) mImageView.setImageBitmap((Bitmap)msg.obj); } }; public void showImageByThread(ImageView imageView, final String url) { mImageView =imageView; mUrl = url;//对传过来的imageView 和url进行缓存(为了避免程序逻辑顺序错误,和viewholder的机制差不多) new Thread() { @Override public void run() { super.run(); Bitmap bitmap = getBitmapFromURL(url); Message message = Message.obtain(); message.obj = bitmap; mHandler.sendMessage(message); } }.start(); } public void cancelAllTask(){ if (mTask != null) { for (MyIconSyncTask task : mTask) { //遍历mTask中的任务, 并执行cancer方法取消掉 task.cancel(false); } } } // 用来加载start -------end 的图片 public void loadImages (int start ,int end ){ //通过这个循环我们拿到对应的循环和URL for (int i =start ; i < end ; i++){ String url = NewsAdapter.URLS[i]; Bitmap bitmap = getBitmapFromCache(url); if (bitmap == null){ MyIconSyncTask task = new MyIconSyncTask(url);//创建 myIconSyncTask对象 task.execute(url); mTask.add(task);//加入到我们创建的mTask集合 }else{ ImageView imageView = (ImageView)mListView.findViewWithTag(url);//找到url对应的ListView imageView.setImageBitmap(bitmap); } } } //创建从URL获取Bitmap的放方法 public Bitmap getBitmapFromURL(String stringUrl) { Bitmap bitmap; BufferedInputStream bis = null; URL url1 = null; try { url1 = new URL(stringUrl); } catch (MalformedURLException e) { e.printStackTrace(); } HttpURLConnection connection = null; try { connection = (HttpURLConnection) url1.openConnection(); } catch (IOException e) { e.printStackTrace(); } try { bis = new BufferedInputStream(connection.getInputStream()); bitmap = BitmapFactory.decodeStream(bis); connection.disconnect(); return bitmap; } catch (IOException e) { e.printStackTrace(); } finally { try { bis.close(); } catch (IOException e) { e.printStackTrace(); } }return null; } public void showImageByAsyncTask (ImageView imageView, String url){ Bitmap bitmap = getBitmapFromCache(url); if (bitmap == null){ imageView.setImageResource(R.mipmap.ic_launcher);//在bitmap=空的时候我们就让它显示默认图片,这样修改后把加载图片的控制权全部放入新建的loadImages方法中了 }else{ imageView.setImageBitmap(bitmap); } } class MyIconSyncTask extends AsyncTask<String,Void,Bitmap> { // private ImageView mImageView; private String mUrl; public MyIconSyncTask(String url){ mUrl = url; // mImageView = imageView; } @Override protected Bitmap doInBackground(String... params) { String mUrl = params[0]; //从网络获取图片,并存入缓存中 Bitmap bitmap = getBitmapFromURL(mUrl); if (bitmap != null){ addBitmapToCache(mUrl,bitmap); } return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); // if (mImageView.getTag().equals(mUrl)) { // mImageView.setImageBitmap(bitmap); ImageView imageView = (ImageView)mListView.findViewWithTag(mUrl);//找到url对应的ListView if (imageView != null && bitmap != null) { imageView.setImageBitmap(bitmap); } mTask.remove(this);//加载位图完毕移除这个异步线程 } } }
NewsBean
package asynctask.zb.com.asynctask_02; import java.net.URL; /** * Created by Administrator on 2016/1/20. */ public class NewsBean { public String newsIconUrl; public String newsTitle; public String newsContent; }
还有一个优化就是ViewHolder 和 concertView ,这张图我觉得非常棒
LsitView和Adapter
工作原理:
1.ListView针对List中每个item,要求adapter给我一个视图(getView)
2.一个新的视图被返回并显示
如果我们有上亿个item要显示怎么办?为每个项目创建一个新视图?NO!这不可能~~~Android实际上为你缓存了视图
Android中有个叫做Recycler(反复循环器)的构件,下图是它的工作原理:
![](http://img-my.csdn.net/uploads/201304/18/1366248240_7944.jpg)
1.如果你有10亿个项目(item),其中只有可见的项目存在内存中,其他的在Recycler中
2.ListView先请求一个type1视图(getView),然后请求其他可见的项目。conVertView在getView中时null的
3.当item1滚出屏幕,并且一个新的项目从屏幕地段上来时,ListView再请求一个type1视图。convertView此时不是空值了,它的值是item1.你只需要设定新的数据返回convertView,不必重新创建一个视图。这样直接使用convertView从而减少了很不不必要view的创建
!!!!!!更快的方式是定义一个ViewHolder,将convertView的tag设置为ViewHolder,不为空是重新使用
ViewHolder只是将需要缓存的那些view封装好,convertView的setTag才是将这些缓存起来供下次调用
当你的listview里布局多样化的时候 viewholder的作用就有比较明显的体现了。 当然了,单一模式的布局一样有性能优化的作用 只是不直观。 假如你2种模式的布局 当发生回收的时候 你会用setTag分别记录是哪两种 这两种模式会被封装到viewholder中进行保存方便你下次使用。 VH就是个静态类 与缓存无关的 ,下面看我们客户端代码的listview
public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder= null; if (convertView == null){ viewHolder = new ViewHolder(); convertView = mInflater.inflate(R.layout.adapter_item,null); viewHolder.iconimage = (ImageView)convertView.findViewById(R.id.tvimage); viewHolder.title = (TextView)convertView.findViewById(R.id.tvtitle); viewHolder.content = (TextView)convertView.findViewById(R.id.tvcontent); convertView.setTag(viewHolder); }else{ viewHolder = (ViewHolder)convertView.getTag(); viewHolder.iconimage.setImageResource(R.mipmap.ic_launcher); String url = mlist.get(position).newsIconUrl; viewHolder.iconimage.setTag(url);//给imageview设置标签 是为了增加一个判断的标准(再imageloader类里),只有URL地址和当前位置的Item的图片相匹配才显示 // new ImageLoader().showImageByThread(viewHolder.iconimage, mlist.get(position).newsIconUrl); mImageLoader.showImageByAsyncTask(viewHolder.iconimage, mlist.get(position).newsIconUrl);//不能使用 new ImageLoader(). 因为每new一次都创建一个ImageLoader()这样就会有很多的lru viewHolder.title.setText(mlist.get(position).newsTitle); viewHolder.content.setText(mlist.get(position).newsContent); } return convertView; }
class ViewHolder{
public TextView title;
public ImageView iconimage;
public TextView content;
}
}