Android批量圖片載入經典系列——採用二級緩衝、非同步載入網狀圖片

來源:互聯網
上載者:User

標籤:

一、問題描述

  Android應用中經常涉及從網路中載入大量圖片,為提升載入速度和效率,減少網路流量都會採用二級緩衝和非同步載入機制,所謂二級緩衝就是通過先從記憶體中擷取、再從檔案中擷取,最後才會訪問網路。記憶體緩衝(一級)本質上是Map集合以key-value對的方式儲存圖片的url和Bitmap資訊,由於記憶體緩衝會造成堆記憶體泄露, 管理相對複雜一些,可採用第三方組件,對於有經驗的可自己編寫組件,而檔案快取比較簡單通常自己封裝一下即可。下面就通過案例看如何?網狀圖片載入的最佳化。

二、案例介紹

  案例新聞的列表圖片

 

三、主要核心組件

  下面先看看實現一級緩衝(記憶體)、二級緩衝(磁碟檔案)所編寫的組件

1、MemoryCache

  在記憶體中儲存圖片(一級緩衝), 採用了1個map來緩衝圖片代碼如下:

public class MemoryCache {    // 最大的緩衝數     private static final int MAX_CACHE_CAPACITY = 30;    //用Map軟引用的Bitmap對象, 保證記憶體空間足夠情況下不會被記憶體回收        private HashMap<String, SoftReference<Bitmap>> mCacheMap =             new LinkedHashMap<String, SoftReference<Bitmap>>() {            private static final long serialVersionUID = 1L;//當緩衝數量超過規定大小(返回true)會清除最早放入緩衝的                protected boolean removeEldestEntry(Map.Entry<String,SoftReference<Bitmap>> eldest){                return size() > MAX_CACHE_CAPACITY;};    };        /**     * 從緩衝裡取出圖片     * @param id     * @return 如果緩衝有,並且該圖片沒被釋放,則返回該圖片,否則返回null     */    public Bitmap get(String id){        if(!mCacheMap.containsKey(id)) return null;        SoftReference<Bitmap> ref = mCacheMap.get(id);        return ref.get();    }        /**     * 將圖片加入緩衝     * @param id     * @param bitmap     */    public void put(String id, Bitmap bitmap){        mCacheMap.put(id, new SoftReference<Bitmap>(bitmap));    }    /**     * 清除所有緩衝     */   public void clear() {    try {            for(Map.Entry<String,SoftReference<Bitmap>>entry :mCacheMap.entrySet()) {    SoftReference<Bitmap> sr = entry.getValue();            if(null != sr) {                Bitmap bmp = sr.get();                if(null != bmp) bmp.recycle();            }        }        mCacheMap.clear();    } catch (Exception e) {            e.printStackTrace();}    }}
2、FileCache

  在磁碟中緩衝圖片(二級緩衝),代碼如下

    public class FileCache {     //快取檔案目錄     private File mCacheDir;    /**     * 建立快取檔案目錄,如果有SD卡,則使用SD,如果沒有則使用系統內建緩衝目錄     * @param context     * @param cacheDir 圖片緩衝的一級目錄     */public FileCache(Context context, File cacheDir, String dir){if(android.os.Environment.getExternalStorageState().equals、(android.os.Environment.MEDIA_MOUNTED))            mCacheDir = new File(cacheDir, dir);      else        mCacheDir = context.getCacheDir();// 如何擷取系統內建的緩衝儲存路徑      if(!mCacheDir.exists())  mCacheDir.mkdirs();    }    public File getFile(String url){        File f=null;        try {//對url進行編輯,解決中文路徑問題            String filename = URLEncoder.encode(url,"utf-8");            f = new File(mCacheDir, filename);        } catch (UnsupportedEncodingException e) {            e.printStackTrace();        }        return f;    }    public void clear(){//清除快取檔案        File[] files = mCacheDir.listFiles();        for(File f:files)f.delete();}}
3、編寫非同步載入組件AsyncImageLoader

  android中採用單執行緒模式即應用運行在UI主線程中,且Android又是即時作業系統要求及時響應否則出現ANR錯誤,因此對於耗時操作要求不能阻塞UI主線程,需要開啟一個線程處理(如本應用中的圖片載入)並將線程放入隊列中,當運行完成後再通知UI主線程變更,同時移除任務——這就是非同步任務,在android中實現非同步可通過本系列一中所用到的AsyncTask或者使用thread+handler機制,在這裡是完全是通過代碼編寫實現的,這樣我們可以更清晰的看到非同步通訊的實現的本質,代碼如下

public class AsyncImageLoader{    private MemoryCache mMemoryCache;//記憶體緩衝    private FileCache mFileCache;//檔案快取    private ExecutorService mExecutorService;//線程池//記錄已經載入圖片的ImageView    private Map<ImageView, String> mImageViews = Collections            .synchronizedMap(new WeakHashMap<ImageView, String>());//儲存正在載入圖片的url     private List<LoadPhotoTask> mTaskQueue = new ArrayList<LoadPhotoTask>();    /**     * 預設採用一個大小為5的線程池     * @param context     * @param memoryCache 所採用的快取     * @param fileCache 所採用的檔案快取     */    public AsyncImageLoader(Context context, MemoryCache memoryCache, FileCache fileCache) {        mMemoryCache = memoryCache;        mFileCache = fileCache;        mExecutorService = Executors.newFixedThreadPool(5);//建立一個容量為5的固定尺寸的線程池(最大正在啟動並執行線程數量)    }    /**     * 根據url載入相應的圖片     * @param url     * @return 先從一級緩衝中取圖片有則直接返回,如果沒有則非同步從檔案(二級緩衝)中取,如果沒有再從網路端擷取     */    public Bitmap loadBitmap(ImageView imageView, String url) {        //先將ImageView記錄到Map中,表示該ui已經執行過圖片載入了        mImageViews.put(imageView, url);        Bitmap bitmap = mMemoryCache.get(url);//先從一級緩衝中擷取圖片        if(bitmap == null) {            enquequeLoadPhoto(url, imageView);//再從二級緩衝和網路中擷取        }        return bitmap;    }        /**     * 加入圖片下載隊列     * @param url     */    private void enquequeLoadPhoto(String url, ImageView imageView) {        //如果任務已經存在,則不重新添加        if(isTaskExisted(url))            return;        LoadPhotoTask task = new LoadPhotoTask(url, imageView);        synchronized (mTaskQueue) {            mTaskQueue.add(task);//將任務添加到隊列中                    }        mExecutorService.execute(task);//向線程池中提交任務,如果沒有達到上限(5),則運行否則被阻塞    }        /**     * 判斷下載隊列中是否已經存在該任務     * @param url     * @return     */    private boolean isTaskExisted(String url) {        if(url == null)            return false;        synchronized (mTaskQueue) {            int size = mTaskQueue.size();            for(int i=0; i<size; i++) {                LoadPhotoTask task = mTaskQueue.get(i);                if(task != null && task.getUrl().equals(url))                    return true;            }        }        return false;    }        /**     * 從快取檔案或者網路端擷取圖片     * @param url     */    private Bitmap getBitmapByUrl(String url) {        File f = mFileCache.getFile(url);//獲得緩衝圖片路徑        Bitmap b = ImageUtil.decodeFile(f);//獲得檔案的Bitmap資訊        if (b != null)//不為空白表示獲得了緩衝的檔案            return b;        return ImageUtil.loadBitmapFromWeb(url, f);//同網路獲得圖片    }        /**     * 判斷該ImageView是否已經載入過圖片了(可用於判斷是否需要進行載入圖片)     * @param imageView     * @param url     * @return     */    private boolean imageViewReused(ImageView imageView, String url) {        String tag = mImageViews.get(imageView);        if (tag == null || !tag.equals(url))            return true;        return false;    }        private void removeTask(LoadPhotoTask task) {        synchronized (mTaskQueue) {            mTaskQueue.remove(task);        }    }        class LoadPhotoTask implements Runnable {        private String url;        private ImageView imageView;            LoadPhotoTask(String url, ImageView imageView) {            this.url = url;            this.imageView = imageView;        }                @Override        public void run() {            if (imageViewReused(imageView, url)) {//判斷ImageView是否已經被複用                removeTask(this);//如果已經被複用則刪除任務                return;            }            Bitmap bmp = getBitmapByUrl(url);//從快取檔案或者網路端擷取圖片            mMemoryCache.put(url, bmp);// 將圖片放入到一級緩衝中            if (!imageViewReused(imageView, url)) {//若ImageView未加圖片則在ui線程中顯示圖片            BitmapDisplayer bd = new BitmapDisplayer(bmp, imageView, url);                        Activity a = (Activity) imageView.getContext();            a.runOnUiThread(bd);//在UI線程調用bd組件的run方法,實現為ImageView控制項載入圖片            }            removeTask(this);//從隊列中移除任務        }        public String  getUrl() {            return url;        }    }        /**     *      *由UI線程中執行該組件的run方法     */    class BitmapDisplayer implements Runnable {        private Bitmap bitmap;        private ImageView imageView;        private String url;        public BitmapDisplayer(Bitmap b, ImageView imageView, String url) {            bitmap = b;            this.imageView = imageView;            this.url = url;        }        public void run() {            if (imageViewReused(imageView, url))                return;            if (bitmap != null)                imageView.setImageBitmap(bitmap);        }    }        /**     * 釋放資源     */    public void destroy() {        mMemoryCache.clear();        mMemoryCache = null;        mImageViews.clear();        mImageViews = null;        mTaskQueue.clear();        mTaskQueue = null;        mExecutorService.shutdown();        mExecutorService = null;    }}

  編寫完成之後,對於非同步任務的執行只需調用AsyncImageLoader中的loadBitmap()方法即可非常方便,對於AsyncImageLoader組件的代碼最好結合注釋好好理解一下,這樣對於Android中線程之間的非同步通訊就會有深刻的認識。

4、工具類ImageUtil
public class ImageUtil {    /**     * 從網路擷取圖片,並緩衝在指定的檔案中     * @param url 圖片url     * @param file 快取檔案     * @return     */    public static Bitmap loadBitmapFromWeb(String url, File file) {        HttpURLConnection conn = null;        InputStream is = null;        OutputStream os = null;        try {            Bitmap bitmap = null;            URL imageUrl = new URL(url);            conn = (HttpURLConnection) imageUrl.openConnection();            conn.setConnectTimeout(30000);            conn.setReadTimeout(30000);            conn.setInstanceFollowRedirects(true);            is = conn.getInputStream();            os = new FileOutputStream(file);            copyStream(is, os);//將圖片緩衝到磁碟中            bitmap = decodeFile(file);            return bitmap;        } catch (Exception ex) {            ex.printStackTrace();            return null;        } finally {            try {                if(os != null) os.close();                if(is != null) is.close();                if(conn != null) conn.disconnect();            } catch (IOException e) {    }        }    }        public static Bitmap decodeFile(File f) {        try {            return BitmapFactory.decodeStream(new FileInputStream(f), null, null);        } catch (Exception e) { }         return null;    }    private  static void copyStream(InputStream is, OutputStream os) {        final int buffer_size = 1024;        try {            byte[] bytes = new byte[buffer_size];            for (;;) {                int count = is.read(bytes, 0, buffer_size);                if (count == -1)                    break;                os.write(bytes, 0, count);            }        } catch (Exception ex) {            ex.printStackTrace();        }    }}
四、測試應用

 組件之間的時序圖:

 

1、編寫MainActivity
public class MainActivity extends Activity {     ListView list;    ListViewAdapter adapter;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        list=(ListView)findViewById(R.id.list);        adapter=new ListViewAdapter(this, mStrings);        list.setAdapter(adapter);    }    public void onDestroy(){        list.setAdapter(null);        super.onDestroy();        adapter.destroy();    }  private String[] mStrings={ "http://news.21-sun.com/UserFiles/x_Image/x_20150606083511_0.jpg","http://news.21-sun.com/UserFiles/x_Image/x_20150606082847_0.jpg",…..};}
2、編寫適配器
public class ListViewAdapter extends BaseAdapter {    private Activity mActivity;    private String[] data;    private static LayoutInflater inflater=null;    private AsyncImageLoader imageLoader;//非同步裝置    public ListViewAdapter(Activity mActivity, String[] d) {        this.mActivity=mActivity;        data=d;        inflater = (LayoutInflater)mActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);        MemoryCache mcache=new MemoryCache();//記憶體緩衝 File sdCard = android.os.Environment.getExternalStorageDirectory();//獲得SD卡        File cacheDir = new File(sdCard, "jereh_cache" );//緩衝根目錄    FileCache fcache=new FileCache(mActivity, cacheDir, "news_img");//檔案快取       imageLoader = new AsyncImageLoader(mActivity, mcache,fcache);    }    public int getCount() {        return data.length;    }    public Object getItem(int position) {        return position;    }    public long getItemId(int position) {        return position;    }      public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder vh=null;        if(convertView==null){            convertView = inflater.inflate(R.layout.item, null);            vh=new ViewHolder();            vh.tvTitle=(TextView)convertView.findViewById(R.id.text);            vh.ivImg=(ImageView)convertView.findViewById(R.id.image);            convertView.setTag(vh);                }else{            vh=(ViewHolder)convertView.getTag();        }        vh.tvTitle.setText("標題資訊測試———— "+position);        vh.ivImg.setTag(data[position]);        //非同步載入圖片,先從一級緩衝、再二級緩衝、最後網路擷取圖片        Bitmap bmp = imageLoader.loadBitmap(vh.ivImg, data[position]);        if(bmp == null) {            vh.ivImg.setImageResource(R.drawable.default_big);        } else {            vh.ivImg.setImageBitmap(bmp);        }        return convertView;    }    private class ViewHolder{        TextView tvTitle;        ImageView ivImg;    }    public void destroy() {        imageLoader.destroy();    }}

Android批量圖片載入經典系列——採用二級緩衝、非同步載入網狀圖片

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.