您的位置:首页 > 移动开发 > Android开发

Fresco实践总结

2017-03-03 09:16 218 查看


Fresco实践总结-高斯模糊、圆形圆角、URL、File、Assets、Resource

题外话:在这之前的一段时间里,由于种种原因,一直在忙,在这草长莺飞的三月,开启我2017的第一篇博文。2017,让我们共同创造神话。本篇文章转载自:http://blog.yanzhenjie.com

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'

// 如果需要支持gif,再添加:
compile 'com.facebook.fresco:animated-gif:0.12.0'
1
2
3
4
5


初始化

建议在App启动就初始化,所以建议写在
Application#onCreate()
中,记得在
manifest.xml
注册Application

一般初始化:
public class App extends Application {

@Override
public void onCreate() {
super.onCreate();
Fresco.initialize(this);
}
...
1
2
3
4
5
6
7
8


高级初始化-配置缓存文件夹

// 高级初始化:
Fresco.initialize(this, ImagePipelineConfig.newBuilder(App.this)
.setMainDiskCacheConfig(
DiskCacheConfig.newBuilder(this)
.setBaseDirectoryPath(new File("SD卡路径")) // 注意Android运行时权限。
.build()
)
.build()
);
1
2
3
4
5
6
7
8
9

上面的高级初始化当然不止这么一点点,这里举出一个敏感的例子,就是配置缓存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;

// 如果有SD卡权限则直接初始化到SD卡。
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)
// 启动app:
startActivity(new Intent(SplashActivity.this, MainActivity.class));
}

@Override
public void onFailed(int requestCode, List<String> deniedPermissions) {
if(requestCode == 100)
// 用户不授权,则退出app:
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+"
1

然后在Application中初始化的时候注意:
/**
* 初始话Fresco。
*/
public void initFresco() {
// 你的OkHttpClient根据你的设计来,建议是单例:
OkHttpClient okHttpClient = new OkHttpClient();
Fresco.initialize(this, OkHttpImagePipelineConfigFactory.newBuilder(App.this, okHttpClient)
.setMainDiskCacheConfig(
DiskCacheConfig.newBuilder(this)
.setBaseDirectoryPath(new File("SD卡的路径..."))
.build()
)
.build()
);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这里只需要注意原来的
ImagePipelineConfig
换成了
OkHttpImagePipelineConfigFactory
,并且需要一个
OkHttpClient
的对象。


加载网络图片、url、assets、res、本地File图片

先给出支持的URI格式列表(列表来自fresco-cn.org):
TypeSchemeSample
http远程图片http://或者https://HttpURLConnection或者OkHttp
本地文件file://FileInputStream
Content providercontent://ContentResolver
res目录下的资源res://Resources.openRawResource
asset目录下的资源asset://AssetManager
Uri中指定图片数据data:mime/type;base64,数据类型必须符合rfc2397规定
(仅支持 UTF-8)


SimpleDraweeView

实际开发中,如果没有特殊需求,我们一般使用
SimpleDraweeView
来占位,传统的图片加载框架一般是使用
ImageView
,例如:
<ImageView
.../>
1
2

在使用
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"/>
1
2
3
4

把它当成我们平常使用的
ImageView
即可,不过我们要注意
Fresco
不支持
wrap_content
(具体原因看这里),需要使用
match_parent
或者显示指定view宽高,有些同学看到这个就很头疼了,但是这真的不是什么问题,下面给出解决方案:

一、由服务器返回URL时返回图片的宽高信息 

平常我们服务器这样返回图片url的:
{
"name":"严振杰",
"head":"http://www.yanzhenjie.com/images/main/yzj_head.png"
}
1
2
3
4

使用Fresco我们可以把一个图片url当成一个对象包裹起来:
{
"name":"严振杰",
"head":
{
"url":"http://www.yanzhenjie.com/images/main/yzj_head.png",
"width":"500",
"height":"500"
}
}
1
2
3
4
5
6
7
8
9

或者在URL后面跟一个宽高的参数:
{
"name":"严振杰",
"head":"http://www.yanzhenjie.com/images/main/yzj_head.png?width=500&height=500"
}
1
2
3
4

二、根据设计师的给的尺寸,预先设置图片宽高 

设计师设计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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13


二、显示本地图片

这里就有个坑了,先看一下下面我写了两个方法,一个需要传入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);
1

所以我们一般留空,因此我们的代码看起来是下面的样子:
/**
* 显示一个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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14


四、显示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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13


五、显示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();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13

你以为到这里就完了吗?并没有,继续看。


一些默认属性的设置

<com.facebook.drawee.view.SimpleDraweeView
android:layout_width="20dp"
android:layout_height="20dp"
fresco:fadeDuration="300" // 淡出时间,毫秒。
fresco:actualImageScaleType="focusCrop" // 等同于android:scaleType。
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" // 背景图,不会被View遮挡。

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);

// load image from ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14


一些特殊效果

第一个,先来一个圆形图片带白色的边:



综合上面的属性这里就不多解释了:
<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"/> // 描边宽度。
1
2
3
4
5
6
7

第二个,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());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

用起来也很简单:
loadToBitmap(imageUrl, new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(@Nullable Bitmap bitmap) {
// 读取成功。
}

@Override
public void onFailureImpl(DataSource dataSource) {
// 读取失败。
}
});
1
2
3
4
5
6
7
8
9
10
11

       
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Fresco android