Code Behavior, one can.t be less.
Fresco是一個Facebook開源的Android圖片加載庫,性能真的讓我無話可說,而且滿足了我對圖片加載的一切幻想,所以我必須爲它寫一篇文章,當然更多的是自己的總結與記錄。
Fresco開源地址:https://github.com/facebook/fresco
Fresco文檔地址:https://www.fresco-cn.org
前端時間我寫了一個Android相冊庫Album:https://github.com/yanzhenjie/album,當時我在做測試的時候發現,用Picasso或者Glide時,當列表達到上千條時,滑動起來就會卡,但是換成Fresco
後一點都不卡了,而且Fresco
做到的幾個內置效果讓我欣喜若狂,所以我把我的使用總結記錄下來:
依賴Fresco
compile 'com.facebook.fresco:fresco:0.14.1'
compile 'com.facebook.fresco:animated-gif:0.12.0'
初始化
建議在App啓動就初始化,所以建議寫在Application#onCreate()
中,記得在manifest.xml
註冊Application
一般初始化:
public class App extends Application {
@Override
public void onCreate() {
super.onCreate();
Fresco.initialize(this);
}
...
高級初始化-配置緩存文件夾
Fresco.initialize(this, ImagePipelineConfig.newBuilder(App.this)
.setMainDiskCacheConfig(
DiskCacheConfig.newBuilder(this)
.setBaseDirectoryPath(new File("SD卡路徑"))
.build()
)
.build()
);
上面的高級初始化當然不止這麼一點點,這裏舉出一個敏感的例子,就是配置緩存SD卡路徑,這裏涉及到Android6.0運行時權限,我也給一個解決方案,我使用的權限管理庫是AndPermission
(https://github.com/yanzhenjie/AndPermission):
首先在Application
中判斷是否有SD卡權限,如果有則初始化到SD卡,如果沒有則採用默認配置:
public class App extends Application {
private static App app;
@Override
public void onCreate() {
super.onCreate();
app = this;
if (AndPermission.hasPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE))
initFresco();
else {
Fresco.initialize(this);
}
}
/**
* 高級初始話Fresco。
*/
public void initFresco() {
Fresco.initialize(this, ImagePipelineConfig.newBuilder(App.this)
.setMainDiskCacheConfig(
DiskCacheConfig.newBuilder(this)
.setBaseDirectoryPath(new File("SD卡的路徑..."))
.build()
)
.build()
);
}
public static App get() {
return app;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
然後在SplashActivity
申請SD卡權限,已被下次進入App
時初始化Fresco
時擁有SD卡權限:
public class SplashActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
AndPermission.with(this)
.requestCode(100)
.permission(Manifest.permission.READ_CALENDAR)
.send();
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
AndPermission.onRequestPermissionsResult(requestCode, permissions, grantResults, listener);
}
/**
* 權限監聽。
*/
private PermissionListener listener = new PermissionListener() {
@Override
public void onSucceed(int requestCode, List<String> grantPermissions) {
if(requestCode == 100)
startActivity(new Intent(SplashActivity.this, MainActivity.class));
}
@Override
public void onFailed(int requestCode, List<String> deniedPermissions) {
if(requestCode == 100)
finish();
}
};
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
高級初始化-配置網絡層爲OkHttp
Fresco
默認使用HttpURLConnection
作爲網絡層,當然也可以配置OkHttp作爲它的網絡層,配置OkHttp爲它的網絡層需要依賴下面的庫:
compile "com.facebook.fresco:imagepipeline-okhttp3:0.12.0+"
然後在Application中初始化的時候注意:
/**
* 初始話Fresco。
*/
public void initFresco() {
OkHttpClient okHttpClient = new OkHttpClient();
Fresco.initialize(this, OkHttpImagePipelineConfigFactory.newBuilder(App.this, okHttpClient)
.setMainDiskCacheConfig(
DiskCacheConfig.newBuilder(this)
.setBaseDirectoryPath(new File("SD卡的路徑..."))
.build()
)
.build()
);
}
這裏只需要注意原來的ImagePipelineConfig
換成了OkHttpImagePipelineConfigFactory
,並且需要一個OkHttpClient
的對象。
加載網絡圖片、url、assets、res、本地File圖片
先給出支持的URI格式列表(列表來自fresco-cn.org):
Type |
Scheme |
Sample |
---|
http遠程圖片 |
http://或者https:// |
HttpURLConnection或者OkHttp |
本地文件 |
file:// |
FileInputStream |
Content provider |
content:// |
ContentResolver |
res目錄下的資源 |
res:// |
Resources.openRawResource |
asset目錄下的資源 |
asset:// |
AssetManager |
Uri中指定圖片數據 |
data:mime/type;base64, |
數據類型必須符合rfc2397規定
(僅支持 UTF-8) |
SimpleDraweeView
實際開發中,如果沒有特殊需求,我們一般使用SimpleDraweeView
來佔位,傳統的圖片加載框架一般是使用ImageView
,例如:
<ImageView
.../>
在使用Fresco
時我們一般使用SimpleDraweeView
,它也是繼承ImageView
的:
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/iv_head_background"
android:layout_width="match_parent"
android:layout_height="@dimen/dp_200"/>
把它當成我們平常使用的ImageView
即可,不過我們要注意Fresco
不支持wrap_content
(具體原因看這裏),需要使用match_parent
或者顯示指定view寬高,有些同學看到這個就很頭疼了,但是這真的不是什麼問題,下面給出解決方案:
一、由服務器返回URL時返回圖片的寬高信息
平常我們服務器這樣返回圖片url的:
{
"name":"嚴振杰",
"head":"http://www.yanzhenjie.com/images/main/yzj_head.png"
}
使用Fresco我們可以把一個圖片url當成一個對象包裹起來:
{
"name":"嚴振杰",
"head":
{
"url":"http://www.yanzhenjie.com/images/main/yzj_head.png",
"width":"500",
"height":"500"
}
}
或者在URL後面跟一個寬高的參數:
{
"name":"嚴振杰",
"head":"http://www.yanzhenjie.com/images/main/yzj_head.png?width=500&height=500"
}
二、根據設計師的給的尺寸,預先設置圖片寬高
設計師設計UI的時候肯定會用一個屏幕作爲標準,比如iOS的750*1340
,或者Android的720*1280
,我們可以根據設計圖和手機實際寬高計算出View在手機中應有的寬高,見下面的代碼。
最後:我們在解析出來寬高後,我們可以動態的設置SimpleDraweeView
的寬高:
/**
* 設置view大小。
*
* @param view View。
* @param width 指定寬。
* @param width 指定高。
*/
public static void requestLayout(View view, int width, int height) {
ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
if (layoutParams == null) {
layoutParams = new ViewGroup.LayoutParams(width, height);
view.setLayoutParams(layoutParams);
} else {
view.getLayoutParams().width = width;
view.getLayoutParams().height = height;
view.requestLayout();
}
}
/**
* 根據設計圖寬高,計算出View在該屏幕上的實際寬高。
*
* @param width 設計圖中View寬。
* @param height 設計圖中View高。
*/
public static void calcRealSizeByDesign(View view, int width, int height) {
int realWidth, realHeight;
realWidth = 設備屏幕寬度 * width / 設計圖屏幕寬度;
realHeight = measure[0] * height / width;
requestLayout(view, realWidth, realHeight);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
基礎配置和注意的地方講完了,那麼下面就是重頭戲了,如何加載圖片。
一、加載http/https遠程圖片
/**
* 顯示http或者https遠程圖片。
*
* @param draweeView imageView。
* @param url 連接地址。
*/
public static void showUrl(SimpleDraweeView draweeView, String url) {
try {
draweeView.setImageURI(Uri.parse(url));
} catch (Exception e) {
e.printStackTrace();
}
}
二、顯示本地圖片
這裏就有個坑了,先看一下下面我寫了兩個方法,一個需要傳入View的實際寬高,一個不需要。上面已經說了,SimpleDraweeView
需要在xml中、java中指定它的寬高,或者是使用match_parent
。
這裏需要注意,1. 如果view指定的寬高不是match_parent
則直接調用第二個不需要傳入寬高的發那個發,如果爲SimpleDraweeView
寫的寬高是match_parent
時,加載圖片需要告訴Fresco
你的View在屏幕上的實際寬高是多少,否則是不能加載出來的。
比如,你的SimpleDraweeView
是全屏的,那麼你就填入屏幕的寬高,如果不是全屏,就利用上面講的方法測量出View的實際寬高後傳入。
/**
* 顯示一個本地圖片。
*
* @param draweeView imageView。
* @param path 路徑。
* @param width 實際寬。
* @param height 實際高度。
*/
public static void showFile(SimpleDraweeView draweeView, String path, int width, int height) {
try {
Uri uri = Uri.parse("file://" + path);
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setResizeOptions(new ResizeOptions(width, height))
.build();
AbstractDraweeController controller = Fresco.newDraweeControllerBuilder()
.setOldController(draweeView.getController())
.setImageRequest(request)
.build();
draweeView.setController(controller);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 顯示本地圖片。
*
* @param draweeView imageView。
* @param path 路徑。
*/
public static void showFile(SimpleDraweeView draweeView, String path) {
try {
Uri uri = Uri.parse("file://" + path);
draweeView.setImageURI(uri);
} catch (Exception e) {
e.printStackTrace();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
三、顯示res中圖片
這裏要注意,我們在爲res中的圖片生成Uri的時候:
Uri uri = Uri.parse("res://包名(任何字符串或者留空)/" + R.drawable.ic_launcher);
所以我們一般留空,因此我們的代碼看起來是下面的樣子:
/**
* 顯示一個Res中的圖片。
*
* @param draweeView ImageView。
* @param resId 資源ID。
*/
public static void showRes(SimpleDraweeView draweeView, @DrawableRes int resId) {
try {
draweeView.setImageURI(Uri.parse("res:///" + resId));
} catch (Exception e) {
e.printStackTrace();
}
}
四、顯示ContentProvider圖片
/**
* 顯示content provider圖片。
*
* @param draweeView image view。
* @param path 路徑。
*/
public static void showContentProvider(SimpleDraweeView draweeView, String path) {
try {
draweeView.setImageURI(Uri.parse("content://" + path));
} catch (Exception e) {
e.printStackTrace();
}
}
五、顯示assets中的圖片
/**
* 顯示Assets中的圖片。
*
* @param draweeView ImageView.
* @param path 路徑。
*/
public static void showAsset(SimpleDraweeView draweeView, String path) {
try {
draweeView.setImageURI(Uri.parse("asset://" + path));
} catch (Exception e) {
e.printStackTrace();
}
}
你以爲到這裏就完了嗎?並沒有,繼續看。
一些默認屬性的設置
<com.facebook.drawee.view.SimpleDraweeView
android:layout_width="20dp"
android:layout_height="20dp"
fresco:fadeDuration="300"
fresco:actualImageScaleType="focusCrop"
fresco:placeholderImage="@color/wait_color"
fresco:placeholderImageScaleType="fitCenter"
fresco:failureImage="@drawable/error"
fresco:failureImageScaleType="centerInside"
fresco:retryImage="@drawable/retrying"
fresco:retryImageScaleType="centerCrop"
fresco:progressBarImage="@drawable/progress_bar"
fresco:progressBarImageScaleType="centerInside"
fresco:progressBarAutoRotateInterval="1000"
fresco:backgroundImage="@color/blue"
fresco:roundAsCircle="false"
fresco:roundedCornerRadius="1dp"
fresco:roundTopLeft="true"
fresco:roundTopRight="false"
fresco:roundBottomLeft="false"
fresco:roundBottomRight="true"
fresco:roundingBorderWidth="2dp"
fresco:roundingBorderColor="@color/border_color" 描邊的顏色。
/>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
xml中可以配置,在Java代碼中也是可以配置的,我這裏列出一部分API:
SimpleDraweeView simpleDraweeView = new SimpleDraweeView(context);
simpleDraweeView.setLayoutParams(new ViewGroup.LayoutParams(-1, -1));
GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(context.getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
.setPlaceholderImage(R.drawable.fresco_failed)
.setPlaceholderImageScaleType(ScalingUtils.ScaleType.FIT_XY)
.setFailureImage(R.drawable.fresco_failed)
.setPressedStateOverlay(ResCompat.getDrawable(R.drawable.transparent_half_1))
.setFailureImageScaleType(ScalingUtils.ScaleType.FIT_XY)
.build();
simpleDraweeView.setHierarchy(hierarchy);
一些特殊效果
第一個,先來一個圓形圖片帶白色的邊:
![圓形帶有描邊]()
綜合上面的屬性這裏就不多解釋了:
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/iv_user_fund_user_head"
android:layout_width="80dp"
android:layout_height="80dp"
fresco:roundAsCircle="true" // 圓形圖片。
fresco:roundingBorderColor="@color/white" // 白色描邊。
fresco:roundingBorderWidth="2dp"/> // 描邊寬度。
第二個,Fresco高斯模糊:
![高斯模糊]()
/**
* 以高斯模糊顯示。
*
* @param draweeView View。
* @param url url.
* @param iterations 迭代次數,越大越魔化。
* @param blurRadius 模糊圖半徑,必須大於0,越大越模糊。
*/
public static void showUrlBlur(SimpleDraweeView draweeView, String url, int iterations, int blurRadius) {
try {
Uri uri = Uri.parse(url);
ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
.setPostprocessor(new IterativeBoxBlurPostProcessor(iterations, blurRadius))
.build();
AbstractDraweeController controller = Fresco.newDraweeControllerBuilder()
.setOldController(draweeView.getController())
.setImageRequest(request)
.build();
draweeView.setController(controller);
} catch (Exception e) {
e.printStackTrace();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
效果就這樣子,其它的關於進度條子類的自己去試試吧,我就不演示佔篇幅了。
拿到緩存的Bitmap
有幾次在羣裏討論關於Fresco的問題,很多同學吐槽Fresco沒有直接拿到Bitmap
的方法,其實直接拿到Bitmap
相當於從SD卡讀取一個文件,如果圖片過大,就會耗時,造成App卡(假)死,所以我們要採用異步的方式:
/**
* 加載圖片成bitmap。
*
* @param imageUrl 圖片地址。
*/
public static void loadToBitmap(String imageUrl, BaseBitmapDataSubscriber mDataSubscriber) {
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(Uri.parse(imageUrl))
.setProgressiveRenderingEnabled(true)
.build();
ImagePipeline imagePipeline = Fresco.getImagePipeline();
DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage
(imageRequest, App.get());
dataSource.subscribe(mDataSubscriber, CallerThreadExecutor.getInstance());
}
用起來也很簡單:
loadToBitmap(imageUrl, new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(@Nullable Bitmap bitmap) {
}
@Override
public void onFailureImpl(DataSource dataSource) {
}
});