Android非同步載入全解析之引入二級緩衝,android非同步

來源:互聯網
上載者:User

Android非同步載入全解析之引入二級緩衝,android非同步
Android非同步載入全解析之引入二級緩衝
為啥要二級緩衝前面我們有了一級緩衝,為啥還要二級緩衝呢?說白了,這就和電腦是一樣的,我們電腦有記憶體和硬碟,記憶體讀取速度快,所以CPU直接讀取記憶體中的資料,但是,記憶體資源有限,所以我們可以把資料儲存到硬碟上,這就是二級緩衝,硬碟雖然讀取速度慢,但是人家容量大。Android的緩衝技術也是使用了這樣一個特性,總的來說,使用二級緩衝的方案,就是先從一級緩衝——記憶體中拿,沒有的話,再去二級緩衝——手機中拿,如果還沒有,那就只能去下載了。有了DiskLruCache,我們就可以很方便的將一部分內容緩衝到手機儲存中,做暫時的持久化儲存,像我們經常用的一些新聞彙總類App、ZARKER等,基本都利用了DiskLruCache,瀏覽過的網頁,即使在沒有網路的情況下,也可以瀏覽。
DiskLruCache配置DiskLruCache,聽名字就知道是LruCache的兄弟,只不過這個應該是Google的私生子,還沒有像LruCache一樣添加到API中,所以我們只能去官網上下載DiskLruCache的代碼,其實也就一個類。:https://developer.android.com/samples/DisplayingBitmaps/src/com.example.android.displayingbitmaps/util/DiskLruCache.html#l22

在工程中使用DiskLruCache非常簡單,只需要在項目中建立一個libcore.io的包,並將DiskLruCache.java檔案copy過去即可。
初始化在使用DiskLruCache之前,我們需要對緩衝的目錄進行下配置,DiskLruCache並不需要限定緩衝儲存的位置,但一般情況下,我們的緩衝都儲存在緩衝目錄下: /sdcard/Android/data/package name/cache,當然,如果沒有sdcard,那麼我們就使用內建儲存的快取區域:/data/data/package name/cache。在設定好緩衝目錄後,我們就可以使用DiskLruCache.open方法來建立DiskLruCache:

File cacheDir = getFileCache(context, "disk_caches");if (!cacheDir.exists()) {    cacheDir.mkdirs();}try {    mDiskCaches = DiskLruCache.open(cacheDir, 1, 1, 10 * 1024 * 1024);} catch (IOException e) {    e.printStackTrace();}private File getFileCache(Context context, String cacheFileName) {    String cachePath;    if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())            || !Environment.isExternalStorageRemovable()) {        cachePath = context.getExternalCacheDir().getPath();    } else {        cachePath = context.getCacheDir().getPath();    }    return new File(cachePath + File.separator + cacheFileName);}
DiskLruCache.open方法有這樣幾個參數:緩衝目錄程式版本號碼:版本更新後,緩衝清0valueCount緩衝大小:隨意,但也不能太任性,按位元組算
應該不用解釋了,唯一值得說的是valueCount這個參數,它是說同一個key可以對應Value的個數,一般都是1,基本沒用。最後我們來看看最後返回的:
return new File(cachePath + File.separator + cacheFileName)

這裡通過cacheFileName在緩衝目錄下再建立一個目錄是幹嘛呢?這個目錄是用來對不同的緩衝對象進行區分的,例如images、text等等。我們可以通過size()方法來擷取所有快取資料的大小。也可以使用delete()方法來刪除所有緩衝。
寫入緩衝許可權:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

都說了寫緩衝,那讀寫權限肯定是不能少了。
DiskLruCache寫入緩衝與使用SharedPreferences方法類似,需要使用Editor對象:
DiskLruCache.Editor editor = mDiskCaches.edit(key);

傳入的key,就是我們需要下載的url地址,例片的地址,但是,url經常具有很多非法字元,這些會對我們的解析工作造成很多困難,而且,有時候我們的url地址也是需要保密的,所以我們經常通過MD5來進行url的加密,這樣不僅可以加密,而且可以讓所有的URL都變為規則的十六進位字串。下面我們展示一個經典的寫入緩衝模板代碼:
String key = toMD5String(url);/////////////////////////////////////////////////////////////////////////////////DiskLruCache.Editor editor = mDiskCaches.edit(key);if (editor != null) {    OutputStream outputStream = editor.newOutputStream(0);    if (getBitmapUrlToStream(url, outputStream)) {        editor.commit();    } else {        editor.abort();    }}mDiskCaches.flush();/////////////////////////////////////////////////////////////////////////////////public String toMD5String(String key) {    String cacheKey;    try {        final MessageDigest digest = MessageDigest.getInstance("MD5");        digest.update(key.getBytes());        cacheKey = bytesToHexString(digest.digest());    } catch (NoSuchAlgorithmException e) {        cacheKey = String.valueOf(key.hashCode());    }    return cacheKey;}private String bytesToHexString(byte[] bytes) {    StringBuilder sb = new StringBuilder();    for (int i = 0; i < bytes.length; i++) {        String hex = Integer.toHexString(0xFF & bytes[i]);        if (hex.length() == 1) {            sb.append('0');        }        sb.append(hex);    }    return sb.toString();}private static boolean getBitmapUrlToStream(String urlString, OutputStream outputStream) {    HttpURLConnection urlConnection = null;    BufferedOutputStream out = null;    BufferedInputStream in = null;    try {        final URL url = new URL(urlString);        urlConnection = (HttpURLConnection) url.openConnection();        in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);        out = new BufferedOutputStream(outputStream, 8 * 1024);        int b;        while ((b = in.read()) != -1) {            out.write(b);        }        return true;    } catch (final IOException e) {        e.printStackTrace();    } finally {        if (urlConnection != null) {            urlConnection.disconnect();        }        try {            if (out != null) {                out.close();            }            if (in != null) {                in.close();            }        } catch (final IOException e) {            e.printStackTrace();        }    }    return false;}

這裡唯一的需要注意的是,下載的方法與我們之前使用的方法有所不同,主要是為了通用性,DiskLruCache將對應URL的內容以流的形式進行儲存,檔案名稱就是MD5加密後的字串。
讀取緩衝讀取緩衝的方法大家應該也能想到了,自然是調用get方法:
DiskLruCache.Snapshot snapShot = mDiskCaches.get(key);

不過它返回的是DiskLruCache的Snapshot對象。當我們擷取到了Snapshot對象,就可以從它裡面擷取輸出資料流,從而取出緩衝的資料:
DiskLruCache.Snapshot snapShot = mDiskCaches.get(key);InputStream is = snapShot.getInputStream(0);  Bitmap bitmap = BitmapFactory.decodeStream(is);  mImageView.setImageBitmap(bitmap);

移除緩衝移除緩衝,我們可以猜到,我們需要使用remove方法來實現:
mDiskCache.remove(key);
當然,DiskLruCache並不希望我們手動去移除緩衝,因為人家用了Lru演算法,跟我們在記憶體中使用的演算法一樣,該死的時候,它自己會死。
與生命週期綁定DiskLruCache在使用時,經常與我們的Activity的生命週期進行綁定,例如在onPause()方法中調用flush()方法,將內容與journal記錄檔同步,在onDestroy()方法中去調用close()方法結束DiskLruCache的open。
日誌同步通過前面的方法,我們已經可以緩衝一個來自網路的圖片了。下面我們進入緩衝的檔案夾,並查看裡面的資料:

我們可以發現,這些檔案,就是以MD5命名的快取檔案,它的最後面,有一個journal檔案,我們通過cat命令開啟:


這裡我們選取一類記錄,這些記錄總是以dirty開頭,然後clean,最後read。這個是什麼意思呢?第一行dirty代表我們準備開始快取資料,clean代表我們緩衝到資料了,後面的30405代表緩衝的大小,最後的read代表進行了讀取操作。看到這裡,相信大家已經想起了我們非常熟悉的sqlite,它實際上也是利用檔案來進行儲存的。DiskLruCache實際上就是類比了一個簡化的sqlite,它的實現機制與sqlite基本類似。
引入二級緩衝ok,我們回到原來的項目,給工程增加二級緩衝,匯入DiskLruCache的源檔案,這裡就不講了。我們在前面一級緩衝的基礎上,修改下ImageLoaderWithCaches類,建立ImageLoaderWithDoubleCaches類,這裡面我們只需要在構造方法中增加對DiskLruCache的初始化,在AsyncTask中,我們來修改二級緩衝的邏輯。前面的步驟相同,在取映像的時候都從記憶體緩衝中取,如果取不到,那麼在AsyncTask在硬碟緩衝中取,如果還取不到,那就去下載,同時,將下載好的映像加入記憶體緩衝,如果硬碟緩衝中有,那麼就直接加入記憶體緩衝。看起來其實還是非常簡單的,只要修改下AsyncTask即可。
package com.imooc.listviewacyncloader;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.AsyncTask;import android.os.Environment;import android.util.LruCache;import android.widget.ImageView;import android.widget.ListView;import java.io.BufferedInputStream;import java.io.BufferedOutputStream;import java.io.File;import java.io.FileDescriptor;import java.io.FileInputStream;import java.io.IOException;import java.io.OutputStream;import java.net.HttpURLConnection;import java.net.URL;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.util.HashSet;import java.util.Set;import libcore.io.DiskLruCache;public class ImageLoaderWithDoubleCaches {    private Set<ASyncDownloadImage> mTasks;    private LruCache<String, Bitmap> mMemoryCaches;    private DiskLruCache mDiskCaches;    private ListView mListView;    public ImageLoaderWithDoubleCaches(Context context, ListView listview) {        this.mListView = listview;        mTasks = new HashSet<>();        int maxMemory = (int) Runtime.getRuntime().maxMemory();        int cacheSize = maxMemory / 10;        mMemoryCaches = new LruCache<String, Bitmap>(cacheSize) {            @Override            protected int sizeOf(String key, Bitmap value) {                return value.getByteCount();            }        };        File cacheDir = getFileCache(context, "disk_caches");        if (!cacheDir.exists()) {            cacheDir.mkdirs();        }        try {            mDiskCaches = DiskLruCache.open(cacheDir, 1, 1, 10 * 1024 * 1024);        } catch (IOException e) {            e.printStackTrace();        }    }    public void showImage(String url, ImageView imageView) {        Bitmap bitmap = getBitmapFromMemoryCaches(url);        if (bitmap == null) {            imageView.setImageResource(R.drawable.ic_launcher);        } else {            imageView.setImageBitmap(bitmap);        }    }    public Bitmap getBitmapFromMemoryCaches(String url) {        return mMemoryCaches.get(url);    }    public void addBitmapToMemoryCaches(String url, Bitmap bitmap) {        if (getBitmapFromMemoryCaches(url) == null) {            mMemoryCaches.put(url, bitmap);        }    }    public void loadImages(int start, int end) {        for (int i = start; i < end; i++) {            String url = Images.IMAGE_URLS[i];            Bitmap bitmap = getBitmapFromMemoryCaches(url);            if (bitmap == null) {                ASyncDownloadImage task = new ASyncDownloadImage(url);                mTasks.add(task);                task.execute(url);            } else {                ImageView imageView = (ImageView) mListView.findViewWithTag(url);                imageView.setImageBitmap(bitmap);            }        }    }    private File getFileCache(Context context, String cacheFileName) {        String cachePath;        if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())                || !Environment.isExternalStorageRemovable()) {            cachePath = context.getExternalCacheDir().getPath();        } else {            cachePath = context.getCacheDir().getPath();        }        return new File(cachePath + File.separator + cacheFileName);    }    private static boolean getBitmapUrlToStream(String urlString, OutputStream outputStream) {        HttpURLConnection urlConnection = null;        BufferedOutputStream out = null;        BufferedInputStream in = null;        try {            final URL url = new URL(urlString);            urlConnection = (HttpURLConnection) url.openConnection();            in = new BufferedInputStream(urlConnection.getInputStream(), 8 * 1024);            out = new BufferedOutputStream(outputStream, 8 * 1024);            int b;            while ((b = in.read()) != -1) {                out.write(b);            }            return true;        } catch (final IOException e) {            e.printStackTrace();        } finally {            if (urlConnection != null) {                urlConnection.disconnect();            }            try {                if (out != null) {                    out.close();                }                if (in != null) {                    in.close();                }            } catch (final IOException e) {                e.printStackTrace();            }        }        return false;    }    public void cancelAllTasks() {        if (mTasks != null) {            for (ASyncDownloadImage task : mTasks) {                task.cancel(false);            }        }    }    public String toMD5String(String key) {        String cacheKey;        try {            final MessageDigest digest = MessageDigest.getInstance("MD5");            digest.update(key.getBytes());            cacheKey = bytesToHexString(digest.digest());        } catch (NoSuchAlgorithmException e) {            cacheKey = String.valueOf(key.hashCode());        }        return cacheKey;    }    private String bytesToHexString(byte[] bytes) {        StringBuilder sb = new StringBuilder();        for (int i = 0; i < bytes.length; i++) {            String hex = Integer.toHexString(0xFF & bytes[i]);            if (hex.length() == 1) {                sb.append('0');            }            sb.append(hex);        }        return sb.toString();    }    public void flushCache() {        if (mDiskCaches != null) {            try {                mDiskCaches.flush();            } catch (IOException e) {                e.printStackTrace();            }        }    }    class ASyncDownloadImage extends AsyncTask<String, Void, Bitmap> {        private String url;        public ASyncDownloadImage(String url) {            this.url = url;        }        @Override        protected Bitmap doInBackground(String... params) {            url = params[0];            FileDescriptor fileDescriptor = null;            FileInputStream fileInputStream = null;            DiskLruCache.Snapshot snapShot = null;            String key = toMD5String(url);            try {                snapShot = mDiskCaches.get(key);                if (snapShot == null) {                    DiskLruCache.Editor editor = mDiskCaches.edit(key);                    if (editor != null) {                        OutputStream outputStream = editor.newOutputStream(0);                        if (getBitmapUrlToStream(url, outputStream)) {                            editor.commit();                        } else {                            editor.abort();                        }                    }                    snapShot = mDiskCaches.get(key);                }                if (snapShot != null) {                    fileInputStream = (FileInputStream) snapShot.getInputStream(0);                    fileDescriptor = fileInputStream.getFD();                }                Bitmap bitmap = null;                if (fileDescriptor != null) {                    bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);                }                if (bitmap != null) {                    addBitmapToMemoryCaches(params[0], bitmap);                }                return bitmap;            } catch (IOException e) {                e.printStackTrace();            } finally {                if (fileDescriptor == null && fileInputStream != null) {                    try {                        fileInputStream.close();                    } catch (IOException e) {                    }                }            }            return null;        }        @Override        protected void onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            ImageView imageView = (ImageView) mListView.findViewWithTag(url);            if (imageView != null && bitmap != null) {                imageView.setImageBitmap(bitmap);            }            mTasks.remove(this);        }    }}

整體代碼與之前使用一級緩衝的代碼基本相同,大家只要在AsyncTask修改一定邏輯就好了。
再次運行程式,與之前使用一級緩衝的圖相同,這裡就不貼了,只是這裡在斷網後,同樣可以載入緩衝中的圖片。
以上,未完待續,後面我們會進一步最佳化>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>



聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.