1. 程式人生 > >XUtils改造以新增drawable支援

XUtils改造以新增drawable支援

=== XUtilsBitmapUtils 改造以新增drawable支援 ===

# XUtils 簡介

XUtils 是一套少有的早期國產安卓框架, 其源於AFinal, 目錄結構也與之相似, 但是程式碼卻進行了大量的重構, 是XUtils更加現代, 解決了AFinal 的OOM等問題.

目前 XUtils 已經支援 API 8(android 2.2) 至 API 21(android 5.0.x).

XUtils 主要內建了DbUtils 模組, ViewUtils 模組, HttpUtils 模組, BitmapUtils 模組.

對於新手來說, 這些功能著實使用而且強大, 為我們省下了不少的功夫 去處理業務.

同類的框架, 國外流行的的有androidannotations, roboguice, androidquery,droidparts等, 當然國內也有不少競爭者,

ThinkAndroid,UltimateAndroid, LoonAndroid, KJFrameForAndroid, SmartAndroid, 都是可以可以用來借鑑的. 本文暫時專注於XUtils的使用.

具體的細化模組可以參考 官方地址(wyouflf/xUtils):

https://github.com/wyouflf/xUtils

做過android的同學一定都知道安卓處理Bitmap可謂一絕, Bitmap絕對是吃記憶體的大戶, 而且Dalvik

虛擬機器(暫時不考慮ART技術)垃圾回收經常不及時, 所以圖片處理不當,經常會出現OOM(out of memory), 即記憶體溢位的情況. 在接觸XUtils等框架之前, 很多人都是自己通過BitmapFactory.Options 來解決燃眉之急, 網上也有很多對策, 但是這樣很不繫統, 而且有些方案, 例如使用軟引用或者弱引用, 已經在安卓4之後不再被推薦, 仍然可能會出現OOM. 所以一款流行的, 穩定的, 現代的程式碼框架是必不可少的. XUtils 恰恰滿足了這一點.

XUtils 的圖片處理存在快取, 主要是記憶體快取和外存快取. 但是這不是今天本文的重點, 但是以後會提及. 今天主要說說XUtils不太好的方面, 首先直接上修改過的官方程式碼:

// this 是一個 Context

BitmapUtilsbitmapUtils = new BitmapUtils(this);

// 載入網路圖片

bitmapUtils.display(testImageView,"http://www.52deng.com/logo.png");

bitmapUtils.display(testImageView,"ftp://www.52deng.com/logo.png");

// 載入本地圖片, 路徑以/開頭, 需要填寫絕對路徑

bitmapUtils.display(testImageView,"/sdcard/dengdeng/test.jpg");

// 載入assets中的圖片, 路徑以assets開頭

bitmapUtils.display(testImageView,"assets/dengdeng/wallpaper.jpg");

// 使用ListView等容器展示圖片時, 可通過PauseOnScrollListener在滑動和快速滑動過程中控制暫停載入圖片

listView.setOnScrollListener(newPauseOnScrollListener(bitmapUtils, false, true));

listView.setOnScrollListener(newPauseOnScrollListener(bitmapUtils, false, true, customListener));

註釋已經被我優化, 相信結合程式碼, 語義應該更加明朗了. (← 你夠了, 語文渣)

但是仔細觀察會發現, 其實UXtils還是有不完美的地方: 貌似並不支援從專案中的drawable獲取圖片進行展示, 這樣豈不是遇到大圖片又要回歸BitmapFactory.Options等基礎方案了嗎? 這裡給大家推薦一下另一個安卓專攻圖片處理的框架Android-Universal-Image-Loader, 官方地址如下:

https://github.com/nostra13/Android-Universal-Image-Loader,

看關注度就能看出來, 它在Github上處於壟斷地位, 當然還有其他的專攻網路和圖片非同步的框架(國外的有glide, ion, Picasso,volley等), 都是很厲害和出名的. 那麼我們來看看 他支援的圖片處理方案, 不改了, 直接引用官方的例子:

"http://site.com/image.png"// from Web

"file:///mnt/sdcard/image.png"// from SD card

"file:///mnt/sdcard/video.mp4"// from SD card (video thumbnail)

"content://media/external/images/media/13"// from content provider

"content://media/external/video/media/13"// from content provider (video thumbnail)

"assets://image.png"// from assets

"drawable://"+ R.drawable.img// fromdrawables (non-9patch images)

NOTE:Use drawable://only if you really need it! Always considerthe native way toload drawables -ImageView.setImageResource(...)instead of using of ImageLoader.

看, 他是 支援多種圖片協議或者儲存路徑的, 也包括drawable, 但是值得注意的是, 他其實並不推薦快取drawable, 根據我的理解, 畢竟有一些drawable很小, 直接使用 ImageView 等空間自帶的放置圖片的方法即可. 但是遇到OOM的話, 該出手時就出手. 由於時間緊迫, 暫時不細研究這款開源專案的設計, 直接扒程式碼. 檢出專案之後, Ctrl+H選擇專案, 全文搜尋 關鍵字”drawable://”. 結果出來一堆東西, 換個思路, 搜尋”assets://”, 居然找到的是例子, 再換思路, 搜尋”assets:”, ok, 僥倖找到了核心程式碼 (其實他是通過 scheme 匹配傳遞的url的協議的):

// com.nostra13.universalimageloader.core.download.BaseImageDownloader.Java

 

       @Override

       public InputStream getStream(StringimageUri,Objectextra) throws IOException{

              switch (Scheme.ofUri(imageUri)) {

                     caseHTTP:

                     caseHTTPS:

                            return getStreamFromNetwork(imageUri,extra);

                     caseFILE:

                            return getStreamFromFile(imageUri,extra);

                     caseCONTENT:

                            return getStreamFromContent(imageUri,extra);

                     caseASSETS:

                            return getStreamFromAssets(imageUri,extra);

                     caseDRAWABLE:

                            return getStreamFromDrawable(imageUri,extra);

                     caseUNKNOWN:

                     default:

                            return getStreamFromOtherSource(imageUri,extra);

              }

       }


好吧, 把DRAWABLE看看:

protected InputStream getStreamFromDrawable(StringimageUri,Objectextra) {

              String drawableIdString = Scheme.DRAWABLE.crop(imageUri);

              int drawableId =Integer.parseInt(drawableIdString);

              return context.getResources().openRawResource(drawableId);

       }


OK了, 那麼來匹配一下 Xutils, 開啟Ctrl+H全文搜尋開啟, 搜尋”assets”, 輕鬆找到了:

// com.lidroid.xutils.bitmap.download.DefaultDownloader.java

        if (uri.startsWith("/")) {

                FileInputStream fileInputStream =newFileInputStream(uri);

                fileLen = fileInputStream.available();

                bis = new BufferedInputStream(fileInputStream);

                result = system.currentTimeMillis() +this.getDefaultExpiry();

            } else if (uri.startsWith("assets/")) {

                InputStream inputStream = this.getContext().getAssets().open(uri.substring(7,uri.length()));

                fileLen = inputStream.available();

                bis = new BufferedInputStream(inputStream);

                result = Long.MAX_VALUE;

            } else {

                final URLurl =newURL(uri);

                urlConnection =url.openConnection();

                urlConnection.setConnectTimeout(this.getDefaultConnectTimeout());

                urlConnection.setReadTimeout(this.getDefaultReadTimeout());

                bis = new BufferedInputStream(urlConnection.getInputStream());

                result = urlConnection.getExpiration();

                result = result < System.currentTimeMillis() ?System.currentTimeMillis() +this.getDefaultExpiry() :result;

                fileLen = urlConnection.getContentLength();

            }

}


有了前幾步的經驗, 輕鬆改造, 加一個else if, 程式碼 參考之前的Android-Universal-Image-Loader的核心程式碼:

if (uri.startsWith("/")) {

                FileInputStream fileInputStream =newFileInputStream(uri);

                fileLen = fileInputStream.available();

                bis = new BufferedInputStream(fileInputStream);

                result = System.currentTimeMillis() +this.getDefaultExpiry();

            } else if (uri.startsWith("assets/")) {

                InputStream inputStream = this.getContext().getAssets().open(uri.substring(7,uri.length()));

                fileLen = inputStream.available();

                bis = new BufferedInputStream(inputStream);

                result = Long.MAX_VALUE;

            } else if (uri.startsWith("drawable://")) {//赤裸裸地抄襲,我也用這個協議

                String drawableIdString = uri.substring(11,uri.length());//注意別算錯了

                intdrawableId =Integer.parseInt(drawableIdString); //還原原始的 id

                InputStream inputStream = this.getContext().getResources().openRawResource(drawableId);

                fileLen = inputStream.available();//抄上面的

                bis = new BufferedInputStream(inputStream); //抄上面的

                result = Long.MAX_VALUE;//抄上面的,先這麼寫,以後討論

            } else {

                final URLurl =newURL(uri);

                urlConnection =url.openConnection();

                urlConnection.setConnectTimeout(this.getDefaultConnectTimeout());

                urlConnection.setReadTimeout(this.getDefaultReadTimeout());

                bis = new BufferedInputStream(urlConnection.getInputStream());

                result = urlConnection.getExpiration();

                result = result < System.currentTimeMillis() ?System.currentTimeMillis() +this.getDefaultExpiry() :result;

                fileLen = urlConnection.getContentLength();

            }

       }


輕鬆改造, TEST!!

String uri = "drawable://" +R.drawable.super_larger_logo;

BitmapHelp.getBitmapUtils(this).display(largePic_imgV, uri);

OK, 測試通過, 圖片出現, 未出現OOM. 至此, 成功地新增drawable支援. 同理, 我們也可以抄抄”content”等協議的程式碼, 本文就不贅述了, 注意原始碼的協議, 引用或者修改都要留出處啊.