Fresco使用
本指南将引导您完成在应用程序中开始使用Fresco所需的步骤,包括加载第一张图像。
1.更新Gradle配置
编辑您的build.gradle文件。您必须将以下行添加到该dependencies部分:
- dependencies {
- // your app's other dependencies
- implementation 'io.openharmony.tpc.thirdlib:fresco:1.0.0'
- }
2.可选:添加其他Fresco功能模块
根据您的应用程序的需求,还可以添加以下可选模块。
- dependencies {
- // For animated GIF support
- implementation 'io.openharmony.tpc.thirdlib:animated_gif:1.0.0'
- }
3.初始化Fresco和声明权限
Fresco需要初始化。只需执行1次,因此将初始化放置在AbilityPackage。例如:
- [MyAbilityPackage.java]
- public class MyAbilityPackage extends AbilityPackage {
- @Override
- public void onInitialize() {
- super.onInitialize();
- Fresco.initialize(this);
- }
- }
*注意:*请记住还要在中声明您的AbilityPackage类,config.json并添加所需的权限。在大多数情况下,您将需要INTERNET权限。
- "module": {
- ...
- "reqPermissions": [
- {
- "name": "ohos.permission.INTERNET",
- "reason": "internet",
- "usedScene": {
- "ability": [
- "com.sample.MainAbility"
- ],
- "when": "always"
- }
- }
- ],
- ...
- }
4.创建布局
在布局XML中,将自定义名称空间添加到顶层元素。访问自定义fresco:属性是必需的,该自定义属性使您可以控制图像的加载和显示方式。
- <!-- Any valid element will do here -->
- <DirectionalLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- xmlns:fresco="http://schemas.huawei.com/res"
- ohos:height="match_parent"
- ohos:width="match_parent"
- >
然后将添加SimpleDraweeView到布局:
- <com.facebook.drawee.view.SimpleDraweeView
- ohos:id="$+id:my_image_view"
- ohos:layout_width="130vp"
- ohos:layout_height="130vp"
- fresco:placeholderImage="$graphic:my_placeholder"
- />
要显示图像,只需执行以下操作:
- Uri uri = Uri.parse("https://.../logo.png");
- SimpleDraweeView draweeView = (SimpleDraweeView) findComponentById(ResourceTable.Id_my_image_view);
- draweeView.setImageURI(uri);
其余的由Fresco完成。
显示占位符,直到准备好图像为止。当视图离开屏幕时,图像将被下载,缓存,显示和从内存中清除。
使用SimpleDraweeView
使用Fresco,使用SimpleDraweeView加载图像。这些可以在XML布局中使用。最简单的SimpleDraweeView示例:
- <com.facebook.drawee.view.SimpleDraweeView
- ohos:id="$+id:drawee"
- ohos:width="200vp"
- ohos:height="200vp" />
注:SimpleDraweeView不支持match_content对width或height属性。唯一的例外是在设置宽高比时,如下所示:
- <com.facebook.drawee.view.SimpleDraweeView
- ohos:id="$+id:drawee"
- ohos:width="200vp"
- ohos:height="match_content"
- fresco:viewAspectRatio="1.33"
- />
加载图片
将图片加载到中的最简单方法SimpleDraweeView是调用setImageURI:
- mSimpleDraweeView.setImageURI(uri);
复制这样就可以使用Fresco加载图片
高级XML属性
SimpleDraweeView,只是名称这样简单,但它通过XML属性支持大量的自定义。下面的示例展示了所有这些:
- <com.facebook.drawee.view.SimpleDraweeView
- ohos:id="$+id:drawee"
- ohos:width="20vp"
- ohos:height="20vp"
- fresco:fadeDuration="300"
- fresco:actualImageScaleType="$integer:DraweeScaleType_CENTER"
- fresco:placeholderImage="$color:wait_color"
- fresco:placeholderImageScaleType="$integer:DraweeScaleType_FIT_START"
- fresco:failureImage="$graphic:error"
- fresco:failureImageScaleType="$integer:DraweeScaleType_CENTER_INSIDE"
- fresco:retryImage="$graphic:retrying"
- fresco:retryImageScaleType="$integer:DraweeScaleType_CENTER_CROP"
- fresco:progressBarImage="$graphic:progress_bar"
- fresco:progressBarImageScaleType="$integer:DraweeScaleType_FIT_CENTER"
- fresco:progressBarAutoRotateInterval="1000"
- fresco:backgroundImage="$color:blue"
- fresco:overlayImage="$graphic:watermark"
- fresco:pressedStateOverlayImage="$color:red"
- fresco:roundAsCircle="false"
- fresco:roundedCornerRadius="1dp"
- fresco:roundTopLeft="true"
- fresco:roundTopRight="false"
- fresco:roundBottomLeft="false"
- fresco:roundBottomRight="true"
- fresco:roundTopStart="false"
- fresco:roundTopEnd="false"
- fresco:roundBottomStart="false"
- fresco:roundBottomEnd="false"
- fresco:roundWithOverlayColor="$color:corner_color"
- fresco:roundingBorderWidth="2dp"
- fresco:roundingBorderColor="$color:border_color"
- />
代码定制
尽管通常建议以XML设置这些选项,但也可以从代码中设置以上所有属性。为此,您需要DraweeHierarchy在设置图片URI之前创建一个:
- GenericDraweeHierarchy hierarchy =
- GenericDraweeHierarchyBuilder.newInstance(getResources())
- .setActualImageColorFilter(colorFilter)
- .setActualImageFocusPoint(focusPoint)
- .setActualImageScaleType(scaleType)
- .setBackground(background)
- .setDesiredAspectRatio(desiredAspectRatio)
- .setFadeDuration(fadeDuration)
- .setFailureImage(failureImage)
- .setFailureImageScaleType(scaleType)
- .setOverlays(overlays)
- .setPlaceholderImage(placeholderImage)
- .setPlaceholderImageScaleType(scaleType)
- .setPressedStateOverlay(overlay)
- .setProgressBarImage(progressBarImage)
- .setProgressBarImageScaleType(scaleType)
- .setRetryImage(retryImage)
- .setRetryImageScaleType(scaleType)
- .setRoundingParams(roundingParams)
- .build();
- mSimpleDraweeView.setHierarchy(hierarchy);
- mSimpleDraweeView.setImageURI(uri);
**注意:**其中一些选项可以在现有层次结构上设置,而不必构建新的层次结构。要做到这一点,只需从SimpleDraweeView中获取层次结构,并调用它的任何setter方法,,例如:
- mSimpleDraweeView.getHierarchy().setPlaceHolderImage(placeholderImage);
样例
有关样例
圆角和圆图
每个图像都是矩形。应用程序经常需要以柔和的圆角或圆圈显示的图像。Drawee支持多种方案,所有方案都没有复制位图的内存开销。
圆角
圆角实际有2种呈现方式:
- 圆圈 - 设置roundAsCircle为true
- 圆角 - 设置roundedCornerRadius
矩形支持使四个角中的每一个具有不同的半径,但这必须用Java代码而不是XML指定。
设置
可以使用两种不同的方法对图像进行调整:
- BITMAP_ONLY-使用位图着色器绘制带有圆角的位图。这是默认的舍入方法。它不支持动画,并且不支持centerCrop(默认)focusCrop和以外的任何比例类型fit_xy。如果您将此舍入方法与其他比例类型一起使用,例如center,则不会获得Exception,但图像可能看起来不正确,特别是在源图像小于视图的情况下。
- OVERLAY_COLOR-通过覆盖呼叫者指定的纯色绘制圆角。付款人的背景应为静态且具有相同的纯色。使用roundWithOverlayColorXML格式,或setOverlayColor在代码中使用此方法舍入。
在XML中配置
在SimpleDraweeView中配置RoundingParams的几个属性
- <com.facebook.drawee.view.SimpleDraweeView
- ...
- fresco:roundedCornerRadius="5vp"
- fresco:roundBottomStart="false"
- fresco:roundBottomEnd="false"
- fresco:roundWithOverlayColor="$color:blue"
- fresco:roundingBorderWidth="1vp"
- fresco:roundingBorderColor="$color:red"
- >
在代码中配置
在构建层次结构时,您可以将RoundingParams的实例传递给您的GenericDraweeHierarchyBuilder:
- RoundingParams roundingParams = RoundingParams.fromCornersRadius(7f);
- mSimpleDraweeView.setHierarchy(new GenericDraweeHierarchyBuilder(getResources())
- .setRoundingParams(roundingParams)
- .build());
构建层次结构后,您还可以更改所有舍入参数:
- int color = getResources().getColor(R.color.red);
- RoundingParams roundingParams = RoundingParams.fromCornersRadius(5f);
- roundingParams.setBorder(color, 1.0f);
- roundingParams.setRoundAsCircle(true);
- mSimpleDraweeView.getHierarchy().setRoundingParams(roundingParams);
样例
有关完整示例,请参见DraweeRoundedCornersFragment展示应用程序中的:DraweeRoundedCornersFragment.java
进度条
在应用程序中设置进度条的最简单方法是在构建层次结构时使用ProgressBarDrawable类:
- .setProgressBarImage(new ProgressBarDrawable())
这进度条将显示为沿着Drawee底部的深蓝色矩形。
自定义进度栏
如果要自定义进度指示器,请注意,为了使其在加载时准确反映进度,需要重写Drawable.onLevelChange方法:
- class CustomProgressBar extends Drawable {
- @Override
- protected boolean onLevelChange(int level) {
- // level is on a scale of 0-10,000
- // where 10,000 means fully downloaded
- // your app's logic to change the drawable's
- // appearance here based on progress
- }
- }
样例
Fresco展示应用程序具有DraweeHierarchyFragment,可使用可绘制的进度条进行演示:
缩放类型
您可以为Drawee中的每个不同的可绘制对象指定不同的缩放类型。
可用缩放类型
这些与Image类所支持的基本相同。但是目前传的是int数值
如何设置缩放类型
实际,占位符,重试和失败图像的ScaleType都可以使用XML之类的属性进行设置fresco:actualImageScaleType。您也可以使用GenericDraweeHierarchyBuilder类在代码中进行设置。
即使在构建层次结构之后,也可以使用GenericDraweeHierarchy即时修改实际的图像比例类型 。
但是,千万不能使用ohos:scaleType属性,也没有.setScaleType方法。这些对Drawees没有影响。
缩放类型:“ focusCrop”
Fresco提供centerCrop比例尺类型,该类型将填充整个查看区域,同时保留图像的长宽比,并在必要时进行裁剪。
这非常有用,但是麻烦的是,裁剪并不总是在您需要的地方进行。例如,如果您想要在图像右下角裁剪到某人的脸,那centerCrop将做错事情。
通过指定焦点,可以说出图像的哪一部分应在视图中居中。如果将焦点指定在图像的顶部,例如(0.5f,0f),则无论如何,我们保证该焦点将在视图中尽可能地可见并居中。
对焦点在相对坐标系中指定。即,(0f,0f)是左上角,(1f,1f)是右下角。相对坐标允许焦点保持不变,这非常有用。
焦点(0.5f,0.5f)等效于 centerCrop.
要使用对焦点,必须首先在XML中设置正确的比例类型:
- 1 fresco:actualImageScaleType="$integer:DraweeScaleType_FOCUS_CROP"
在Java代码中,您必须以编程方式为图像设置正确的焦点:
- Point focusPoint = new Point(0f, 0.5f);
- mSimpleDraweeView.getHierarchy().setActualImageFocusPoint(focusPoint);
ScaleType:“无”
如果使用的可绘制对象利用了平铺模式,则需要使用none比例类型才能使其正常工作。
缩放类型:自定义缩放类型
有时,您需要以现有缩放比例类型都无法做到的方式缩放图像。Drawee允许您通过实现自己的功能轻松地做到这一点ScalingUtils.ScaleType。该接口中只有一种方法getTransform,该方法应基于以下条件来计算转换矩阵:
- 父边界(应在视图坐标系中放置图像的矩形)
- 子大小(实际位图的宽度和高度)
- 焦点(儿童坐标系中的相对坐标)
当然,您的类可以包含计算转换可能需要的任何其他数据。
让我们来看一个例子。假设parentBoundsare(100, 150, 500, 450)和子维度为(420,210)。观察到父级宽度为500 - 100 = 400,高度为450 - 150 = 300。如果我们不进行任何转换(即,将转换设置为单位矩阵),则将在中绘制图像(0, 0, 420, 210)。但是ScaleTypeDrawable必须尊重父母施加的范围,这样才能将画布剪裁到(100, 150, 500, 450)。这意味着实际上仅可见图像的右下部分:(100, 150, 420, 210)。
我们可以通过翻译by来解决此问题,(parentBounds.left, parentBounds.top)在这种情况下,就是(100, 150)。但是现在图像的右侧部分被裁剪了,因为图像实际上比父边界更宽!现在,图像放置在(100, 150, 500, 360)视图坐标中,或等效地(0, 0, 400, 210)放置在子坐标中。我们丢失20了右边的像素。
为了避免图像被裁剪,我们可以缩小图像。在这里我们可以缩放以400/420使图像具有一定的大小(400,200)。现在,图像完全适合水平视图,但不在垂直方向上居中。
为了使图像居中,我们需要对其进行更多转换。我们可以看到父边界中的空白空间是400 - 400 = 0水平的和300 - 200 = 100垂直的。如果我们平移此空白空间的一半,我们将在每一侧保留相等数量的空白空间,从而有效地使图像居中于父边界。
恭喜你!您刚刚实现了FIT_CENTER比例类型:
- public class AbstractScaleType implements ScaleType {
- @Override
- public Matrix getTransform(Matrix outTransform, Rect parentRect, int childWidth, int childHeight, float focusX, float focusY) {
- // calculate scale; we take the smaller of the horizontal and vertical scale factor so that the image always fits
- final float scaleX = (float) parentRect.width() / (float) childWidth;
- final float scaleY = (float) parentRect.height() / (float) childHeight;
- final float scale = Math.min(scaleX, scaleY);
- // calculate translation; we offset by parent bounds, and by half of the empty space
- // note that the child dimensions need to be adjusted by the scale factor
- final float dx = parentRect.left + (parentRect.width() - childWidth * scale) * 0.5f;
- final float dy = parentRect.top + (parentRect.height() - childHeight * scale) * 0.5f;
- // finally, set and return the transform
- outTransform.setScale(scale, scale);
- outTransform.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
- return outTransform;
- }
- }
样例
部分样例截图:
占位符,失败和重试图像
当您加载网络图片时,可能会出错,需要很长时间,或者某些图片甚至根本不可用。我们已经看到了如何显示进度条。在此页面上,我们查看了在SimpleDraweeView实际图像不可用时(或者甚至完全没有)可以显示的其他内容。请注意,所有这些都可以具有不同的缩放类型,您可以对其进行自定义。
占位符图像
从设置URI或控制器之前开始显示占位符图像,直到完成加载为止(成功与否)。
XML格式
- <com.facebook.drawee.view.SimpleDraweeView
- ohos:id="$+id:my_image_view"
- ohos:width="20vp"
- ohos:height="20vp"
- fresco:placeholderImage="$graphic:my_placeholder" />
代码
- mSimpleDraweeView.getHierarchy().setPlaceholderImage(placeholderImage);
失败图片
当请求错误地完成时(与网络有关的错误(404,超时)或与图像数据有关的错误(错误的图像,不受支持的格式)),将显示故障图像。
XML格式
- <com.facebook.drawee.view.SimpleDraweeView
- ohos:id="$+id:my_image_view"
- ohos:width="20vp"
- ohos:height="20vp"
- fresco:failureImage="$graphic:my_failure" />
代码
- mSimpleDraweeView.getHierarchy().setFailureImage(failureImage);
重试图片
将显示重试图像,而不是失败图像。当用户点击它时,在显示失败图像之前,该请求最多重试四次。为了使重试图像正常工作,您需要在控制器中启用对它的支持,这意味着像下面这样设置您的图像请求:
- mSimpleDraweeView.setController(Fresco.newDraweeControllerBuilder().setTapToRetryEnabled(true).setUri(uri).build());
XML格式
- <com.facebook.drawee.view.SimpleDraweeView
- ohos:id="$+id:my_image_view"
- ohos:width="20vp"
- ohos:height="20vp"
- fresco:failureImage="$graphic:my_failure_drawable" />
代码
- simpleDraweeView.getHierarchy().setRetryImage(retryImage);
旋转
您可以通过在图像请求中指定旋转角度来旋转图像,如下所示:
- final ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(uri).setRotationOptions(RotationOptions.forceRotation(RotationOptions.ROTATE_90)).build();
- mSimpleDraweeView.setController(Fresco.newDraweeControllerBuilder().setImageRequest(imageRequest).build());
自动旋转
JPEG文件有时在图像元数据中存储方向信息。如果要自动旋转图像以匹配设备的方向,可以在图像请求中执行以下操作:
- final ImageRequest imageRequest = ImageRequestBuilder.newBuilderWithSource(uri).setRotationOptions(RotationOptions.autoRotate()).build();
- mSimpleDraweeView.setController(Fresco.newDraweeControllerBuilder().setImageRequest(imageRequest).build());
组合旋转
如果加载的EXIF数据中包含旋转信息的JPEG文件,则调用forceRotation将添加到图像的默认旋转中。例如,如果EXIF标头指定90度,然后调用forceRotation(ROTATE_90),则原始图像将被旋转180度。
例子
Fresco展示应用程序具有一个DraweeRotationFragment,可演示各种旋转设置。您可以将其与此处的示例图像一起使用。
Resizing
在本节中,我们使用以下术语:
- Scaling是一种画布操作,通常是硬件加速的。位图本身总是相同的大小。它只是按比例缩小或按比例放大绘制的。请参见ScaleType。
- Resizing是在软件中执行的流水线操作。这会在解码之前更改内存中的编码图像。解码后的位图将小于原始图像。
- Downsampling也是以软件实现的流水线操作。与其创建新的编码图像,不如仅解码像素的子集,从而产生较小的输出位图。
Resize
Resize不会修改原始文件,它只是在解码之前调整内存中已编码图像的大小。
如果要resize,请ResizeOptions在构造时传递一个对象ImageRequest:
- ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri).setResizeOptions(new ResizeOptions(50, 50)).build();
- mSimpleDraweeView.setController(Fresco.newDraweeControllerBuilder().setOldController(mSimpleDraweeView.getController()).setImageRequest(request).build());
Resize有一些限制:
- 它仅支持JPEG文件
- 实际调整大小是最接近原始大小的1/8
- 它不能使您的图像变大,只能变小(虽然不是真正的限制)
向下采样(Downsampling)
下采样是Fresco最近新增的一项实验功能。要使用它,必须在配置ImagePipeline时显式启用它:
- .setDownsampleEnabled(true)
如果启用此选项,则图像管线会降低图像的采样率,而不是调整其大小。您仍然必须setResizeOptions如上所述要求每个图像请求。
下采样通常比调整大小更快,因为它是解码步骤的一部分,而不是其自己的单独步骤。它还支持PNG和WebP(动画除外)图像以及JPEG。
您应该在何时使用哪个?
如果图像不大于视图,则仅应进行缩放。它更快,更容易编码,并产生更高质量的输出。当然,小于视图的图像是不大于视图的图像的子集。因此,如果需要放大图像,也应该通过缩放而不是调整大小来完成。这样,不会在不会提供更好质量的更大位图上浪费内存。但是,对于比视图大得多的图像(例如本地摄像机图像),强烈建议除了缩放外还需要调整大小。
至于“更大”的含义,根据经验,如果图像比视图大2倍以上(以像素总数表示,即宽*高),则应调整其大小。这几乎总是适用于相机拍摄的本地图像。例如,屏幕尺寸为1080 x 1920像素(约2MP)的设备和摄像头为16MP的设备所生成的图像是显示屏尺寸的8倍。毫无疑问,在这种情况下调整大小始终是最好的选择。
对于网络图像,请尝试下载尽可能接近要显示的图像。通过下载大小不合适的图像,您在浪费用户的时间和数据。
如果图像大于视图,则不调整其大小会浪费内存。但是,还需要考虑性能折衷。显然,调整大小本身会增加CPU成本。但是,通过不调整比视图更大的图像的大小,需要将更多字节传输到GPU,并且图像从位图缓存中逐出的频率更高,从而导致更多的解码。换句话说,在应调整大小时还增加了CPU成本。因此,没有灵丹妙药,并且取决于设备特性,存在一个阈值点,在此阈值点之后,调整大小比没有调整大小更有效。
支持的URI
Fresco支持在各种位置的图像。Fresco并没有接受相对URI。所有URI必须是绝对的,并且必须包含方案。
以下是接受的URI方案:
注意:图像管道只能使用图像资源(例如PNG图像)。其他资源类型(例如字符串或XML Element)在图像管道的上下文中没有意义,因此无法由定义支持。一种可能引起混淆的情况是XML中声明的element(例如ShapeElement)。需要注意的重要一点是,这不是图像。如果要显示XML可绘制图像作为主图像,则将其设置为占位符并使用nulluri。
缓存
Fresco将图像存储在三种不同类型的缓存中,这些缓存按层次结构进行组织,检索图像的成本增加了您的工作量。
1.PixelMap缓存
位图缓存将解码的图像存储为HarmonyPixelMap对象。这些已经准备好进行显示或后处理。
您的应用在后台运行时应清除此缓存。
对于非静态图像格式或自定义图像格式,位图缓存可以通过扩展CloseableImage类来保存任何解码的图像数据。
2.编码的内存缓存
此缓存以原始压缩格式存储图像。从此缓存中检索的图像必须在显示之前进行解码。
如果要求其他转换,例如Resizing,旋转或转码,则在解码之前进行。
3.本地缓存
像编码的内存缓存一样,此缓存存储压缩的图像,在显示之前必须对其进行解码,有时还需要对其进行转换。
与其他应用程序不同,退出应用程序或关闭设备后不会清除此缓存。
当本地缓存即将达到DiskCacheConfig定义的大小限制时,Fresco使用磁盘缓存中的LRU逐出逻辑(请参见DefaultEntryEvictionComparatorSupplier.java)。
用户当然可以始终从Harmony的“设置”菜单中将其清除。
检查是否在缓存中
您可以使用ImagePipeline中的方法查看项目是否在缓存中。内存缓存的检查是同步的:
- ImagePipeline imagePipeline = Fresco.getImagePipeline();
- Uri uri;
- boolean inMemoryCache = imagePipeline.isInBitmapMemoryCache(uri);
磁盘缓存检查是异步的,因为磁盘检查必须在另一个线程上进行。您可以这样使用它:
- DataSource<Boolean> inDiskCacheSource = imagePipeline.isInDiskCache(uri);
- DataSubscriber<Boolean> subscriber = new BaseDataSubscriber<Boolean>() {
- @Override
- protected void onNewResultImpl(DataSource<Boolean> dataSource) {
- if (!dataSource.isFinished()) {
- return;
- }
- boolean isInCache = dataSource.getResult();
- // your code here
- }
- };
- inDiskCacheSource.subscribe(subscriber, executor);
以上API假设你使用默认的CacheKeyFactory。如果你自定义CacheKeyFactory,你可能需要用把ImageRequest作为它的参数。
清除缓存中的一条url
ImagePipeline现有函数可以根据Uri删除缓存。
- ImagePipeline imagePipeline = Fresco.getImagePipeline();
- Uri uri;
- imagePipeline.evictFromMemoryCache(uri);
- imagePipeline.evictFromDiskCache(uri);
- // combines above two lines
- imagePipeline.evictFromCache(uri);
如上所述,evictFromDiskCache(Uri)假定你使用的是默认的CacheKeyFactory。如果你自定义,请使用evictFromDiskCache(ImageRequest)。
清除缓存
- ImagePipeline imagePipeline = Fresco.getImagePipeline();
- imagePipeline.clearMemoryCaches();
- imagePipeline.clearDiskCaches();
- // combines above two lines
- imagePipeline.clearCaches();
使用一个或两个磁盘缓存?
大多数应用程序只需要一个磁盘缓存。但是在某些情况下,您可能希望将较小的图像保留在单独的缓存中,以防止较大的图像将它们赶出。
为此,只需在配置图像管道时调用setMainDiskCacheConfig和setSmallImageDiskCacheConfig方法。
什么定义*小?*您的应用程序可以。当对影像的要求,设置其CacheChoice:
- ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
- .setCacheChoice(ImageRequest.CacheChoice.SMALL)
如果您只需要一个缓存,则可以避免调用setSmallImageDiskCacheConfig。Image pipeline将默认为两者使用相同的缓存,并且CacheChoice将被忽略。
整理缓存
当配置了图像流水线,可以设置每个缓存的最大尺寸。但是有时候您可能想降低到更低的水平。例如,您的应用程序可能具有其他类型数据的缓存,这些数据可能需要更多空间并挤占了Fresco的空间。或者,您可能正在检查设备整体是否用完了存储空间。
Fresco的缓存实现DiskTrimmable或MemoryTrimmable接口。这些是您的应用程序可以告诉他们进行紧急驱逐的钩子。
然后,您的应用程序可以使用实现DiskTrimmableRegistry和MemoryTrimmableRegistry接口的对象来配置管道。
这些对象必须保留可修剪列表。他们必须使用特定于应用程序的逻辑来确定何时必须保留内存或磁盘空间。然后,它们通知可修剪对象进行修剪。
可关闭引用
大多数应用程序应使用Drawees,而不必担心关闭。
Java语言是垃圾回收的,大多数开发人员习惯于随意创建对象,并且理所当然地将它们最终从内存中消失。
位图是使Java开发人员错过C ++及其许多智能指针库(例如Boost)的一件事。
Fresco的解决方案可在CloseableReference类中找到。为了正确使用它,您必须遵循以下规则:
1.引用者拥有。
在这里,我们创建了一个引用,但是由于我们将其传递给调用方,因此调用方将获得所有权:
- CloseableReference<Val> foo() {
- Val val;
- // We are returning the reference from this method,
- // so whoever is calling this method is the owner
- // of the reference and is in charge of closing it.
- return CloseableReference.of(val);
- }
2.拥有者在退出前关闭。
在这里,我们创建了一个引用,但没有将其传递给调用者。因此,我们必须关闭它:
- void gee() {
- // We are the caller of `foo` and so
- // we own the returned reference.
- CloseableReference<Val> ref = foo();
- try {
- // `haa` is a callee and not a caller, and so
- // it is NOT the owner of this reference, and
- // it must NOT close it.
- haa(ref);
- } finally {
- // We are not returning the reference to the
- // caller of this method, so we are still the owner,
- // and must close it before leaving the scope.
- ref.close();
- }
- }
该finally块几乎总是要做到这一点的最好办法。
3.永远不要关闭该值。
CloseableReference包装一个共享资源,当没有更多活动引用指向该资源时,该资源将被释放。活动参考的跟踪由内部参考计数器自动完成。当引用计数降至0时,CloseableReference将释放基础资源。的主要目的CloseableReference是管理基础资源,因此您不必这样做。就是说,CloseableReference如果您拥有,则您有责任关闭,但不是它所指向的价值!如果显式关闭基础值,则将错误地使指向该资源的所有其他活动引用无效。
- CloseableReference<Val> ref = foo();
- Val val = ref.get();
- // 处理val
- // 绝对不要close val
- //// val.close();
- // 使用CloseableReference来释放val
- ref.close();
4.拥有者以外不应该关闭。
在这里,我们通过参数接收引用。调用方仍然是所有者,因此我们不应该将其关闭。
- void haa(CloseableReference<?> ref) {
- // We are callee, and not a caller, and so
- // we must NOT close the reference.
- // We are guaranteed that the reference won't
- // become invalid for the duration of this call.
- HiLog.info(hiLogLabel,"Haa Async: %{public}s" + ref.get());
- }
如果我们.close()在这里错误地呼叫.get(),则如果呼叫者试图呼叫,IllegalStateException则会抛出一个。
5.在赋值给变量前,先进行clone。
如果需要保留引用,则需要克隆它。
如果在类中使用它:
- class MyClass {
- CloseableReference<Val> myValRef;
- void mmm(CloseableReference<Val> ref) {
- myValRef = ref.clone();
- };
- void close() {
- // MyClass的调用者需要关闭myValRef
- CloseableReference.closeSafely(myValRef);
- }
- }
如果在内部中使用它:
- void haa(CloseableReference<?> ref) {
- final CloseableReference<?> refClone = ref.clone();
- executor.submit(new Runnable() {
- public void run() {
- try {
- HiLog.info(hiLogLabel,"Haa Async: %{public}s" + refClone.get());
- } finally {
- refClone.close();
- }
- }
- });
- // 当前函数域内可安全关闭,闭包内为已经clone过的引用。
- }
配置ImagePipeline
大多数应用程序可以通过简单的命令完全初始化Fresco:
- Fresco.initialize(context);
对于需要更高级自定义的应用程序,我们使用ImagePipelineConfig类提供它。
这是一个最大的例子。真正需要所有这些设置的应用程序很少,但是在此仅供参考。
- ImagePipelineConfig config = ImagePipelineConfig.newBuilder(context)
- .setBitmapMemoryCacheParamsSupplier(bitmapCacheParamsSupplier)
- .setCacheKeyFactory(cacheKeyFactory)
- .setDownsampleEnabled(true)
- .setWebpSupportEnabled(true)
- .setEncodedMemoryCacheParamsSupplier(encodedCacheParamsSupplier)
- .setExecutorSupplier(executorSupplier)
- .setImageCacheStatsTracker(imageCacheStatsTracker)
- .setMainDiskCacheConfig(mainDiskCacheConfig)
- .setMemoryTrimmableRegistry(memoryTrimmableRegistry)
- .setNetworkFetchProducer(networkFetchProducer)
- .setPoolFactory(poolFactory)
- .setProgressiveJpegConfig(progressiveJpegConfig)
- .setRequestListeners(requestListeners)
- .setSmallImageDiskCacheConfig(smallImageDiskCacheConfig)
- .build();
- Fresco.initialize(context, config);
确保将您的ImagePipelineConfig对象传递给Fresco.initialize!否则,Fresco将使用默认配置,而不是您构建的默认配置。
关于Suppliers
配置构建器的几种方法采用实例的Suppliers而不是实例本身的参数。创建起来有点复杂,但是允许您在应用运行时更改行为。内存缓存之一,每五分钟检查一次其供应商。
如果不需要动态更改参数,请使用每次都会返回相同对象的Supplier:
- Supplier<X> xSupplier = new Supplier<X>() {
- private X mX = new X(xparam1, xparam2...);
- public X get() {
- return mX;
- }
- );
- // when creating image pipeline
- .setXSupplier(xSupplier);
线程池
默认情况下,ImagePipeline使用三个线程池:
- 网络下载的三个线程
- 所有磁盘操作都有两个线程-本地文件读取和磁盘缓存
- 所有CPU绑定操作都有两个线程-解码,转换和后台操作,例如后处理。
您可以通过设置自己的网络层来自定义网络行为。
要更改所有其他操作的行为,请传入ExecutorSupplier的实例。
内存策略
如果您的应用程序侦听系统内存事件,则可以将其传递给Fresco来修剪内存缓存。
大多数应用程序侦听事件的最简单方法是覆盖Ability.onMemoryLevel。您还可以使用ElementsCallback任何子类。
您应该具有MemoryTrimmableRegistry的实现。该对象应保留MemoryTrimmable对象的集合-Fresco的缓存将在其中。发生系统内存事件时,可以MemoryTrimmable在每个可修剪对象上调用适当的方法。
配置内存缓存
位图缓存和编码的内存缓存由MemoryCacheParams#MemoryCacheParams\(int, int, int, int, int\))对象的供应商配置。
配置磁盘缓存
您使用构建器模式创建DiskCacheConfig对象:
- DiskCacheConfig diskCacheConfig = DiskCacheConfig.newBuilder()
- .set....
- .set....
- .build()
- // when building ImagePipelineConfig
- .setMainDiskCacheConfig(diskCacheConfig)
保持缓存状态
如果要跟踪诸如高速缓存命中率之类的指标,则可以实现ImageCacheStatsTracker类。这为每个可用于保留自己的统计信息的缓存事件提供了回调。
数据源和数据订阅(DataSources and DataSubscribers)
DataSource就像Java的Future,都是异步计算的结果。不同点在于,DataSources对于一个调用会返回一系列结果,Future只返回一个。
提交图像请求后,图像管道将返回数据源。要获得结果,您需要使用DataSubscriber。
执行器(Executors)
订阅数据源时,必须提供执行程序。执行程序的目的是在特定线程和特定策略上执行可运行对象(在我们的情况下为订户回调方法)。Fresco提供了几位执行者,其中一位应仔细选择要使用的执行者:
- 如果您需要从回调中执行任何UI内容(访问视图,可绘制对象等),则必须使用UiThreadImmediateExecutorService.getInstance()。Android视图系统不是线程安全的,只能从主线程(UI线程)进行访问。
- 如果回调是轻量级的,并且不执行任何与UI相关的工作,则可以简单地使用CallerThreadExecutor.getInstance()。该执行程序在调用者的线程上执行可运行对象。根据调用线程的不同,回调可以在UI或后台线程上执行。无法保证它将成为哪个线程,因此,应非常谨慎地使用该执行程序。同样,仅适用于与UI无关的轻量级内容。
- 如果您需要执行一些昂贵的与UI无关的工作(数据库访问,磁盘读/写或任何其他慢速操作),则不应使用,CallerThreadExecutor也不要使用UiThreadExecutorService,而应使用后台线程执行程序之一来执行此操作。有关示例实现,请参见DefaultExecutorSupplier.forBackgroundTasks。
从数据源获取结果
这是一个通用示例,说明如何从CloseableReference
- DataSource<CloseableReference<T>> dataSource = ...;
- DataSubscriber<CloseableReference<T>> dataSubscriber =
- new BaseDataSubscriber<CloseableReference<T>>() {
- @Override
- protected void onNewResultImpl(
- DataSource<CloseableReference<T>> dataSource) {
- if (!dataSource.isFinished()) {
- // if we are not interested in the intermediate images,
- // we can just return here.
- return;
- }
- CloseableReference<T> ref = dataSource.getResult();
- if (ref != null) {
- try {
- // do something with the result
- T result = ref.get();
- ...
- } finally {
- CloseableReference.closeSafely(ref);
- }
- }
- }
- @Override
- protected void onFailureImpl(DataSource<CloseableReference<T>> dataSource) {
- Throwable t = dataSource.getFailureCause();
- // handle failure
- }
- };
- dataSource.subscribe(dataSubscriber, executor);
从数据源获取结果
上面的示例在执行回调后立即关闭引用。如果需要保留结果,则必须在需要结果的时间内保存相应CloseableReference的内容。可以按照以下步骤进行操作:
- DataSource<CloseableReference<T>> dataSource = ...;
- DataSubscriber<CloseableReference<T>> dataSubscriber =
- new BaseDataSubscriber<CloseableReference<T>>() {
- @Override
- protected void onNewResultImpl(
- DataSource<CloseableReference<T>> dataSource) {
- if (!dataSource.isFinished()) {
- // if we are not interested in the intermediate images,
- // we can just return here.
- return;
- }
- // keep the closeable reference
- mRef = dataSource.getResult();
- // do something with the result
- T result = mRef.get();
- ...
- }
- @Override
- protected void onFailureImpl(DataSource<CloseableReference<T>> dataSource) {
- Throwable t = dataSource.getFailureCause();
- // handle failure
- }
- };
- dataSource.subscribe(dataSubscriber, executor);
重要提示:一旦不再需要结果,则必须关闭引用。否则可能会导致内存泄漏。有关更多详细信息,请参见可关闭的参考。
- CloseableReference.closeSafely(mRef);
- mRef = null;
但是,如果您正在使用,则BaseDataSubscriber不必手动关闭dataSource(关闭mRef就足够了)。BaseDataSubscriber会dataSource在onNewResultImpl调用后自动为您关闭。如果您没有使用BaseDataSubscriber(例如,如果您正在呼叫dataSource.getResult()),请确保也将其关闭dataSource。
要获取编码的图像…
- DataSource<CloseableReference<PooledByteBuffer>> dataSource =
- mImagePipeline.fetchEncodedImage(imageRequest, CALLER_CONTEXT);
图像管线PooledByteBuffer用于编码图像。这是我们T上面的例子。这是创建InputStreamout的示例,PooledByteBuffer以便我们可以读取图像字节:
- InputStream is = new PooledByteBufferInputStream(result);
- try {
- // Example: get the image format
- ImageFormat imageFormat = ImageFormatChecker.getImageFormat(is);
- // Example: write input stream to a file
- Files.copy(is, path);
- } catch (...) {
- ...
- } finally {
- Closeables.closeQuietly(is);
- }
要获取解码的图像…
- DataSource<CloseableReference<CloseableImage>>
- dataSource = imagePipeline.fetchDecodedImage(imageRequest, callerContext);
图像流水线CloseableImage用于解码图像。这是我们T上面的例子。这是一个Bitmap摆脱困境的例子CloseableImage:
- CloseableImage image = ref.get();
- if (image instanceof CloseableBitmap) {
- // do something with the bitmap
- PixelMap pixelMap = (CloseableBitmap image).getUnderlyingBitmap();
- ...
- }
只想要一个PixelMap
如果您对管道的请求是针对单个PixelMap的,则可以利用我们易于使用的BaseBitmapDataSubscriber:
- dataSource.subscribe(new BaseBitmapDataSubscriber() {
- @Override
- public void onNewResultImpl(@Nullable Bitmap bitmap) {
- // You can use the bitmap here, but in limited ways.
- // No need to do any cleanup.
- }
- @Override
- public void onFailureImpl(DataSource dataSource) {
- // No cleanup required here.
- }
- },
- executor);
警告。
该订阅服务器不适用于动画图像,因为这些图像不能表示为单个位图。
您不能将位图分配给onNewResultImpl方法范围以外的任何变量。如上面示例中所解释的,原因是在订户完成执行之后,图像管道将回收位图并释放其内存。如果之后尝试绘制位图,则您的应用将崩溃并显示IllegalStateException.
您仍然可以安全地将位图传递到Harmony通知和远程视图。如果Harmony需要您的位图以便将其传递到系统进程,则会在ashmem中复制位图数据(与Fresco使用的堆相同)。因此,Fresco的自动清理将毫无问题地工作。
如果这些要求使您无法使用BaseBitmapDataSubscriber,则可以采用上述更通用的方法。
图片请求(Image Requests)
如果您需要ImageRequest仅包含URI的,则可以使用helper方法ImageRequest.fromURI。加载多个图像是这种情况的常见情况。
如果除了简单的URI外,还需要告诉图像管道更多的信息,则需要使用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中的字段
- uri-唯一的必填字段。请参阅支持的URI。
- autoRotateEnabled-是否启用自动旋转。
- progressiveEnabled-是否启用渐进式加载。
- postprocessor-对解码图像进行后处理的组件。
- resizeOptions-所需的宽度和高度。请谨慎使用。请参阅Resizing。
最低允许的请求级别
图像流水线在其中查找图像时遵循确定的顺序。
- 检查位图缓存。这几乎是瞬间。如果找到,请返回。
- 检查编码的内存缓存。如果找到,则解码图像并返回。
- 检查“磁盘”(本地存储)缓存。如果找到,则从磁盘加载,解码并返回。
- 转到网络上的原始文件或本地文件。根据要求下载,调整大小和/或旋转,解码和返回。特别是对于网络映像,从长远来看,这将是最慢的。
该setLowestPermittedRequestLevel字段使您可以控制管道将在此列表中进行多下的操作。可能的值为:
- BITMAP_MEMORY_CACHE
- ENCODED_MEMORY_CACHE
- DISK_CACHE
- FULL_FETCH
在您需要即时或至少相对较快的图像或根本不需要图像的情况下,这很有用。
监听事件
动机
Fresco中的 image pipeline和Component控制器具有内置的监听接口。可以使用它来跟踪性能和对事件做出反应。
Fresco带有两个主要的事件接口:
- 会在RequestListener中全局注册,ImagePipelineConfig并记录由生产者-消费者链处理的所有请求
- 会ControllerListener被添加到个人DraweeView,便于对诸如“此图片已完全加载”之类的事件做出反应
ControllerListener
虽然RequestListener是全局侦听器,但ControllerListener对于某些则是本地的DraweeView。这是对显示的视图做出更改(例如“图像无法加载”或“图像已完全加载”)做出反应的好方法。同样,最好BaseControllerListener对此进行扩展。
一个简单的监听器可能如下所示:
- public class MyControllerListener extends new BaseControllerListener<ImageInfo>() {
- @Override
- public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
- HiLog.info(hiLogLabel,"DraweeUpdate Image is fully loaded!");
- }
- @Override
- public void onIntermediateImageSet(String id, ImageInfo imageInfo, Animatable animatable) {
- HiLog.info(hiLogLabel,"DraweeUpdate Image is partly loaded! (maybe it's a progressive JPEG?)");
- if (imageInfo != null) {
- int quality = imageInfo.getQualityInfo().getQuality();
- HiLog.info(hiLogLabel,"DraweeUpdate Image quality (number scans) is: %{public}s",quality);
- }
- }
- @Override
- public void onFailure(String id, Throwable throwable) {
- HiLog.info(hiLogLabel,"DraweeUpdate Image failed to load: %{public}s",throwable.getMessage());
- }
- }
您可以DraweeController通过以下方式将其添加到自己的文件中:
- DraweeController controller = Fresco.newDraweeControllerBuilder()
- .setImageRequest(request)
- .setControllerListener(new MyControllerListener())
- .build();
- mSimpleDraweeView.setController(controller);
RequestListener
该RequestListener自带的回调方法大量接口。最重要的是,您会注意到它们都提供了独特的功能requestId,从而可以跨多个阶段跟踪请求。
由于有大量的回调,建议从扩展BaseRequestListener而不是扩展,而仅实现您感兴趣的方法。您可以按以下方式在Application类中注册侦听器:
- final Set<RequestListener> listeners = new HashSet<>();
- listeners.add(new MyRequestLoggingListener());
- ImagePipelineConfig imagePipelineConfig = ImagePipelineConfig.newBuilder(this)
- .setRequestListeners(listeners)
- .build();
- Fresco.initialize(this, imagePipelineConfig);
我们将逐步浏览展示应用程序生成的一个图像请求的日志记录,并讨论各个含义。在hilog运行展示应用程序时,您可以自己观察以下内容:
- RequestLoggingListener: time 2095589: onRequestSubmit: {requestId: 5, callerContext: null, isPrefetch: false}
onRequestSubmit(...)当anImageRequest进入图像管道时被调用。在这里,您可以利用调用方上下文对象来识别应用程序的哪个功能正在发送请求。
- RequestLoggingListener: time 2095590: onProducerStart: {requestId: 5, producer: BitmapMemoryCacheGetProducer}
- RequestLoggingListener: time 2095591: onProducerFinishWithSuccess: {requestId: 5, producer: BitmapMemoryCacheGetProducer, elapsedTime: 1 ms, extraMap: {cached_value_found=false}}
该onProducerStart(...)和onProducerFinishWithSuccess(...)(或onProducerFinishWithFailure(...))被称为沿着管道中的所有生产者。上面的一个是对位图缓存的检查。
- RequestLoggingListener: time 2095592: onProducerStart: {requestId: 5, producer: BackgroundThreadHandoffProducer}
- RequestLoggingListener: time 2095593: onProducerFinishWithSuccess: {requestId: 5, producer: BackgroundThreadHandoffProducer, elapsedTime: 1 ms, extraMap: null}
- RequestLoggingListener: time 2095594: onProducerStart: {requestId: 5, producer: BitmapMemoryCacheProducer}
- RequestLoggingListener: time 2095594: onProducerFinishWithSuccess: {requestId: 5, producer: BitmapMemoryCacheProducer, elapsedTime: 0 ms, extraMap: {cached_value_found=false}}
- RequestLoggingListener: time 2095595: onProducerStart: {requestId: 5, producer: EncodedMemoryCacheProducer}
- RequestLoggingListener: time 2095596: onProducerFinishWithSuccess: {requestId: 5, producer: EncodedMemoryCacheProducer, elapsedTime: 1 ms, extraMap: {cached_value_found=false}}
- RequestLoggingListener: time 2095596: onProducerStart: {requestId: 5, producer: DiskCacheProducer}
- RequestLoggingListener: time 2095598: onProducerFinishWithSuccess: {requestId: 5, producer: DiskCacheProducer, elapsedTime: 2 ms, extraMap: {cached_value_found=false}}
- RequestLoggingListener: time 2095598: onProducerStart: {requestId: 5, producer: PartialDiskCacheProducer}
- RequestLoggingListener: time 2095602: onProducerFinishWithSuccess: {requestId: 5, producer: PartialDiskCacheProducer, elapsedTime: 4 ms, extraMap: {cached_value_found=false}}
当请求移交给后台(BackgroundThreadHandoffProducer)并在缓存中执行查找时,我们会看到更多这些内容。
- RequestLoggingListener: time 2095602: onProducerStart: {requestId: 5, producer: NetworkFetchProducer}
- RequestLoggingListener: time 2095745: onProducerEvent: {requestId: 5, stage: NetworkFetchProducer, eventName: intermediate_result; elapsedTime: 143 ms}
- RequestLoggingListener: time 2095764: onProducerFinishWithSuccess: {requestId: 5, producer: NetworkFetchProducer, elapsedTime: 162 ms, extraMap: {queue_time=140, total_time=161, image_size=40502, fetch_time=21}}
- RequestLoggingListener: time 2095764: onUltimateProducerReached: {requestId: 5, producer: NetworkFetchProducer, elapsedTime: -1 ms, success: true}
对于此特定请求,它NetworkFetchProducer是“最终生产者”。这就是说,它是为满足请求提供确定的输入源的人。如果图像被缓存,DiskCacheProducer则将是“最终”生产者。
- RequestLoggingListener: time 2095766: onProducerStart: {requestId: 5, producer: DecodeProducer}
- RequestLoggingListener: time 2095786: onProducerFinishWithSuccess: {requestId: 5, producer: DecodeProducer, elapsedTime: 20 ms, extraMap: {imageFormat=JPEG, ,hasGoodQuality=true, bitmapSize=788x525, isFinal=true, requestedImageSize=unknown, encodedImageSize=788x525, sampleSize=1, queueTime=0}
- RequestLoggingListener: time 2095788: onRequestSuccess: {requestId: 5, elapsedTime: 198 ms}
在上升的过程中,DecodeProducer也会成功,最后onRequestSuccess(...)调用该方法。
您会注意到,大多数方法都以形式提供了可选信息Map
预取图像
在显示图像之前预取图像有时会缩短用户的等待时间。但是请记住,需要权衡取舍。预取会占用用户的数据,并耗尽其CPU和内存的份额。通常,不建议对大多数应用程序进行预取。
但是,图像管道允许您预取到磁盘或位图缓存。两者都将为网络URI使用更多数据,但是磁盘缓存将不进行解码,因此将使用更少的CPU。
**注意:**请注意,如果您的网络提取程序不支持优先级,则预提取请求可能会减慢屏幕上立即需要的图像的速度。无论是OkHttpNetworkFetcher也HttpUrlConnectionNetworkFetcher目前的支持重点。
预取到磁盘:
- imagePipeline.prefetchToDiskCache(imageRequest, callerContext);
预取到位图缓存:
- imagePipeline.prefetchToBitmapCache(imageRequest, callerContext);
取消预取:
- // keep the reference to the returned data source.
- DataSource<Void> prefetchDataSource = imagePipeline.prefetchTo...;
- // later on, if/when you need to cancel the prefetch:
- prefetchDataSource.close();
在预取已经完成之后关闭预取数据源是无操作且完全安全的。
修改图像(后处理)
动机
后处理器允许对获取的图像进行自定义修改。在大多数情况下,由于移动设备的资源通常更加有限,因此在将图像发送给客户端之前,服务器应该已经完成了图像处理。但是,在许多情况下,客户端处理是有效的选择。例如,如果图像由您控制的第三方提供,或者图像是本地的(在设备上)。
背景
在Fresco的流水线中,当图像已经被解码为位图并且原始版本存储在内存中的位图缓存中时,最后才应用后处理器。虽然后处理器可以直接在提供的位图上工作,但它也可以创建具有不同尺寸的新位图。
理想情况下,已实现的后处理器应为给定参数提供一个缓存键。这样,新生成的位图也将缓存在内存中的位图缓存中,不需要重新创建。
所有后处理器均使用后台执行程序执行。但是,幼稚的迭代或复杂的计算仍会花费很长时间,应该避免。如果您要进行像素数量非线性的计算,则有一部分包含一些技巧,可帮助您使用本机代码加快后处理器的速度。
示例:创建灰度过滤器
让我们从简单的事情开始:将位图转换为灰度版本的后处理器。为此,我们需要遍历位图的像素并替换其颜色值。
图像在进入后处理器之前被复制。在高速缓存中的原始图像不会影响你在后处理器进行任何更改。
该BasePostprocessor希望我们的子类重写它的一个BasePostprocessor#process方法。最简单的方法是对提供的位图进行就地修改。在此,图像在进入后处理器之前被复制。因此,图像的缓存原来是不是受你的后处理器进行任何更改。稍后我们将讨论如何修改输出位图的配置和大小。
- public class FastGreyScalePostprocessor extends BasePostprocessor {
- @Override
- public void process(Bitmap bitmap) {
- final int w = bitmap.getWidth();
- final int h = bitmap.getHeight();
- final int[] pixels = new int[w * h];
- bitmap.getPixels(pixels, 0, w, 0, 0, w, h);
- for (int x = 0; x < w; x++) {
- for (int y = 0; y < h; y++) {
- final int offset = y * w + x;
- pixels[offset] = getGreyColor(pixels[offset]);
- }
- }
- // this is much faster then calling #getPixel and #setPixel as it crosses
- // the JNI barrier only once
- bitmap.setPixels(pixels, 0, w, 0, 0, w, h);
- }
- static int getGreyColor(int color) {
- final int alpha = color & 0xFF000000;
- final int r = (color >> 16) & 0xFF;
- final int g = (color >> 8) & 0xFF;
- final int b = color & 0xFF;
- // see: https://en.wikipedia.org/wiki/Relative_luminance
- final int luminance = (int) (0.2126 * r + 0.7152 * g + 0.0722 * b);
- return alpha | luminance << 16 | luminance << 8 | luminance;
- }
- }
请求多张图片(多URI)
需要设置您自己的图像请求。
从低分辨率到高分辨率
假设您要向用户显示高分辨率,下载缓慢的图像。您可能不希望让他们凝视一会儿占位符,而是要先快速下载一个较小的缩略图。
您可以设置两个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元数据中存储了缩略图,则图像管道可以将其作为中间结果返回。Drawee将首先显示缩略图预览,然后在完成加载和解码后显示完整图像。
- 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。例如,您可能已经上传了从相机拍摄的图像。原始图像太大,无法上载,因此图像首先会缩小。在这种情况下,首先尝试获取local-downscaled-uri,然后失败,尝试获取local-original-uri,甚至失败,尝试获取网络上载的uri,将是有益的。 。下载我们可能已经在本地下载的图像将是一个耻辱。
映像管道通常首先在内存缓存中搜索图像,然后在磁盘缓存中搜索,然后才搜索到网络或其他来源。不必为每个映像一个接一个地执行此操作,我们可以通过管道检查内存缓存中的所有映像。仅在未找到磁盘高速缓存的情况下,才进行搜索。只有在未找到磁盘高速缓存的情况下,才会发出外部请求。
只需创建一个图像请求数组,然后将其传递给构建器即可。
- 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);
仅显示其中一个请求。找到的第一个,无论是在内存,磁盘还是网络级别,都将返回。管道将假定数组中的请求顺序为首选项顺序。
指定DataSource Supplier
为了获得更大的灵活性,可以DataSource Supplier在构建Drawee控制器时指定自定义。您可以实施自己的供应商,也可以按照自己喜欢的方式组合现有的供应商。请参阅 FirstAvailableDataSourceSupplier和IncreasingQualityDataSourceSupplier以获取示例实现。了解AbstractDraweeControllerBuilder如何将这些供应商组合在一起。
使用ControllerBuilder
- SimpleDraweeView`有两种指定图像的方法。简单的方法是使用`setImageURI.
如果要对Drawee显示图像的方式进行更多控制,可以使用DraweeController。本页说明了如何构建和使用它。
建立一个DraweeController
将uri传递给PipelineDraweeControllerBuilder。然后为控制器指定其他选项:
- ControllerListener listener = new BaseControllerListener() {...}
- DraweeController controller = Fresco.newDraweeControllerBuilder()
- .setUri(uri)
- .setTapToRetryEnabled(true)
- .setOldController(mSimpleDraweeView.getController())
- .setControllerListener(listener)
- .build();
- mSimpleDraweeView.setController(controller);
setOldController构建新的控制器时应致电。这将允许重新使用旧的控制器,并避免了一些不必要的内存分配。
更多细节:
- 监听事件
自定义ImageRequest
为了获得更高级的用法,您可能需要将ImageRequest设置为管道,而不仅仅是URI。一个例子是使用后处理器。
- Uri uri;
- Postprocessor myPostprocessor = new Postprocessor() { ... }
- ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
- .setPostprocessor(myPostprocessor)
- .build();
- DraweeController controller = Fresco.newDraweeControllerBuilder()
- .setImageRequest(request)
- .setOldController(mSimpleDraweeView.getController())
- // other setters as you need
- .build();
直接使用ImagePipeline
此页面仅供高级使用。大多数应用程序应使用Drawees与Fresco的图像管道进行交互。
由于内存使用情况,直接使用图像管道是一项挑战。抽纸器会自动跟踪是否需要将图像保存在内存中。他们将交换它们,并在需要显示时立即将其加载回。如果直接使用图像管道,则您的应用程序必须重复此逻辑。
图像管道返回包装在CloseableReference中的对象。当这些引用需要它们的图像时,它们将保持有效,然后.close()在完成引用后在这些引用上调用该方法。如果您的应用程序未使用Drawees,则必须执行相同的操作。
如果您不保留CloseableReference对pipleine返回的Java引用,CloseableReference则将收集垃圾并将底层Bitmap回收,而仍在使用它们。如果不关闭就关闭CloseableReference,则存在内存泄漏和OOM的风险。
确切地说,当位图对象超出范围时,Java垃圾收集器将释放图像内存,但这可能为时已晚。垃圾收集非常昂贵,并且依赖于它处理大型对象会导致性能问题。当Android未为位图维护单独的内存空间时,在Android 4.x及更低版本上尤其如此。
调用ImagePipeline
您必须建立一个图像请求。完成此操作后,您可以将其直接传递给ImagePipeline:
- ImagePipeline imagePipeline = Fresco.getImagePipeline();
- DataSource<CloseableReference<CloseableImage>>
- dataSource = imagePipeline.fetchDecodedImage(imageRequest, callerContext);
有关如何从数据源接收数据的信息,请参见DataSources 上的页面。
跳过解码
如果您不想解码图像,但想以其原始压缩格式获取图像字节,请fetchEncodedImage改用:
- DataSource<CloseableReference<PooledByteBuffer>>
- dataSource = imagePipeline.fetchEncodedImage(imageRequest, callerContext);
PixelMap缓存的即时结果
与其他PixelMap缓存不同,对PixelMap缓存的查找是在UI线程中完成的。如果有PixelMap,您将立即获得它。
- DataSource<CloseableReference<CloseableImage>> dataSource =
- imagePipeline.fetchImageFromBitmapCache(imageRequest, callerContext);
- try {
- CloseableReference<CloseableImage> imageReference = dataSource.getResult();
- if (imageReference != null) {
- try {
- // Do something with the image, but do not keep the reference to it!
- // The image may get recycled as soon as the reference gets closed below.
- // If you need to keep a reference to the image, read the following sections.
- } finally {
- CloseableReference.closeSafely(imageReference);
- }
- } else {
- // cache miss
- ...
- }
- } finally {
- dataSource.close();
- }
图片同步加载
与立即从位图缓存中检索图像的方式类似,也可以使用同步从网络加载图像DataSources.waitForFinalResult()。
- DataSource<CloseableReference<CloseableImage>> dataSource =
- imagePipeline.fetchImageFromBitmapCache(imageRequest, callerContext);
- try {
- CloseableReference<CloseableImage> imageReference = dataSource.getResult();
- if (imageReference != null) {
- try {
- // Do something with the image, but do not keep the reference to it!
- // The image may get recycled as soon as the reference gets closed below.
- // If you need to keep a reference to the image, read the following sections.
- } finally {
- CloseableReference.closeSafely(imageReference);
- }
- } else {
- // cache miss
- ...
- }
- } finally {
- dataSource.close();
- }
千万不能跳过这些finally块!
调用Context
如我们所见,大多数ImagePipeline获取方法都包含一个名为callerContexttype的第二个参数Object。我们可以将其视为上下文对象设计模式的一种实现。基本上,这是一个我们绑定到特定对象的对象ImageRequest,可以用于不同目的(例如Logger)。所有Producer实现还可以访问相同的对象ImagePipeline。
调用者上下文也可以是null。
使用其他网络层
默认情况下,图像管道使用Java框架中包含的HttpURLConnection。但是,如果应用程序需要,则可以使用自定义网络层。Fresco已经包含一个基于OkHttp的替代网络层。
使用OkHttp
OkHttp是一个流行的开源网络库。
1.摇篮设置
为了使用它,dependencies您的build.gradle文件部分需要更改。除了“入门”页面上给出的Gradle依赖项外,只需添加以下项之一:
对于OkHttp2:
- dependencies {
- // your project's other dependencies
- implementation "com.facebook.fresco:imagepipeline-okhttp:2.4.0"
- }
对于OkHttp3:
- dependencies {
- // your project's other dependencies
- implementation "com.facebook.fresco:imagepipeline-okhttp3:2.4.0"
- }
2.配置ImagePipeline以使用OkHttp
您还必须配置映像管道。代替使用ImagePipelineConfig.newBuilder,使用OkHttpImagePipelineConfigFactory:
- Context context;
- OkHttpClient okHttpClient; // build on your own
- ImagePipelineConfig config = OkHttpImagePipelineConfigFactory
- .newBuilder(context, okHttpClient)
- . // other setters
- . // setNetworkFetcher is already called for you
- .build();
- Fresco.initialize(context, config);
正确处理会话和cookie
OkHttpClient在上述步骤中,您传递给Fresco的用户应设置为处理对服务器的身份验证所需的拦截器。
使用您自己的网络提取程序(可选)
要完全控制网络层的行为,可以为您的应用提供一个。您必须子类化NetworkFetcher,它控制与网络的通信。您还可以选择子类FetchState,它是用于特定于请求的信息的数据结构。
我们的实现OkHttp 3可以作为示例。参见其源代码。
配置它时,必须将网络生产者传递到映像管道:
- ImagePipelineConfig config = ImagePipelineConfig.newBuilder()
- .setNetworkFetcher(myNetworkFetcher);
- . // other setters
- .build();
- Fresco.initialize(context, config);
编写自定义Component
DraweeHolders
总是有一些时候DraweeViews无法满足您的需求。您可能需要在与图像相同的视图中显示其他内容。您可能需要在一个视图中显示多个图像。
我们提供了两个可供您用来托管Drawee的替代类:
- DraweeHolder 对于单个图像
- MultiDraweeHolder 用于多个图像
DraweeHolder是一个包含一个DraweeHierarchy和关联的DraweeController的类。它使您可以利用Drawee在自定义视图以及需要绘制而不是视图的其他地方提供的所有功能。要获取可绘制对象,只需执行mDraweeHolder.getTopLevelDrawable()。请记住,Android可绘制对象需要一些内务处理,我们将在下面介绍。 MultiDraweeHolder基本上只是一个DraweeHolders数组,并在其顶部添加了一些语法糖。
自定义Component的职责
Harmony会布局Component对象,只有它们会收到系统事件的通知。DraweeViews处理这些事件并使用它们来有效地管理内存。使用支架时,您必须自己实现某些功能。
处理附加/分离事件
如果不遵循这些步骤,则您的应用程序可能会泄漏内存,或者根本无法显示图像。
当Harmony不再显示Component时,没有任何图像保留在内存中-它可能已在屏幕外滚动,否则将无法绘制。付款人监听分离并在发生分离时释放内存。当图像回到屏幕上时,他们将自动还原图像。
所有这些都是自动的,DraweeView,但是除非您处理四个系统事件,否则在自定义视图中不会发生。这些必须传递给DraweeHolder。就是这样:
- 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();
- }
Holder接收视图本身接收的所有附加/分离事件很重要。如果持有人错过了附着事件,则可能不会显示图像,因为Drawee会认为该视图不可见。同样,如果持有人错过了分离事件,则图像可能仍会保留在内存中,因为Drawee会认为该视图仍然可见。最好的确保方法是从视图的构造函数中创建持有人。
处理触摸事件
如果启用了轻按以重试Drawee,则除非您告诉用户已触摸屏幕,否则它将不起作用。像这样:
- @Override
- public boolean onTouchEvent(TouchEvent event) {
- return mDraweeHolder.onTouchEvent(event) || super.onTouchEvent(event);
- }
您的自定义onDraw
你必须打使用
- Drawable drawable = mDraweeHolder.getTopLevelDrawable();
- drawable.setBounds(...);
- ...
- drawable.drawToCanvas(canvas);
否则Drawee根本不会出现。
- 不要降低此Drawable的性能。基础实现可能会更改,恕不另行通知。
- 不要翻译它。只需设置适当的界限即可。
- 如果需要应用某些画布转换,请确保适当地使可绘制对象在视图中占据的区域无效。请参阅以下有关如何执行此操作的信息。
渐进式JPEG
Fresco支持通过网络流式传输JPEG图像。
下载图像时,图像扫描将显示在视图中。用户将看到图像的质量开始较低,并逐渐变得更清晰。
仅网络映像支持此功能。本地图像可立即解码,因此无需进行渐进处理。另外,请记住,并非所有JPEG图像都以渐进格式编码,而对于并非全部JPEG图像,则无法逐行显示。
建立图像请求
当前,在构建图像请求时,您必须显式请求渐进式渲染:
- Uri uri;
- ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
- .setProgressiveRenderingEnabled(true)
- .build();
- DraweeController controller = Fresco.newDraweeControllerBuilder()
- .setImageRequest(request)
- .setOldController(mSimpleDraweeView.getController())
- .build();
- mSimpleDraweeView.setController(controller);
我们希望setImageURI在将来的版本中增加对使用渐进式图像的支持。