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(反复循环器)的构件,下图是它的工作原理:


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;

}
}
 




原文地址:https://www.cnblogs.com/AceIsSunshineRain/p/5152634.html