Android圖片處理神器BitmapFun源碼分析

來源:互聯網
上載者:User

標籤:

作為一名Android開發人員,相信大家對圖片OOM的問題已經耳熟能詳了,關於圖片緩衝和解決OOM的開源項目也是相當的多,被大家熟知的就是Universal_image_loader和Volley了,Volley在前面的文章中已經有介紹。Universal_image_loader在圖片緩衝功能方面應該算功能最強的,但是感覺很多功能用不上,所以在項目中我一般不太喜歡使用Universal_image_loader(因為本身自己的App源碼非常多,加入這些開源庫就就更大了,容易出現無法編譯的問題,因為Android貌似對一個應用中的方法個數好像有限制,貌似是655**個吧,具體多少我也記不清)。

關於處理圖片緩衝上,我接觸的兩個播放器項目中,使用的都是BitmapFun,BitmapFun 是Google為Android開發提供了一個培訓教程,既然是Google提供的,那麼我覺得作為一名合格的Android開發人員很有必要學習學習,而且BitmapFun非常簡單,基本可以滿足我們項目中對於圖片緩衝處理需求了。

對於開源項目的學習,我通常很少在應用程式層面來學習的,因為如何使用一個開源項目的相關部落格已經相當多了,而且寫得都非常詳細,對於大多數開源項目它都是內建sample的,所以如果想學習如何使用某個開源項目,好好研究sample就行了,但是我始終認為,熟悉經典開源項目源碼才是王道。好了廢話不多說,我們開始學習BitmapFun源碼吧。

1、BitmapFun結構
BitmapFun和其他開源庫的結構稍有不同,因為它僅僅是Google的培訓教程,所以BitmapFun和它的sample放在了一個工程裡面,結構圖如下:上面部分是BitmapFun的應用,下面部分是BitmapFun的源碼。

 

 

 

2、相關類介紹
在BitmapFun中最重要的一個類就是ImageFetcher,請求圖片主要就是調用loadImage方法,但是這個類是繼承ImageResizer,而ImageResizser是繼承ImageWorker,所以我們就從ImageWorker開始學習吧

 

ImageWorker.java/**這個類用來封裝一次圖片的載入過程,包括使用從緩衝中載入 */public abstract class ImageWorker {    private static final String TAG = ImageWorker;//這個變數用於動畫效果,沒有實際意義    private static final int FADE_IN_TIME = 200;//緩衝,包括磁碟緩衝和記憶體緩衝    private ImageCache mImageCache;//建立緩衝需要的參數    private ImageCache.ImageCacheParams mImageCacheParams;//載入過程中,ImageView顯示的圖片    private Bitmap mLoadingBitmap;//是否使用漸層效果    private boolean mFadeInBitmap = true;//是否提前退出任務,如果true,那麼圖片請求回來後是不會顯示出來的    private boolean mExitTasksEarly = false;//是否暫停任務    protected boolean mPauseWork = false;    private final Object mPauseWorkLock = new Object();    protected Resources mResources;    private static final int MESSAGE_CLEAR = 0;    private static final int MESSAGE_INIT_DISK_CACHE = 1;    private static final int MESSAGE_FLUSH = 2;    private static final int MESSAGE_CLOSE = 3;    protected ImageWorker(Context context) {        mResources = context.getResources();    }    /**     * 請求一張圖片的介面     * @param 圖片url     * @param 要顯示這種圖片的ImageView     */    public void loadImage(Object data, ImageView imageView) {        if (data == null) {            return;        }        BitmapDrawable value = null;//如果緩衝對象不為空白,那麼從記憶體緩衝中讀取對象        if (mImageCache != null) {            value = mImageCache.getBitmapFromMemCache(String.valueOf(data));        }        if (value != null) {            // 記憶體快取命中,那麼直接顯示            imageView.setImageDrawable(value);        } else if (cancelPotentialWork(data, imageView)) {//記憶體緩衝沒有命中,那麼建立一個圖片請求Task,將imageView作為參數            final BitmapWorkerTask task = new BitmapWorkerTask(imageView);//AsyncDrawable 是BitmapDrawable子類,主要用來存放當前任務的弱應用            final AsyncDrawable asyncDrawable =                    new AsyncDrawable(mResources, mLoadingBitmap, task);//將asyncDrawable設定到imageView中,這樣imageView和當前任務就一一對應了            imageView.setImageDrawable(asyncDrawable);            //調用AsyncTask的executeOnExecutor方法,這個AsyncTask和Android系統中的AsyncTask有些區別,但是使用上一樣的            task.executeOnExecutor(AsyncTask.DUAL_THREAD_EXECUTOR, data);        }    }    /**     * 設定載入過程中的預設圖片     *     * @param bitmap     */    public void setLoadingImage(Bitmap bitmap) {        mLoadingBitmap = bitmap;    }    /**     * 將本地圖片設定為預設圖片     *     * @param resId     */    public void setLoadingImage(int resId) {        mLoadingBitmap = BitmapFactory.decodeResource(mResources, resId);    }    /**     * 添加一個緩衝對象,建立磁碟緩衝時需要子線程中完成     * @param fragmentManager     * @param cacheParams The cache parameters to use for the image cache.     */    public void addImageCache(FragmentManager fragmentManager,            ImageCache.ImageCacheParams cacheParams) {        mImageCacheParams = cacheParams;        mImageCache = ImageCache.getInstance(fragmentManager, mImageCacheParams);//完成磁碟緩衝初始化        new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);    }    /**     * Adds an {@link ImageCache} to this {@link ImageWorker} to handle disk and memory bitmap     * caching.     * @param activity     * @param diskCacheDirectoryName See     * {@link ImageCache.ImageCacheParams#ImageCacheParams(Context, String)}.     */    public void addImageCache(FragmentActivity activity, String diskCacheDirectoryName) {        mImageCacheParams = new ImageCache.ImageCacheParams(activity, diskCacheDirectoryName);        mImageCache = ImageCache.getInstance(activity.getSupportFragmentManager(), mImageCacheParams);        new CacheAsyncTask().execute(MESSAGE_INIT_DISK_CACHE);    }    /**     * 設定是否使用漸層效果     */    public void setImageFadeIn(boolean fadeIn) {        mFadeInBitmap = fadeIn;    }//是否提前退出任務    public void setExitTasksEarly(boolean exitTasksEarly) {        mExitTasksEarly = exitTasksEarly;        setPauseWork(false);    }    /**     * Subclasses should override this to define any processing or work that must happen to produce     * the final bitmap. This will be executed in a background thread and be long running. For     * example, you could resize a large bitmap here, or pull down an image from the network.     *     * @param data The data to identify which image to process, as provided by     *            {@link ImageWorker#loadImage(Object, ImageView)}     * @return The processed bitmap     */    protected abstract Bitmap processBitmap(Object data);    /**     * @return The {@link ImageCache} object currently being used by this ImageWorker.     */    protected ImageCache getImageCache() {        return mImageCache;    }    /**     * Cancels any pending work attached to the provided ImageView.     * @param imageView     */    public static void cancelWork(ImageView imageView) {//通過ImageView找到task,為什麼可以找到?因為imageView和task一一對應        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);//如果task不為空白,那麼取消        if (bitmapWorkerTask != null) {            bitmapWorkerTask.cancel(true);            if (BuildConfig.DEBUG) {                final Object bitmapData = bitmapWorkerTask.data;                Log.d(TAG, cancelWork - cancelled work for  + bitmapData);            }        }    }    /**     * Returns true if the current work has been canceled or if there was no work in     * progress on this image view.     * Returns false if the work in progress deals with the same data. The work is not     * stopped in that case.     */    public static boolean cancelPotentialWork(Object data, ImageView imageView) {//通過imageView找到task        final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);        if (bitmapWorkerTask != null) {//如果找到的task不為null,並且task的url和給定的url相同,那麼取消任務            final Object bitmapData = bitmapWorkerTask.data;            if (bitmapData == null || !bitmapData.equals(data)) {                bitmapWorkerTask.cancel(true);                if (BuildConfig.DEBUG) {                    Log.d(TAG, cancelPotentialWork - cancelled work for  + data);                }            } else {                // The same work is already in progress.                return false;            }        }        return true;    }    /**     * 通過iamgeView找到對應的Task     */    private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {        if (imageView != null) {            final Drawable drawable = imageView.getDrawable();            if (drawable instanceof AsyncDrawable) {                final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;                return asyncDrawable.getBitmapWorkerTask();            }        }        return null;    }    /**     * 一個請求圖片的非同步任務,     */    private class BitmapWorkerTask extends AsyncTask<object, bitmapdrawable=""> {//請求圖片的url        private Object data;//持有ImageView的弱引用        private final WeakReference imageViewReference;        public BitmapWorkerTask(ImageView imageView) {            imageViewReference = new WeakReference(imageView);        }        /**         * Background processing.         */        @Override        protected BitmapDrawable doInBackground(Object... params) {            if (BuildConfig.DEBUG) {                Log.d(TAG, doInBackground - starting work);            }            data = params[0];            final String dataString = String.valueOf(data);            Bitmap bitmap = null;            BitmapDrawable drawable = null;            // 如果work已經暫停並且圖片請求沒有取消,那麼就等待            synchronized (mPauseWorkLock) {                while (mPauseWork && !isCancelled()) {                    try {                        mPauseWorkLock.wait();                    } catch (InterruptedException e) {}                }            }//如果有緩衝,並且沒有取消,當前弱引用中的imageView對應的task還是自己(task),那麼從磁碟緩衝中讀取//為什麼在這裡讀磁碟緩衝?因為磁碟緩衝只能在非同步線程讀取,doingbackground就是在非同步線程執行            if (mImageCache != null && !isCancelled() && getAttachedImageView() != null                    && !mExitTasksEarly) {                bitmap = mImageCache.getBitmapFromDiskCache(dataString);            }            //如果沒有命中,並且沒有取消,並且當前弱引用中的ImageView對應的task還是自己,那麼請求網狀圖片,//調用processBitmap方法,這個方法是個抽象的,在ImageFecter中實現            if (bitmap == null && !isCancelled() && getAttachedImageView() != null                    && !mExitTasksEarly) {                bitmap = processBitmap(params[0]);            }            // If the bitmap was processed and the image cache is available, then add the processed            // bitmap to the cache for future use. Note we don‘t check if the task was cancelled            // here, if it was, and the thread is still running, we may as well add the processed            // bitmap to our cache as it might be used again in the future            if (bitmap != null) {                if (Utils.hasHoneycomb()) {                    // Running on Honeycomb or newer, so wrap in a standard BitmapDrawable                    drawable = new BitmapDrawable(mResources, bitmap);                } else {                    // Running on Gingerbread or older, so wrap in a RecyclingBitmapDrawable                    // which will recycle automagically                    drawable = new RecyclingBitmapDrawable(mResources, bitmap);                }//將圖片加入緩衝                if (mImageCache != null) {                    mImageCache.addBitmapToCache(dataString, drawable);                }            }            if (BuildConfig.DEBUG) {                Log.d(TAG, doInBackground - finished work);            }            return drawable;        }        /**         * Once the image is processed, associates it to the imageView         */        @Override        protected void onPostExecute(BitmapDrawable value) {            // 如果取消了或者提前退出,那麼不顯示這個圖片,直接設定null            if (isCancelled() || mExitTasksEarly) {                value = null;            }            final ImageView imageView = getAttachedImageView();            if (value != null && imageView != null) {                if (BuildConfig.DEBUG) {                    Log.d(TAG, onPostExecute - setting bitmap);                }//將圖片顯示出來                setImageDrawable(imageView, value);            }        }        @Override        protected void onCancelled(BitmapDrawable value) {            super.onCancelled(value);//任務取消了,必須通知後台線程停止等待            synchronized (mPauseWorkLock) {                mPauseWorkLock.notifyAll();            }        }               private ImageView getAttachedImageView() {            final ImageView imageView = imageViewReference.get();            final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);            if (this == bitmapWorkerTask) {                return imageView;            }            return null;        }    }    /**     *用於實現imageView和task一一對應的類     */    private static class AsyncDrawable extends BitmapDrawable {        private final WeakReference bitmapWorkerTaskReference;        public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {            super(res, bitmap);            bitmapWorkerTaskReference =                new WeakReference(bitmapWorkerTask);        }        public BitmapWorkerTask getBitmapWorkerTask() {            return bitmapWorkerTaskReference.get();        }    }    /**     * 顯示圖片,漸層顯示或者普通顯示     *     * @param imageView     * @param drawable     */    private void setImageDrawable(ImageView imageView, Drawable drawable) {        if (mFadeInBitmap) {            // Transition drawable with a transparent drawable and the final drawable            final TransitionDrawable td =                    new TransitionDrawable(new Drawable[] {                            new ColorDrawable(android.R.color.transparent),                            drawable                    });            // Set background to loading bitmap            imageView.setBackgroundDrawable(                    new BitmapDrawable(mResources, mLoadingBitmap));            imageView.setImageDrawable(td);            td.startTransition(FADE_IN_TIME);        } else {            imageView.setImageDrawable(drawable);        }    }}


分析完ImageWorker之後,我們發現在ImageWorker中已經提供了擷取網狀圖片的方法loadImage,當我調用了此方法後,首先會試圖從記憶體緩衝擷取圖片,如果擷取成功,直接返回,如果沒有擷取成功,則啟動一個BitmapWorkerTask,使用非同步線程擷取圖片,在非同步線程中,首先到磁碟中擷取,如果磁碟沒有擷取,最後才從網路擷取,我們發現在BitmapWorkerTask中是通過調用processBitmap方法完成圖片擷取的,但是這個方法是一個抽象方法,需要子類去實現,那我們到它的子類ImageResizer中

 

 

 

@Override    protected Bitmap processBitmap(Object data) {        return processBitmap(Integer.parseInt(String.valueOf(data)));    }


它調用的是另外一個重載的processBitmap方法,我們看看另外一個方法吧

 

 

private Bitmap processBitmap(int resId) {        if (BuildConfig.DEBUG) {            Log.d(TAG, processBitmap -  + resId);        }        return decodeSampledBitmapFromResource(mResources, resId, mImageWidth,                mImageHeight, getImageCache());    }


我們發現這個方法僅僅是用來載入本地圖片的,那它是如何?網狀圖片的載入的呢,如果你把ImageResizer源碼通讀一邊,你會發現ImageResizer這個類的主要功能如下:
1、設定顯示圖片的sizse
2、從磁碟緩衝中載入圖片

所以從網路載入圖片根本不是這個類的功能,聰明的同學馬上就應該想到了ImageFetcher這個類,對!,我們就直接看看ImageFetcher這個類吧

 

 

private Bitmap processBitmap(String data) {        final String key = ImageCache.hashKeyForDisk(data);        FileDescriptor fileDescriptor = null;        FileInputStream fileInputStream = null;        DiskLruCache.Snapshot snapshot;//檢查mHttpDiskCache是否已經初始化,這裡一定要注意,mHttpDiskCache這個磁碟緩衝是在ImageFetcher調用addImageCache時初始化的,如果你沒有調用addImageCache//那麼這裡就會阻塞,從而無法擷取圖片,具體情況還請大家自己分析代碼吧        synchronized (mHttpDiskCacheLock) {            // Wait for disk cache to initialize            while (mHttpDiskCacheStarting) {                try {                    mHttpDiskCacheLock.wait();                } catch (InterruptedException e) {}            }//下面這段代碼就是從mHttpDiskCache裡面寫入圖片            if (mHttpDiskCache != null) {                try {                    snapshot = mHttpDiskCache.get(key);                    if (snapshot == null) {                        if (BuildConfig.DEBUG) {                            Log.d(TAG, processBitmap, not found in http cache, downloading...);                        }                        DiskLruCache.Editor editor = mHttpDiskCache.edit(key);                        if (editor != null) {//下載圖片邏輯在這裡                            if (downloadUrlToStream(data,                                    editor.newOutputStream(DISK_CACHE_INDEX))) {                                editor.commit();                            } else {                                editor.abort();                            }                        }                        snapshot = mHttpDiskCache.get(key);                    }                    if (snapshot != null) {                        fileInputStream =                                (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);                        fileDescriptor = fileInputStream.getFD();                    }                } catch (IOException e) {                    Log.e(TAG, processBitmap -  + e);                } catch (IllegalStateException e) {                    Log.e(TAG, processBitmap -  + e);                } finally {                    if (fileDescriptor == null && fileInputStream != null) {                        try {                            fileInputStream.close();                        } catch (IOException e) {}                    }                }            }        }        Bitmap bitmap = null;        if (fileDescriptor != null) {//調用ImageResizer中的方法來將mHttpDiskCache中的緩衝產生指定大小的圖片            bitmap = decodeSampledBitmapFromDescriptor(fileDescriptor, mImageWidth,                    mImageHeight, getImageCache());        }        if (fileInputStream != null) {            try {                fileInputStream.close();            } catch (IOException e) {}        }        return bitmap;    }    /**     * 從網路通過HttpURLConnection下載圖片,並寫入到磁碟緩衝     *     * @param urlString The URL to fetch     * @return true if successful, false otherwise     */    public boolean downloadUrlToStream(String urlString, OutputStream outputStream) {        disableConnectionReuseIfNecessary();        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(), IO_BUFFER_SIZE);            out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE);            int b;            while ((b = in.read()) != -1) {                out.write(b);            }            return true;        } catch (final IOException e) {            Log.e(TAG, Error in downloadBitmap -  + e);        } finally {            if (urlConnection != null) {                urlConnection.disconnect();            }            try {                if (out != null) {                    out.close();                }                if (in != null) {                    in.close();                }            } catch (final IOException e) {}        }        return false;    }


好了,對於Bitmapfun的整個代碼邏輯我就簡單的分析到這裡吧,其實瞭解了Bitmapfun的代碼邏輯後,我們完全可以對其進行最佳化,我在這裡僅僅提出一點可以最佳化的地方,最佳化的方法就交給大家完成吧

比如BitmapWorkerTask在擷取圖片的時候先是讀取磁碟緩衝,然後從網路擷取,也就是說如果讀取本地和讀取網狀圖片時在同一條線程中完成的,這個時候就有可能出現一個問題,本地圖片存在卻無法載入出來:例如:在網路條件不好的情況下,前面的五個圖片請求剛好用完了所有的線程,由於網路條件不好,一直沒有返回,而第六個圖片剛好有緩衝,那麼它是無法載入出來的,因為沒有線程了,所以解決方案就是學習Volley(我前面的文章對於Volley已經介紹了)中的解決方案,讓一條線程專門處理本地圖片,其他線程用於處理網狀圖片。

就寫到這裡吧,如果大家有什麼沒看明白或者我寫錯了的,歡迎留言.....

 

Android圖片處理神器BitmapFun源碼分析

聯繫我們

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