前不久闲鱼团队的公众号发了一篇文章讲了闲鱼团队在Flutter图片框架的演进过程文章,里面讲到了使用外接纹理的方式来实现图片下载功能:闲鱼Flutter图片框架架构演进(超详细),本文的用意就是动手实现闲鱼的这个外接纹理图片下载功能。canvas
在刚学Flutter的时候咱们的图片下载功能通常都是直接使用Flutter官方提供的api来加载网络图片,如:api
Image(
image: NetworkImage("https://www.xxx.com/xx.jpg")
)
复制代码
这样子已经能够实现了界面须要展现的图片了,但是为何闲鱼团队不这么写还要"折腾"什么图片下载框架呢?在闲鱼的文章中抛出的三点其实就能够解释了为何还要对Flutter的图片下载功能进行进一步封装实现: bash
什么是外接纹理? 文章中一直在讲的外接纹理是个什么概念? 其实外接纹理在代码上叫作Texture
,这个类在Flutter中的代码量很是少一共没几行:网络
class Texture extends LeafRenderObjectWidget {
/// Creates a widget backed by the texture identified by [textureId].
const Texture({
Key key,
@required this.textureId,
}) : assert(textureId != null),
super(key: key);
/// The identity of the backend texture.
final int textureId;
@override
TextureBox createRenderObject(BuildContext context) => TextureBox(textureId: textureId);
@override
void updateRenderObject(BuildContext context, TextureBox renderObject) {
renderObject.textureId = textureId;
}
}
复制代码
虽然代码少,可是功能确实强大,整个Flutter渲染流程中须要的东西都提供了。在我看来Texture
跟原生的结合的关键就是textureId
,大体的理解就是Flutter端的TextTure
和原生端的Surface
二者经过textureId
完成相互绑定,从而达到原生绘制给Flutter端显示效果。 架构
Texture
中须要传入
textureId
从而达到跟原生
Surface
绑定,因此第一步就是须要生成
textureId
,这里原生主要采用自定义
MethodChannel
的方式:
TextureRegistry textureRegistry = registrar.textures();
TextureRegistry.SurfaceTextureEntry surfaceTextureEntry = textureRegistry.createSurfaceTexture();
long textureId = surfaceTextureEntry.id();
Map<String, Object> reply = new HashMap<>();
reply.put("textureId", textureId);
textureSurfaces.put(String.valueOf(textureId), surfaceTextureEntry);
result.success(reply);
复制代码
这里的关键就是经过Flutter提供的SurfaceTextureEntry
来获取值,并经过Channel的方式传递给Flutter端,而后在Flutter端进行调用从而获得这个textureId
的值:框架
init() async {
var response = await _channel.invokeMethod("load");
_textureId = response["textureId"];
}
复制代码
当咱们获得了须要的textureId
以后就能够初始化一个Texture
对象了:async
Widget build(BuildContext context) {
return Texture(textureId: _textureId);
}
复制代码
上面完成了第一步,接下来就是如何复用原生的图片下载功能了从而将获得图片传给Flutter端显示。 在Android原生开发中基本上你们都在使用Fresco
或者Glide
来加载图片(固然有自家的图片库),不过最终的目的都是获得图片,而后经过textureId
传给Flutter端。我这里直接展现图片下载成功后的回调,代码实现:ide
int textureId = call.argument("textureId");
final String url = call.argument("url");
int imageWidth = bitmap.getWidth();
int imageHeight = bitmap.getHeight();
TextureRegistry.SurfaceTextureEntry surfaceTextureEntry = textureSurfaces.get(String.valueOf(textureId));
Rect rect = new Rect(0, 0, 200, 200);
surfaceTextureEntry.surfaceTexture().setDefaultBufferSize(imageWidth, imageHeight);
Surface surface = new Surface(surfaceTextureEntry.surfaceTexture());
Canvas canvas = surface.lockCanvas(rect);
canvas.drawBitmap(bitmap, null, rect, null);
bitmap.recycle();
surface.unlockCanvasAndPost(canvas);
result.success(0);
复制代码
这里的原生代码最关键的就是获取Surface
,有了它就能够获取到Canvas
天然就能够画出想要的效果,Flutter端就能够显示了。 而Flutter端调用的时候传入相关的textureId
以及图片地址给原生,如:优化
var params = Map();
params["textureId"] = _textureId;
params["url"] = url;
result = await _channel.invokeMethod("start", params);
value = result;
复制代码
整个demo差很少就这样子结束了,运行起来看到的效果以下: ui
Image
方式进行比较。首先在滑动的过程当中二者实现方式都差很少,闲鱼的文章中对比了下内存优化了很多,我这里也对比下内存: 自带的
Image
的内存表现:
Texture
的内存表现:
Image
加载图片的时候在AS中查看Graphics的内存表现会飙升,我这里一共加载20张图片滑动到底部后相比确实差了很多。因为两种实现方式的图片存在于内存的位置不一样,若是从总得内存占有量来说
Texture
确定表现得更加优秀点。
可是,当你在混合开发中一张图片已经加载完成原生会直接复用,多是从内存读取也有多是从sdcard读取,不只加载速度快并且也能为用户省很多流量,闲鱼文章也提到采用外接纹理的方式确实能有效的复用原生图片,总得来说能够有效的解决了闲鱼文章开头的三个问题。