用RxJava實現一個ImageLoader

在看這篇文章已經假設你對RxJava有了一點點了解,如果你都不知道RxJava是啥,戳我,這篇文章是RxJava入門很好的資料。

RxJava的流式編程方式確實讓有些複雜的代碼簡化不少,提高了代碼的可閱讀性,後期維護起來自然也方便很多。一開始只是看RxJava的文檔確實很抽象,面對一大堆操作符,各種不知道什麼鬼東西的概念,看的雲裏霧裏的,內心一萬頭草泥馬奔過,摔!所以還是做一個Demo來學習是比較高效的學習方式。

在這個前提下,我就開始着手寫了一個ImageLoader。之所以是做ImageLoader,是因爲最近也正好在看主席的《Android開發藝術探索》,其中一章正好是實現了一個ImageLoader,書裏的實現方式是線程池和Handler,耗時的加載圖片任務在線程池中執行,執行完成後發送消息到Handler,給ImageView設置圖片在主線程中完成。基本設計思路和AsyncTask一致,對這塊不懂的可以去看下AsyncTask的源碼,在此就不展開分析了,善用搜索。

這種類似AsyncTask的實現看着代碼很繁瑣,用RxJava來代替Handler和線程池實現線程切換和異步加載便捷很多,代碼的邏輯看起來也舒服多了。

在貼上代碼之前還是先講下ImageLoader實現的大體思路吧,首先加載圖片需要內存緩存和磁盤緩存功能,分別使用了LruCache和DiskLruCache,並且需要圖片壓縮的功能,壓縮功能是BitmapFactory解析bitmap時通過更改Options中的inSampleSize(採樣率)實現的。加載圖片入口方法如下:

   public void bindImage(ImageView imageView, String url, int reqWidth, int reqHeight) {
        String key = hashKeyFormUrl(url);
        try {
            if (mLruCache.get(key) != null) {
                bindImageFromMemo(imageView, key); //從內存緩存中加載
            }
            if (mDiskLruCache.get(key) != null) {
                bindImageFromDisk(imageView, key, reqWidth, reqHeight); //從磁盤緩存中加載
            } else {
                downloadImageFromHttp(imageView, url, reqWidth, reqHeight);  //從網絡下載圖片
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

想必不用過多解釋這個加載流程了吧,依次從內存緩存,磁盤緩存,網絡加載圖片。這裏以從網絡加載圖片代碼邏輯爲例,對比使用RxJava和不使用的區別,大家自行感受區別。代碼具體邏輯是從網絡加載圖片,並將下載好的圖片存儲到磁盤緩存中,並且將壓縮過的bitmap設置給imageview。。首先演示使用線程池加Handler的實現方式:

    private Handler mHandler = new Handler(getMainLooper()){//主線程中的Handler,作用是切換線程,在這裏是將線程池中執行完畢返回的bitmap等封裝好的對象發送到主線程中處理
        @Override
        public void handleMessage(Message msg) {
            if(msg.what == MESSAGE_POST_RESULT){
                Result result = (Result)msg.obj;
                ImageView imageview = result.imageview;
                String url = result.url;
                Bitmap bitmap = result.bitmap;
                if(url.equals((String)imageview.getTag(R.id.imageloader_id))){//防止圖片錯位
                       imageview.setImageBitmap(bitmap);
                }
            }
        }
    };

private void downloadImageFromHttp(final ImageView imageView, final String url, final int reqWidth, final int reqHeight) {
        imageView.setTag(R.id.imageloader_id, url);
        THREAD_POOL_EXECUTOR.execute(//在線程池中執行下載任務,執行完成後發送消息
                new Runnable() {
                    @Override
                    public void run() {
                       InputStream inputStream = new URL(s).openStream();
                       saveBitmapToDisk(inputStream, hashKeyFormUrl(url));  
                       Bitmap bitmap = getBitmapFromDisk(hashKeyFormUrl(url),reqWidth,reqHeight);
                       mHandler.obtainMessage(MESSAGE_POST_RESULT,new Result(imageView,url,bitmap)).setToTarget();
                    }
                });
    }

以上省去了線程池配置代碼,代碼邏輯還是有點繞的,而且代碼量也不少,對於不是很熟悉Handler和線程池的盆友可能寫着寫着就繞暈了,所以這裏強烈推薦使用RxJava來實現以上功能,代碼如下:

 private void downloadImageFromHttp(final ImageView imageView, final String url, final int reqWidth, final int reqHeight) {
        imageView.setTag(R.id.imageloader_id, url);
        Observable.just(url)
                .flatMap(new Func1<String, Observable<Bitmap>>() {
                    @Override
                    public Observable<Bitmap> call(String s) {
                        InputStream inputStream = null;
                        try {
                            inputStream = new URL(s).openStream();
                            saveBitmapToDisk(inputStream, hashKeyFormUrl(url));
                        } catch (IOException e) {
                            e.printStackTrace();
                        } finally {
                            return Observable.just(getBitmapFromDisk(hashKeyFormUrl(url), reqWidth, reqHeight));
                        }
                    }
                })
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Action1<Bitmap>() {
                    @Override
                    public void call(Bitmap bitmap) {
                        if (imageView.getTag(R.id.imageloader_id) == url) {
                            imageView.setImageBitmap(bitmap);
                        }
                    }
                });
    }

直觀來看,代碼從上到下,一氣呵成,不用線程池繁瑣的配置,沒有handler線程切換等操作,像穿珠子一樣,邏輯從頭到尾都是一條線。

首先just操作符接收了傳入的url,接下來flatmap進行數據的變換操作,通過url獲取輸入流,並且將其解析成bitmap存儲到磁盤緩存中,然後再從磁盤緩存中讀取剛寫入的bitmap,並且最終將讀取的bitmap傳入subscriber的回調方法中設置bitmap。看到這裏,你可能會疑問了,這麼一串操作都是在主線程中完成的嗎?這還能不ANR???其實並不是··關鍵在於以下兩行代碼:

subscribeOn(Schedulers.io())
observeOn(AndroidSchedulers.mainThread())

這兩行代碼的作用是切換線程,前者讓subscribe過程中發生在子線程中,這裏的Schedulers.io()表示的進行io操作的線程調度器,內部實質是線程池。後者是讓subscriber的回調過程發生在UI線程中。輕鬆兩行代碼就實現了線程間的切換,比起handler來切換線程這樣簡直不能太方便。

其他使用到RxJava的地方和以上大同小異,無非是從磁盤緩存加載和從內存緩存加載過程的不同,具體的代碼邏輯可以參照源碼,使用到比較頻繁的操作符有:just,from,map,flatmap等,大家可以着重去了解這幾個操作符的使用。個人覺得使用RxJava主要還是一個編程思維的轉變,概括來說就是“流”,忘記回調,忘記線程,忘記。。。

寫完了,請大家批評指正。。

最後貼上源碼地址:

源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章