原文連結:http://www.fresco-cn.org/
關于 Fresco
Fresco 是一個強大的圖檔加載元件。
Fresco 中設計有一個叫做 image pipeline 的子產品。它負責從網絡,從本地檔案系統,本地資源加載圖檔。為了最大限度節省空間和CPU時間,它含有3級緩存設計(2級記憶體,1級檔案)。
Fresco 中設計有一個叫做 Drawees 子產品,友善地顯示loading圖,當圖檔不再顯示在螢幕上時,及時地釋放記憶體和空間占用。
Fresco 支援 Android2.3(API level 9) 及其以上系統。
特性
記憶體管理
解壓後的圖檔,即Android中的
Bitmap
,占用大量的記憶體。大的記憶體占用勢必引發更加頻繁的GC。在5.0以下,GC将會顯著地引發界面卡頓。
在5.0以下系統,Fresco将圖檔放到一個特别的記憶體區域。當然,在圖檔不顯示的時候,占用的記憶體會自動被釋放。這會使得APP更加流暢,減少因圖檔記憶體占用而引發的OOM。
Fresco 在低端機器上表現一樣出色,你再也不用因圖檔記憶體占用而思前想後。
圖檔的漸進式呈現
漸進式的JPEG圖檔格式已經流行數年了,漸進式圖檔格式先呈現大緻的圖檔輪廓,然後随着圖檔下載下傳的繼續,呈現逐漸清晰的圖檔,這對于移動裝置,尤其是慢網絡有極大的利好,可帶來更好的使用者體驗。
Android 本身的圖檔庫不支援此格式,但是Fresco支援。使用時,和往常一樣,僅僅需要提供一個圖檔的URI即可,剩下的事情,Fresco會處理。
Gif圖和WebP格式
是的,支援加載Gif圖,支援WebP格式。
圖像的呈現
Fresco 的 Drawees 設計,帶來一些有用的特性:
- 自定義居中焦點(對人臉等圖檔顯示非常有幫助)
- 圓角圖,當然圓圈也行。
- 下載下傳失敗之後,點選重制下載下傳
- 自定義占位圖,自定義overlay, 或者進度條
- 指定使用者按壓時的overlay
圖像的加載
Fresco 的 image pipeline 設計,允許使用者在多方面控制圖檔的加載:
- 為同一個圖檔指定不同的遠端路徑,或者使用已經存在本地緩存中的圖檔
- 先顯示一個低解析度的圖檔,等高清圖下載下傳完之後再顯示高清圖
- 加載完成回調通知
- 對于本地圖,如有EXIF縮略圖,在大圖加載完成之前,可先顯示縮略圖
- 縮放或者旋轉圖檔
- 處理已下載下傳的圖檔
- WebP 支援
開始使用 Fresco編輯和糾錯
如果你僅僅是想簡單下載下傳一張網絡圖檔,在下載下傳完成之前,顯示一張占位圖,那麼簡單使用SimpleDraweeView 即可。
為了下載下傳網絡圖檔,請確定在
AndroidManifest.xml
中有以下權限:
<uses-permission android:name="android.permission.INTERNET"/>
在 Application 初始化時,在應用調用
setContentView()
之前,進行初始化:
在xml布局檔案中, 加入命名空間:
<!-- 其他元素 -->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:fresco="http://schemas.android.com/apk/res-auto">
加入
SimpleDraweeView
:
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/my_image_view"
android:layout_width="20dp"
android:layout_height="20dp"
fresco:placeholderImage="@drawable/my_drawable"
/>
開始加載圖檔
Uri uri = Uri.parse("https://raw.githubusercontent.com/facebook/fresco/gh-pages/static/fresco-logo.png");
SimpleDraweeView draweeView = (SimpleDraweeView) findViewById(R.id.my_image_view);
draweeView.setImageURI(uri);
剩下的,Fresco會替你完成:
- 顯示占位圖直到加載完成;
- 下載下傳圖檔;
- 緩存圖檔;
- 圖檔不再顯示時,從記憶體中移除;
支援的URIs編輯和糾錯
Fresco 支援許多URI格式。
特别注意:Fresco 不支援 相對路徑的URI. 所有的URI都必須是絕對路徑,并且帶上該URI的scheme。
如下:
類型 | Scheme | 示例 |
---|---|---|
遠端圖檔 | | 或者參考 使用其他網絡加載方案 |
本地檔案 | | |
Content provider | | |
asset目錄下的資源 | | |
res目錄下的資源 | | |
res 示例:
Uri uri = Uri.parse("res://包名(實際可以是任何字元串甚至留白)/" + R.drawable.ic_launcher);
注意,隻有圖檔資源才能使用在Image pipeline中,比如(PNG)。其他資源類型,比如字元串,或者XML Drawable在Image pipeline中沒有意義。是以加載的資源不支援這些類型。
像ShapeDrawable這樣聲明在XML中的drawable可能引起困惑。注意到這畢竟不是圖檔,如果想把這樣的drawable作為圖像顯示。
那麼把這個drawable設定為占位圖,然後把URI設定為null。
圓角和圓圈編輯和糾錯
Drawee 輕松支援圓角顯示,并且顯示圓角時,并不複制和修改Bitmap對象,那樣太耗費記憶體。
圓角
圓角實際有2種呈現方式:
- 圓圈 - 設定
為trueroundAsCircle
- 圓角 - 設定
roundedCornerRadius
設定圓角時,支援4個角不同的半徑。XML中無法配置,但可在Java代碼中配置。
設定圓角
可使用以下兩種方式:
- 預設使用一個 shader 繪制圓角,但是僅僅占位圖和所要顯示的圖有圓角效果。失敗示意圖和重下載下傳示意圖無圓角效果,且這種圓角方式不支援動畫。
- 疊加一個
來繪制圓角。但是背景需要固定成指定的顔色。 在XML中指定solid color
, 或者通過調用roundWithOverlayColor
來完成此設定。setOverlayColor
XML中配置
SimpleDraweeView
支援如下幾種圓角配置:
<com.facebook.drawee.view.SimpleDraweeView
...
fresco:roundedCornerRadius="5dp"
fresco:roundBottomLeft="false"
fresco:roundBottomRight="false"
fresco:roundWithOverlayColor="@color/blue"
fresco:roundingBorderWidth="1dp"
fresco:roundingBorderColor="@color/red"
代碼中配置
在建立 DraweeHierarchy 時,可以給
GenericDraweeHierarchyBuilder
指定一個RoundingParams 用來繪制圓角效果。
RoundingParams roundingParams = RoundingParams.fromCornersRadius(7f);
roundingParams.setOverlayColor(R.color.green);
// 或用 fromCornersRadii 以及 asCircle 方法
genericDraweeHierarchyBuilder
.setRoundingParams(roundingParams);
你也可以在運作時,改變圓角效果
RoundingParams roundingParams =
mSimpleDraweeView.getHierarchy().getRoundingParams();
roundingParams.setBorder(R.color.red, 1.0);
roundingParams.setRoundAsCircle(true);
mSimpleDraweeView.getHierarchy().setRoundingParams(roundingParams);
在運作時,不能改變呈現方式: 原本是圓角,不能改為圓圈。
說明
當使用
BITMAP_ONLY
(預設)模式時的限制:
- 并非所有的圖檔分支部分都可以實作圓角,目前隻有占位圖檔和實際圖檔可以實作圓角,我們正在努力為背景圖檔實作圓角功能。
- 隻有
和BitmapDrawable
類的圖檔可以實作圓角。我們目前不支援包括ColorDrawable
和NinePatchDrawable
在内的其他類型圖檔。(無論他們是在XML或是程式中聲明的)ShapeDrawable
- 動畫不能被圓角。
- 由于Android的
的限制,當一個圖檔不能覆寫全部的View的時候,邊緣部分會被重複顯示,而非留白。對這種情況可以使用不同的縮放類型(比如centerCrop)來保證圖檔覆寫了全部的View。BitmapShader
OVERLAY_COLOR
模式沒有上述限制,但由于這個模式使用在圖檔上覆寫一個純色圖層的方式來模拟圓角效果,是以隻有在圖示背景是靜止的并且與圖層同色的情況下才能獲得較好的效果。
Drawee 内部實作了一個
CLIPPING
模式。但由于有些
Canvas
的實作并不支援路徑剪裁(Path Clipping),這個模式被禁用了且不對外開放。并且由于路徑剪裁不支援反鋸齒,會導緻圓角的邊緣呈現像素化的效果。
總之,如果生成臨時bitmap的方法,所有的上述問題都可以避免。但是這個方法并不被支援因為這會導緻很嚴重的記憶體問題。
綜上所述,在 Android 中實作圓角效果,沒有一個絕對好的方案,你必須在上述的方案中進行選擇。
漸進式JPEG圖編輯和糾錯
注意: 本頁提及的API僅是初步設計,後續可能變動
Fresco 支援漸進式的網絡JPEG圖。在開始加載之後,圖會從模糊到清晰漸漸呈現。
你可以設定一個清晰度标準,在未達到這個清晰度之前,會一直顯示占位圖。
漸進式JPEG圖僅僅支援網絡圖。
初始化
配置Image pipeline時 需要傳遞一個 ProgressiveJpegConfig. 的執行個體。
這個執行個體需要完成兩個事情: 1. 傳回下一個需要解碼的掃描次數 2. 确定多少個掃描次數之後的圖檔才能開始顯示。
下面的執行個體中,為了實作節省CPU,并不是每個掃描都進行解碼。
注意:
- 每次解碼完之後,調用
, 等待掃描值大于傳回值,才有可能進行解碼。getNextScanNumberToDecode
假設,随着下載下傳的進行,下載下傳完的掃描序列如下:
1, 4, 5, 10
。那麼:
- 首次調用
傳回為2, 因為初始時,解碼的掃描數為0。getNextScanNumberToDecode
- 那麼1将不會解碼,下載下傳完成4個掃描時,解碼一次。下個解碼為掃描數為6
- 5不會解碼,10才會解碼
ProgressiveJpegConfig pjpegConfig = new ProgressiveJpegConfig() {
@Override
public int getNextScanNumberToDecode(int scanNumber) {
return scanNumber + 2;
}
public QualityInfo getQualityInfo(int scanNumber) {
boolean isGoodEnough = (scanNumber >= 5);
return ImmutableQualityInfo.of(scanNumber, isGoodEnough, false);
}
}
ImagePipelineConfig config = ImagePipelineConfig.newBuilder()
.setProgressiveJpegConfig(pjpeg)
.build();
除了自己實作ProgressiveJpegConfig, 也可以直接使用SimpleProgressiveJpegConfig.
At Request Time
目前,我們必須顯式地在加載時,允許漸進式JPEG圖檔加載。
Uri uri;
ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(uri)
.setProgressiveRenderingEnabled(true)
.build();
PipelineDraweeController controller = Fresco.newControllerBuilder()
.setImageRequest(requests)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
我們希望在後續的版本中,在
setImageURI
方法中可以直接支援漸進式圖檔加載。
動畫圖支援編輯和糾錯
Fresco 支援 GIF 和 WebP 格式的動畫圖檔。對于 WebP 格式的動畫圖的支援包括擴充的 WebP 格式,即使 Android 2.3及其以後那些沒有原生 WebP 支援的系統。
設定動畫圖自動播放
如果你希望圖檔下載下傳完之後自動播放,同時,當View從螢幕移除時,停止播放,隻需要在image request 中簡單設定,如下:
Uri uri;
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setAutoPlayAnimations(true)
. // other setters
.build();
mSimpleDraweeView.setController(controller);
手動控制動畫圖播放
也許你希望在代碼中直接控制動畫的播放。這種情況下,你需要監聽圖檔是否加載完畢,然後才能控制動畫的播放:
ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(
String id,
@Nullable ImageInfo imageInfo,
@Nullable Animatable anim) {
if (anim != null) {
// app-specific logic to enable animation starting
anim.start();
}
};
Uri uri;
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setUri(uri)
.setControllerListener(controllerListener)
// other setters
.build();
mSimpleDraweeView.setController(controller);
另外,controller提供對Animatable 的通路。
如果有可用動畫的話,可對動畫進行靈活的控制:
Animatable animation = mSimpleDraweeView.getController().getAnimatable();
if (animation != null) {
// 開始播放
animation.start();
// 一段時間之後,根據業務邏輯,停止播放
animation.stop();
}
限制#
動畫現在還不支援 postprocessors 。
多圖請求及圖檔複用編輯和糾錯
多圖請求需 自定義ImageRequest.
先顯示低分辨率的圖,然後是高分辨率的圖
假設你要顯示一張高分辨率的圖,但是這張圖下載下傳比較耗時。與其一直顯示占位圖,你可能想要先下載下傳一個較小的縮略圖。
這時,你可以設定兩個圖檔的URI,一個是低分辨率的縮略圖,一個是高分辨率的圖。
Uri lowResUri, highResUri;
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setLowResImageRequest(ImageRequest.fromUri(lowResUri))
.setImageRequest(ImageRequest.fromUri(highResUri))
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
縮略圖預覽
本功能僅支援本地URI,并且是JPEG圖檔格式
如果本地JPEG圖,有EXIF的縮略圖,image pipeline 可以立刻傳回它作為一個縮略圖。
Drawee
會先顯示縮略圖,完整的清晰大圖在 decode 完之後再顯示。
Uri uri;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setLocalThumbnailPreviewsEnabled(true)
.build();
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
加載最先可用的圖檔
大部分時候,一張圖檔隻有一個 URI。加載它,然後工作完成~
但是假設同一張圖檔有多個 URI 的情況。比如,你可能上傳過一張拍攝的照片。原始圖檔太大而不能上傳,是以圖檔首先經過了壓縮。在這種情況下,首先嘗試擷取本地壓縮後的圖檔 URI,如果失敗的話,嘗試擷取本地原始圖檔 URI,如果還是失敗的話,嘗試擷取上傳到網絡的圖檔 URI。直接下載下傳我們本地可能已經有了的圖檔不是一件光彩的事。
Image pipeline 會首先從記憶體中搜尋圖檔,然後是磁盤緩存,再然後是網絡或其他來源。對于多張圖檔,不是一張一張按上面的過程去做,而是 pipeline 先檢查所有圖檔是否在記憶體。隻有沒在記憶體被搜尋到的才會尋找磁盤緩存。還沒有被搜尋到的,才會進行一個外部請求。
使用時,建立一個image request 數組,然後傳給
ControllerBuilder
:
Uri uri1, uri2;
ImageRequest request = ImageRequest.fromUri(uri1);
ImageRequest request2 = ImageRequest.fromUri(uri2);
ImageRequest[] requests = { request1, request2 };
DraweeController controller = Fresco.newDraweeControllerBuilder()
.setFirstAvailableImageRequests(requests)
.setOldController(mSimpleDraweeView.getController())
.build();
mSimpleDraweeView.setController(controller);
這些請求中隻有一個會被展示。第一個被發現的,無論是在記憶體,磁盤或者網絡,都會是被傳回的那個。pipeline 認為數組中請求的順序即為優先順序。
自定義 DataSource Supplier
DataSource Supplier
為了更好的靈活性,你可以在建立
Drawee controller
時自定義
DataSource Supplier
。你可以以
FirstAvailiableDataSourceSupplier
,
IncreasingQualityDataSourceSupplier
為例自己實作
DataSource Supplier
,或者以
AbstractDraweeControllerBuilder
為例将多個
DataSource Supplier
根據需求組合在一起。
監聽下載下傳事件編輯和糾錯
動機
你也許想在圖檔下載下傳完成後執行一些動作,比如使某個别的
View
可見,或者顯示一些文字。你也許還想在下載下傳失敗後做一些事,比如向使用者顯示一條失敗資訊。
圖檔是背景線程異步加載的,是以你需要某一方式來監聽
DraweeController
傳遞的事件。我們可以使用一個
ControllerListener
實作事件的監聽。
在監聽事件回調時,無法修改圖檔,如果需要修改圖檔,可使用後處理器(Postprocessor)
使用方法
簡單定義一個
ControllerListener
即可,推薦繼承
BaseControllerListener
:
ControllerListener controllerListener = new BaseControllerListener<ImageInfo>() {
@Override
public void onFinalImageSet(
String id,
@Nullable ImageInfo imageInfo,
@Nullable Animatable anim) {
if (imageInfo == null) {
return;
}
QualityInfo qualityInfo = imageInfo.getQualityInfo();
FLog.d("Final image received! " +
"Size %d x %d",
"Quality level %d, good enough: %s, full quality: %s",
imageInfo.getWidth(),
imageInfo.getHeight(),
qualityInfo.getQuality(),
qualityInfo.isOfGoodEnoughQuality(),
qualityInfo.isOfFullQuality());
}
@Override
public void onIntermediateImageSet(String id, @Nullable ImageInfo imageInfo) {
FLog.d("Intermediate image received");
}
@Override
public void onFailure(String id, Throwable throwable) {
FLog.e(getClass(), throwable, "Error loading %s", id)
}
};
Uri uri;
DraweeController controller = Fresco.newControllerBuilder()
.setControllerListener(controllerListener)
.setUri(uri);
// other setters
.build();
mSimpleDraweeView.setController(controller);
對所有的圖檔加載,
onFinalImageSet
或者
onFailure
都會被觸發。前者在成功時,後者在失敗時。
如果允許呈現漸進式JPEG,同時圖檔也是漸進式圖檔,
onIntermediateImageSet
會在每個掃描被解碼後回調。具體圖檔的那個掃描會被解碼,參見漸進式JPEG圖
縮放和旋轉圖檔編輯和糾錯
使用這個功能需要直接建立 image request。
術語
Scaling 是一種畫布操作,通常是由硬體加速的。圖檔實際大小保持不變,它隻不過在繪制時被放大或縮小。
Resizing 是一種軟體執行的管道操作。它傳回一張新的,尺寸不同的圖檔。
Downsampling 同樣是軟體實作的管道操作。它不是建立一張新的圖檔,而是在解碼時改變圖檔的大小。
應該 resize 還是 scale ?
Resize 很少是必要的。Scale 是大部分情況下的優先選擇,即使在用 resize 時。
Resize 有以下幾個限制:
- 修改尺寸是受限的,它不能傳回一張更大的圖檔,隻能讓圖檔變小。
- 目前,隻有 JPEG 圖檔可以修改尺寸。
- 對于産生的圖檔的尺寸,隻能粗略地控制。圖檔不能修改為确定的尺寸,隻能通過支援的修改選項來變化。這意味着即使是修改後的圖檔,也需要在展示前進行 scale 操作。
- 隻支援以下的修改選項: N / 8,1 <= N <= 8
- Resize 是由軟體執行的,相比硬體加速的 scale 操作較慢。
Scale 并沒有以上的限制,它使用 Android 内置的功能使圖檔和顯示邊界相符。在 Android 4.0 及之後,它可以通過 GPU 進行加速。這在大部分情況下是最快,同時也是最高效地将圖檔顯示為你想要的尺寸的方式。唯一的缺點是當圖檔遠大于顯示大小時,會浪費記憶體。
那麼什麼時候該使用 resize 呢?你應該隻在需要展示一張遠大于顯示大小的圖檔時使用 resize 以節省記憶體。一個例子是當你想要在 1280*720(大約 1MP) 的 view 中顯示一張 8MP 的照片時。一張 8MP 的圖檔當解碼為 4位元組/像素的 ARGB 圖檔時大約占 32MB 的記憶體。如果 resize 為顯示大小,它隻占用少于 4MB 記憶體。
對于網絡圖檔,在考慮 resize 之前,先嘗試請求大小合适的圖檔。如果伺服器能傳回一張較小的圖,就不要請求一張 8MP 的高解析度圖檔。你應該考慮使用者的流量。同時,擷取較小的圖檔可以減少你的 APP 的存儲空間和 CPU 占用。
隻有當伺服器不提供可選的較小圖檔,或者你在使用本地圖檔時,你才應該采取 resize。在任何其他情況下,包括放大圖檔,都該使用 scale。對于 scale,隻需要指定
SimpleDraweeView
中
layout_width
和
layout_height
的大小,就像在其他 Android View 中做的那樣。然後指定縮放類型。
Resizing
Resize 并不改變原始圖檔,它隻在解碼前修改記憶體中的圖檔大小。
相比 Android 内置的功能,這個方法可以進行更大範圍的調整。尤其是通過相機拍攝的照片,對于 scale 來說通常太大,需要在顯示前進行 resize。
目前僅支援 JPEG 格式圖檔的 resize 操作,但它是最常用的圖檔格式,且大多數安卓裝置的相機照片存儲為該格式。
如果要 resize,建立
ImageRequest
時,提供一個 ResizeOptions :
Uri uri = "file:///mnt/sdcard/MyApp/myfile.jpg";
int width = 50, height = 50;
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setResizeOptions(new ResizeOptions(width, height))
.build();
PipelineDraweeController controller = Fresco.newDraweeControllerBuilder()
.setOldController(mDraweeView.getController())
.setImageRequest(request)
.build();
mSimpleDraweeView.setController(controller);
向下采樣(Downsampling)
向下采樣是一個最近添加到 Fresco 的特性。使用的話需要在設定 image pipeline 時進行設定:
如果開啟該選項,pipeline 會向下采樣你的圖檔,代替 resize 操作。你仍然需要像上面那樣在每個圖檔請求中調用
setResizeOptions
。
向下采樣在大部分情況下比 resize 更快。除了支援 JPEG 圖檔,它還支援 PNG 和 WebP(除動畫外) 圖檔。
我們希望在将來的版本中預設開啟此選項。
自動旋轉
如果看到的圖檔是側着的,使用者會非常難受。許多裝置會在 JPEG 檔案的 metadata 中記錄下照片的方向。如果你想圖檔呈現的方向和裝置螢幕的方向一緻,你可以簡單地這樣做到:
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setAutoRotateEnabled(true)
.build();
// as above
修改圖檔編輯和糾錯
動機
有時,我們想對從伺服器下載下傳,或者本地擷取的圖檔做些修改,比如在某個坐标統一加個網格什麼的。你可以使用
Postprocessor
,最好的方式是繼承 BasePostprocessor。
例子
下面的例子給圖檔加了紅色網格:
Uri uri;
Postprocessor redMeshPostprocessor = new BasePostprocessor() {
@Override
public String getName() {
return "redMeshPostprocessor";
}
@Override
public void process(Bitmap bitmap) {
for (int x = 0; x < bitmap.getWidth(); x+=2) {
for (int y = 0; y < bitmap.getHeight(); y+=2) {
bitmap.setPixel(x, y, Color.RED);
}
}
}
}
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(redMeshPostprocessor)
.build();
PipelineDraweeController controller = (PipelineDraweeController)
Fresco.newDraweeControllerBuilder()
.setImageRequest(request)
.setOldController(mSimpleDraweeView.getController())
// other setters as you need
.build();
mSimpleDraweeView.setController(controller);
注意點
圖檔在進入後處理器(postprocessor)的圖檔是原圖的一個完整拷貝,原來的圖檔不受修改的影響。在5.0以前的機器上,拷貝後的圖檔也在native記憶體中。
在開始一個圖檔顯示時,即使是反複顯示同一個圖檔,在每次進行顯示時,都需要指定後處理器。對于同一個圖檔,每次顯示可以使用不同的後處理器。
後處理器現在不支援動畫圖檔。
複制 bitmap
可能會出現即時的後處理無法實作的情況。如果出現該情況,
BasePostprocessor
還有一個接收兩個參數的 process 方法。下面的例子實作了水準翻轉圖檔:
@Override
public void process(Bitmap destBitmap, Bitmap sourceBitmap) {
for (int x = 0; x < destBitmap.getWidth(); x++) {
for (int y = 0; y < destBitmap.getHeight(); y++) {
destBitmap.setPixel(destBitmap.getWidth() - x, y, sourceBitmap.getPixel(x, y));
}
}
}
源圖檔和目标圖檔具有相同的大小。
- 不要修改源圖檔。在未來的版本中這會抛出一個異常。
- 不要儲存對任何一個圖檔的引用。它們的記憶體會由 image pipeline 進行管理,目标圖檔會在 Drawww 或 DataSource 中正常地銷毀。
複制成不同大小
如果處理後的圖檔大小需要和原圖檔不同,我們有第三個 process 方法。你可以使用
PlatformBitmapFactory
類以指定的大小安全地建立一張圖檔,在 Java Heap 之外。
下面的例子将源圖檔複制為 1 / 4 大小。
@Override
public CloseableReference<Bitmap> process(
Bitmap sourceBitmap,
PlatformBitmapFactory bitmapFactory) {
CloseableReference<Bitmap> bitmapRef = bitmapFactory.createBitmap(
sourceBitmap.getWidth() / 2,
sourceBitmap.getHeight() / 2);
try {
Bitmap destBitmap = bitmapRef.get();
for (int x = 0; x < destBitmap.getWidth(); x+=2) {
for (int y = 0; y < destBitmap.getHeight(); y+=2) {
destBitmap.setPixel(x, y, sourceBitmap.getPixel(x, y));
}
}
return CloseableReference.cloneOrNull(bitmapRef);
} finally {
CloseableReference.closeSafely(bitmapRef);
}
}
你必須遵循 closeable references 的規則。
不要使用 Android 中 Bitmap.createBitmap() 方法,它會在 Java 堆記憶體中産生一個 bitmap 對象。
應該 Override 哪個方法?
不要重寫多于 1 個的 process 方法。這麼做可能造成無法預測的結果。
緩存處理後的圖檔
你可以選擇性地緩存後處理器的輸出結果。它會和原始圖檔一起放在緩存裡。
如果要這樣做,你的後處理器必須實作
getPostprocessorCacheKey
方法,并傳回一個非空的結果。
為實作緩存命中,随後的請求中使用的後處理器必須是同一個類并傳回同樣的鍵。否則,它的傳回結果将會覆寫之前緩存的條目。
例子:
public class OperationPostprocessor extends BasePostprocessor {
private int myParameter;
public OperationPostprocessor(int param) {
myParameter = param;
}
public void process(Bitmap bitmap) {
doSomething(myParameter);
}
public CacheKey getPostprocessorCacheKey() {
return new MyCacheKey(myParameter);
}
}
如果你想要緩存總是命中,隻需在
getPostprocessorCacheKey
中傳回一個常量值。如果你的
postprocessor
總是傳回不同的結果,而你也不想要緩存命中,傳回 null。
重複的後處理
如果想對同一個圖檔進行多次後處理,那麼繼承BaseRepeatedPostprocessor即可。該類有一個
update
方法,需要執行後處理時,調用該方法即可。
下面的例子展示了在運作時,後處理改變圖檔網格的顔色:
public class MeshPostprocessor extends BaseRepeatedPostprocessor {
private int mColor = Color.TRANSPARENT;
public void setColor(int color) {
mColor = color;
update();
}
@Override
public String getName() {
return "meshPostprocessor";
}
@Override
public void process(Bitmap bitmap) {
for (int x = 0; x < bitmap.getWidth(); x+=2) {
for (int y = 0; y < bitmap.getHeight(); y+=2) {
bitmap.setPixel(x, y, mColor);
}
}
}
}
MeshPostprocessor meshPostprocessor = new MeshPostprocessor();
// setPostprocessor as in above example
// 改變顔色
meshPostprocessor.setColor(Color.RED);
meshPostprocessor.setColor(Color.BLUE);
每個 image request, 應該隻有一個
Postprocessor
,但是這個後處理器是狀态相關了。
透明的圖檔
根據 postprocessor 的性質,目标圖檔不會永遠是完全不透明的。由于
Bitmap.hasAlpha
方法的傳回值,這有時會導緻問題。也就是說如果該方法傳回 false(預設值),Android 會選擇不進行混合地快速繪制。這會導緻出現一張用黑色代替透明像素的半透明圖檔。為了解決這一問題,将目标圖檔中該值設為 true。
(圖檔請求)Image Requests編輯和糾錯
如果你需要的
ImageRequest
僅僅是一個URI,那麼
ImageRequest.fromURI
就足夠了,在多圖請求及圖檔複用中,有這樣的用法。
否則,你需要
ImageRequestBuilder
來做更多的事情。
Uri uri;
ImageDecodeOptions decodeOptions = ImageDecodeOptions.newBuilder()
.setBackgroundColor(Color.GREEN)
.build();
ImageRequest request = ImageRequestBuilder
.newBuilderWithSource(uri)
.setImageDecodeOptions(decodeOptions)
.setAutoRotateEnabled(true)
.setLocalThumbnailPreviewsEnabled(true)
.setLowestPermittedRequestLevel(RequestLevel.FULL_FETCH)
.setProgressiveRenderingEnabled(false)
.setResizeOptions(new ResizeOptions(width, height))
.build();
ImageRequest 的屬性和成員
-
- 唯一的必選的成員. 參考 支援的URIsuri
-
- 是否支援自動旋轉.autoRotateEnabled
-
- 是否支援漸進式加載.progressiveEnabled
-
- 後處理器(postprocess).postprocessor
-
- 圖檔縮放選項,用前請先閱讀縮放和旋轉.resizeOptions
最低請求級别
Image pipeline 加載圖檔時有一套明确的請求流程
- 檢查記憶體緩存,有如,立刻傳回。這個操作是實時的。
- 檢查未解碼的圖檔緩存,如有,解碼并傳回。
- 檢查磁盤緩存,如果有加載,解碼,傳回。
- 下載下傳或者加載本地檔案。調整大小和旋轉(如有),解碼并傳回。對于網絡圖來說,這一套流程下來是最耗時的。
setLowestPermittedRequestLevel
允許設定一個最低請求級别,請求級别和上面對應地有以下幾個取值:
-
BITMAP_MEMORY_CACHE
-
ENCODED_MEMORY_CACHE
-
DISK_CACHE
-
FULL_FETCH
如果你需要立即取到一個圖檔,或者在相對比較短時間内取到圖檔,否則就不顯示的情況下,這非常有用。
自定義View編輯和糾錯
DraweeHolders
總有一些時候,
DraweeViews
是滿足不了需求的,在展示圖檔的時候,我們還需要展示一些其他的内容,或者支援一些其他的操作。在同一個View裡,我們可能會想顯示一張或者多張圖。
在自定義View中,Fresco 提供了兩個類來負責圖檔的展現:
-
單圖情況下用。DraweeHolder
-
多圖情況下用。MultiDraweeHolder
自定義View需要完成的事情
Android 呈現View對象,隻有View對象才能得到一些系統事件的通知。
DraweeViews
處理這些事件通知,高效地管理記憶體。使用
DraweeHolder
時,你需要自己實作這幾個方法。
處理 attach/detach 事件
如果沒按照以下步驟實作的話,很可能會引起記憶體洩露
當圖檔不再在View上顯示時,比如滑動時View滑動到螢幕外,或者不再繪制,圖檔就不應該再存在在記憶體中。Drawees 監聽這些事情,并負責釋放記憶體。當圖檔又需要顯示時,重新加載。
這些在
DraweeView
中是自動的,但是在自定義View中,需要我們自己去操作,如下:
DraweeHolder mDraweeHolder;
@Override
public void onDetachedFromWindow() {
super.onDetachedFromWindow();
mDraweeHolder.onDetach();
}
@Override
public void onStartTemporaryDetach() {
super.onStartTemporaryDetach();
mDraweeHolder.onDetach();
}
@Override
public void onAttachedToWindow() {
super.onAttachedToWindow();
mDraweeHolder.onAttach();
}
@Override
public void onFinishTemporaryDetach() {
super.onFinishTemporaryDetach();
mDraweeHolder.onAttach();
}
處理觸摸事件
如果你啟用了點選重新加載,在自定義View中,需要這樣:
@Override
public boolean onTouchEvent(MotionEvent event) {
return mDraweeHolder.onTouchEvent(event) || super.onTouchEvent(event);
}
自定義onDraw
你必須調用
Drawable drawable = mDraweeHolder.getHierarchy().getTopLevelDrawable();
drawable.setBounds(...);
否則圖檔将不會出現。
- 不要向下轉換這個Drawable
- 不要變換這個Drawable
其他應該做的
- 設定
Drawable.Callback
// When a holder is set to the view for the first time,
// don't forget to set the callback to its top-level drawable:
mDraweeHolder = ...
mDraweeHolder.getTopLevelDrawable().setCallback(this);
// In case the old holder is no longer needed,
// don't forget to clear the callback from its top-level drawable:
mDraweeHolder.getTopLevelDrawable().setCallback(null);
mDraweeHolder = ...
- 重寫
verifyDrawable:
@Override
protected boolean verifyDrawable(Drawable who) {
if (who == mDraweeHolder.getHierarchy().getTopLevelDrawable()) {
return true;
}
// 對其他Drawable的驗證邏輯
}
- 確定
處理了圖檔占用的那塊區域。invalidateDrawable
建立 DraweeHolder
這同樣需要非常小心和細緻
構造函數
我們推薦如下實作構造函數:
- 重寫所有構造函數,共 3 個
- 在每個構造函數中調用同等簽名的父類構造函數,和一個私有的
方法。init
- 在
方法中執行初始化操作。init
即,不要在構造函數中用
this
來調用另外一個構造。
這樣可以保證,不管調用哪個構造,都可以正确地執行初始化流程。holder 在
init
方法中被建立。
建立 Holder
如果有可能,隻在 View 建立時,建立 Drawees。建立 DraweeHierarchy 開銷較大,最好隻做一次。
class CustomView extends View {
DraweeHolder<GenericDraweeHierarchy> mDraweeHolder;
// constructors following above pattern
private void init() {
GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources());
.set...
.set...
.build();
mDraweeHolder = DraweeHolder.create(hierarchy, context);
}
}
設定要顯示的圖檔
使用 controller builder 建立DraweeController,然後調用holder的
setController
方法,而不是設定給自定義 View。
DraweeController controller = Fresco.newControllerBuilder()
.setUri(uri)
.setOldController(mDraweeHolder.getController())
.build();
mDraweeHolder.setController(controller);
MultiDraweeHolder
和
DraweeHolder
相比,
MultiDraweeHolder
有
add
,
remove
,
clear
等方法可以操作 Drawees。如下:
MultiDraweeHolder<GenericDraweeHierarchy> mMultiDraweeHolder;
private void init() {
GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(getResources());
.set...
.build();
mMultiDraweeHolder = new MultiDraweeHolder<GenericDraweeHierarchy>();
mMultiDraweeHolder.add(new DraweeHolder<GenericDraweeHierarchy>(hierarchy, context));
// repeat for more hierarchies
}
同樣,也需要處理系統事件,設定聲音等等,就像處理單個
DraweeHolder
那樣。