• 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;

    }
    }
     
    
    
    


  • 相关阅读:
    Mongo简单查询总结
    将对象转换成Dictionary 字典
    C#调用NPOI组件导出Excel表格
    Lambda中的一些方法的总结
    LinQ总结
    简单的爬虫 一
    Python 中的注释规范
    在VM上配置一个能上网的网络设置
    Python 中新式类的内置方法
    Python 中的locals()
  • 原文地址:https://www.cnblogs.com/AceIsSunshineRain/p/5152634.html
Copyright © 2020-2023  润新知