加载网络图片

来源:互联网 发布:视频管理系统php 编辑:程序博客网 时间:2024/06/06 19:55

一.概述


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

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

图片加载原理   使用网络请求 得到一个 输入流 然后把输入流转化为Bimp 显示到控件上  或者储存到内存中  或数据库

二.代码

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;    }}
复制代码