zoukankan      html  css  js  c++  java
  • android 之图片异步加载

    一.概述

    本文来自"慕课网" 的学习,只是对代码做一下分析

    图片异步加载有2种方式:  (多线程/线程池) 或者 用其实AsyncTask , 其实AsyncTask底层也是用的多线程.

    使用缓存的好处是 , 提高流畅度, 节约流量.

    二.代码

    1.先看图片加载工具类

    public class ImageLoader {
        private ImageView mImageview;
        private String mUrl;
        //创建缓存
        private LruCache<String, Bitmap> mCaches;
        private ListView mListView;
        private Set<NewsAsyncTask> mTask;
    
        public ImageLoader(ListView listView) {
            mListView = listView;
            mTask = new HashSet<>();
            //获得最大的缓存空间
            int maxMemory = (int) Runtime.getRuntime().maxMemory();
            //赋予缓存区最大缓存的四分之一进行缓存
            int cacheSize = maxMemory / 4;
            mCaches = new LruCache<String, Bitmap>(cacheSize) {
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    //在每次存入缓存的时候调用
                    return value.getByteCount();
                }
            };
        }
    
        //将图片通过url与bitmap的键值对形式添加到缓存中
        public void addBitmapToCache(String url, Bitmap bitmap) {
            if (getBitmapFromCache(url) == null) {
                mCaches.put(url, bitmap);
            }
        }
    
        //通过缓存得到图片
        public Bitmap getBitmapFromCache(String url) {
            return mCaches.get(url);
        }
    
        private Handler mHandler = new 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, String url) {
            mImageview = imageView;
            mUrl = url;
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    Bitmap bitmap = getBitmapFromUrl(mUrl);
                    Message message = Message.obtain();
                    message.obj = bitmap;
                    mHandler.sendMessage(message);
                }
            }.start();
        }
    
        //通过异步任务的方式去加载图片
    
        public void showImageByAsyncTask(ImageView imageView, String url) {
            //先从缓存中获取图片
            Bitmap bitmap = getBitmapFromCache(url);
            if (bitmap == null) {
               imageView.setImageResource(R.mipmap.ic_launcher);
            } else {
                imageView.setImageBitmap(bitmap);
            }
        }
    
        private class NewsAsyncTask extends AsyncTask<String, Void, Bitmap> {
    
       //     private ImageView mImageView;
            private String mUrl;
    
            public NewsAsyncTask( String url) {
       //         mImageview = imageView;
                mUrl = url;
            }
    
            @Override
            protected Bitmap doInBackground(String... params) {
                String url = params[0];
                //从网络获取图片
                Bitmap bitmap = getBitmapFromUrl(url);
                //将图片加入缓存中
                if (bitmap != null) {
                    addBitmapToCache(url, bitmap);
                }
                return bitmap;
            }
    
            @Override
            protected void onPostExecute(Bitmap bitmap) {
                super.onPostExecute(bitmap);
                ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl);
                if (imageView!=null&&bitmap!=null){
                    imageView.setImageBitmap(bitmap);
                }
                mTask.remove(this);
            }
        }
    
        //滑动时加载图片
        public void loadImages(int start, int end) {
            for (int i = start; i < end; i++) {
                String url = NewsAdapter.URLS[i];
                //先从缓存中获取图片
                Bitmap bitmap = getBitmapFromCache(url);
                if (bitmap == null) {
                    NewsAsyncTask task = new NewsAsyncTask(url);
                    task.execute(url);
                    mTask.add(task);
                } else {
                    ImageView imageView = (ImageView) mListView.findViewWithTag(url);
                    imageView.setImageBitmap(bitmap);
                }
            }
        }
    
        //停止时取消所有任务加载
        public void cancelAllTasks(){
            if (mTask!=null){
                for (NewsAsyncTask task :mTask){
                    task.cancel(false);
                }
            }
        }
        //网络获取图片
        private Bitmap getBitmapFromUrl(String urlString) {
            Bitmap bitmap;
            InputStream is = null;
            try {
                URL url = new URL(urlString);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                is = new BufferedInputStream(connection.getInputStream());
                bitmap = BitmapFactory.decodeStream(is);
                connection.disconnect();
                return bitmap;
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    assert is != null;
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return null;
        }
    }

    需要注意的几个部分:

      <1

    LruCache<String, Bitmap> mCaches 这是创建一个集合去存储缓存的图片,底层是HashMap实现的,其实和我们之前java中用到HashMap  弱引用/软引用比较类似, 但是
    自2.3以后Android将更频繁的调用GC,导致软引用缓存的数据极易被释放。所以不能用之前的方式来缓存图片了,
    LruCache使用一个LinkedHashMap简单的实现内存的缓存,没有软引用,都是强引用。如果添加的数据大于设置的最大值,就删除最先缓存的数据来调整内存。
    我们可以在构造方法中,先得到当前应用所占总缓存大小,然后分出1/4用于存储图片,对应代码如下:

     //获得当前应用最大的缓存空间
            int maxMemory = (int) Runtime.getRuntime().maxMemory();
            //赋予缓存区最大缓存的四分之一进行缓存
            int cacheSize = maxMemory / 4;
            mCaches = new LruCache<String, Bitmap>(cacheSize) {
                @Override
                protected int sizeOf(String key, Bitmap value) {
                    //在每次存入缓存的时候调用
                    return value.getByteCount();
                }
            };
    <2
    Set<NewsAsyncTask> mTask
    定义一个Task任务集合,每个任务对应一个图片,当该图片被加载后要是否这个对应的task,对应代码如下:
     ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl);
                if (imageView!=null&&bitmap!=null){
                    imageView.setImageBitmap(bitmap);
                }
                mTask.remove(this);

    <3代码中根据 adapter 给每个图片设置 Tag 标识来获取图片,作用是: 避免 listview滚动时,由于convertView缓存造成图片错位显示, 对应代码如下: ----------> adapter代码后面给出

     private Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                if (mImageview.getTag().equals(mUrl))//--------------根据adapter设置的tag获取
                    mImageview.setImageBitmap((Bitmap) msg.obj);
            }
        };

    <4

    当listview一边滚动,一边加载图片会造成一个问题,可能会出现暂时的卡顿现象,尽管这个现象是偶尔发生,如果网络不好情况下,会加重这种情况,这是为什么呢?

    因为listview滚动时,对画面流畅度要求比较高
    虽然异步加载是在新线程中执行的,并未阻塞UI线程,当加载好图片后,去更新UI线程
    就会导致UI线程发生一次重绘,如果这次重绘正好发生在listview滚动的时候
    就会导致这个listview滚动过程中卡顿一下, 这样用户体验大大滴不好

    为解决该问题:

    我们可以在 listview滚动停止后 才去加载可见项, listview滚动过程中,取消加载项(滚动过程中不加载图片数据)
    就能解决这个问题, 因为我们在滚动过程中,其实我并不关心 滚动的内容,我只会关心 滚动停止后要显示的内容,所以这么做是 完全OK的.  对应代码如下:

     //滑动时加载图片 , 这里的 start 和end是 listview第一个和最后一个可见项
    // adapter代码中会有详述
    public void loadImages(int start, int end) { for (int i = start; i < end; i++) { String url = NewsAdapter.URLS[i]; //先从缓存中获取图片 Bitmap bitmap = getBitmapFromCache(url); if (bitmap == null) { NewsAsyncTask task = new NewsAsyncTask(url); task.execute(url); mTask.add(task); } else { ImageView imageView = (ImageView) mListView.findViewWithTag(url); imageView.setImageBitmap(bitmap); } } }

    以上就是图片工具类比较重点的部分 ,下面介绍adapter

    常常的分割线-------------------------------------------------------------------------------------------------------------

    public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener{
    
        private List<NewsBeans> mList;
        private LayoutInflater mInflater;
        private ImageLoader mImageLoader;
        private int mStart;
        private int mEnd;
        //创建静态数组保存图片的url地址
        public static String[] URLS;
        private boolean mFirstIn;
    
        public NewsAdapter(Context context, List<NewsBeans> data,ListView listView) {
            mList = data;
            mInflater = LayoutInflater.from(context);
            mImageLoader = new ImageLoader(listView);
            URLS = new String[data.size()];
            for(int i=0;i<data.size();i++){
                URLS[i] = data.get(i).iv_title;
            }
            listView.setOnScrollListener(this);
            mFirstIn = true;
        }
    
        @Override
        public int getCount() {
            return mList.size();
        }
    
        @Override
        public Object getItem(int position) {
            return mList.get(position);
        }
    
        @Override
        public long getItemId(int position) {
            return position;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder viewHolder;
            if (convertView == null) {
                viewHolder = new ViewHolder();
                convertView = mInflater.inflate(R.layout.list_item, null);
                viewHolder.iv_title = (ImageView) convertView.findViewById(R.id.iv_icon);
                viewHolder.tv_title = (TextView) convertView.findViewById(R.id.tv_title);
                viewHolder.tv_content = (TextView) convertView.findViewById(R.id.tv_content);
                convertView.setTag(viewHolder);
            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }
            //设置默认显示的图片
            viewHolder.iv_title.setImageResource(R.mipmap.ic_launcher);
            //避免缓存影响使同一位置图片加载多次混乱
            String url = mList.get(position).iv_title;
            viewHolder.iv_title.setTag(url);
         //   new ImageLoader().showImageByThread(viewHolder.iv_title, url);
            mImageLoader.showImageByAsyncTask(viewHolder.iv_title, url);
            viewHolder.tv_content.setText(mList.get(position).tv_content);
            viewHolder.tv_title.setText(mList.get(position).tv_title);
            return convertView;
        }
    
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if(scrollState==SCROLL_STATE_IDLE){
                //加载可见项
                mImageLoader.loadImages(mStart,mEnd);
            }else{
                //停止加载
                mImageLoader.cancelAllTasks();
            }
        }
    
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            mStart = firstVisibleItem;
            mEnd = firstVisibleItem + visibleItemCount;
            if (mFirstIn && visibleItemCount>0){
                mImageLoader.loadImages(mStart,mEnd);
            }
        }
    
        class ViewHolder {
            private ImageView iv_title;
            private TextView tv_title;
            private TextView tv_content;
        }
    }

    这里我们只需要注意3点

    1.设置图片唯一标识Tag,避免图片错位显示

    viewHolder.iv_title.setTag(url);

    2.滚动过程中不加载图片,只有滚动停止后加载,下面重点分析2个方法

    @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if(scrollState==SCROLL_STATE_IDLE){
                //加载可见项
                mImageLoader.loadImages(mStart,mEnd);
            }else{
                //停止加载
                mImageLoader.cancelAllTasks();
            }
        }
    
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
            mStart = firstVisibleItem;
            mEnd = firstVisibleItem + visibleItemCount;
            if (mFirstIn && visibleItemCount>0){
                mImageLoader.loadImages(mStart,mEnd);
            }
        }
    onScrollStateChanged()该方法,在listview第一次出现的时候,并不会执行,注意"并不会执行".


    oncroll()该方法在listview创建的时候就会执行,所以我们定义一个标志mFirstIn,在构造方法中初始为true,表示我们是第一次启动listview
    对 mFirstIn && visibleItemCount>0 判断的解释:
    "当前列表时第一次显示,并且listview的item已经展示出来",然后mFirstIn =false ,保证此段代码只有listview第一次显示的时候才会执行,之后滚动过程中不再执行

    这里为什么要判断visibleItemCount>0 呢?
    其实 oncroll会被多次回调的, 但是初次调用 
    visibleItemCount 是 等于0的,也就是说此时item还未被加载
    所以我们要判断 >0 跳过==0的情况,因为==0 item未被加载,当然 也就不会显示网络图片了
    分割线--------------------------------------------------------------------------------------------------------------------------------------

    最后是 MainActivity代码,比较简单不再分析
    public class MainActivity extends AppCompatActivity {
    
        private static String URL = "http://www.imooc.com/api/teacher?type=4&num=30";
        private ListView mListView;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            mListView = (ListView) findViewById(R.id.list_main);
            new LoadImageAsync().execute(URL);
        }
    
        //异步加载所有的网络数据
        class LoadImageAsync extends AsyncTask<String, Void, List<NewsBeans>> {
    
            @Override
            protected List<NewsBeans> doInBackground(String... params) {
                return getJsonData(params[0]);
            }
    
            @Override
            protected void onPostExecute(List<NewsBeans> newsBeans) {
                super.onPostExecute(newsBeans);
                NewsAdapter adapter = new NewsAdapter(MainActivity.this, newsBeans,mListView);
                mListView.setAdapter(adapter);
            }
        }
    
        //得到JSON数据
        private List<NewsBeans> getJsonData(String url) {
            List<NewsBeans> data = new ArrayList<>();
            try {
                //读取流得到json数据
                String jsonList = readStream(new URL(url).openStream());
                JSONObject jsonObject;
                NewsBeans newsBeans;
                try {
                    //解析JSON数据
                    jsonObject = new JSONObject(jsonList);
                    JSONArray jsonArray = jsonObject.getJSONArray("data");
                    for (int i = 0; i <= jsonArray.length(); i++) {
                        jsonObject = jsonArray.getJSONObject(i);
                        newsBeans = new NewsBeans();
                        newsBeans.iv_title = jsonObject.getString("picSmall");
                        newsBeans.tv_title = jsonObject.getString("name");
                        newsBeans.tv_content = jsonObject.getString("description");
                        data.add(newsBeans);
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return data;
        }
    
        //读取输入流
        private String readStream(InputStream is) {
            InputStreamReader isr;
            String result = "";
            try {
                String line;
                //读取输入流
                isr = new InputStreamReader(is, "utf-8");
                //输入流转换成字节流
                BufferedReader br = new BufferedReader(isr);
                //逐行读取
                while ((line = br.readLine()) != null) {
                    result += line;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            return result;
        }
    }

    以上代码只是做了内存缓存,如果想做二级缓存,可以用
    DiskLruCache 硬盘缓存.最后来张图吧

    
    


  • 相关阅读:
    从零开始——PowerShell应用入门(全例子入门讲解)
    详解C# Tuple VS ValueTuple(元组类 VS 值元组)
    How To Configure VMware fencing using fence_vmware_soap in RHEL High Availability Add On——RHEL Pacemaker中配置STONITH
    DB太大?一键帮你收缩所有DB文件大小(Shrink Files for All Databases in SQL Server)
    SQL Server on Red Hat Enterprise Linux——RHEL上的SQL Server(全截图)
    SQL Server on Ubuntu——Ubuntu上的SQL Server(全截图)
    微软SQL Server认证最新信息(17年5月22日更新),感兴趣的进来看看哟
    Configure Always On Availability Group for SQL Server on RHEL——Red Hat Enterprise Linux上配置SQL Server Always On Availability Group
    3分钟带你了解PowerShell发展历程——PowerShell各版本资料整理
    由Find All References引发的思考。,
  • 原文地址:https://www.cnblogs.com/android-zcq/p/5566126.html
Copyright © 2011-2022 走看看