闲鱼"同款"的Flutter图片下载功能(demo版)

前不久闲鱼团队的公众号发了一篇文章讲了闲鱼团队在Flutter图片框架的演进过程文章,里面讲到了使用外接纹理的方式来实现图片下载功能:闲鱼Flutter图片框架架构演进(超详细),本文的用意就是动手实现闲鱼的这个外接纹理图片下载功能。canvas

在刚学Flutter的时候咱们的图片下载功能通常都是直接使用Flutter官方提供的api来加载网络图片,如:api

Image(
  image: NetworkImage("https://www.xxx.com/xx.jpg")
)
复制代码

这样子已经能够实现了界面须要展现的图片了,但是为何闲鱼团队不这么写还要"折腾"什么图片下载框架呢?在闲鱼的文章中抛出的三点其实就能够解释了为何还要对Flutter的图片下载功能进行进一步封装实现: bash

在这里插入图片描述
特别是在混合Flutter开发的时候,不少时候都在想如何才能更高效的复用原生已加载的图片显得特别头疼。在看了闲鱼的文章以后外接纹理的思路确实让我眼前一亮,彷佛找到了点复用的头绪。

什么是外接纹理? 文章中一直在讲的外接纹理是个什么概念? 其实外接纹理在代码上叫作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端显示效果。 架构

在这里插入图片描述
如何实现这一过程? 由于在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

在这里插入图片描述
这样子实现有什么好处? 在完成了图片下载功能后,天然要跟Flutter自带的 Image方式进行比较。首先在滑动的过程当中二者实现方式都差很少,闲鱼的文章中对比了下内存优化了很多,我这里也对比下内存: 自带的 Image的内存表现:
在这里插入图片描述
采用 Texture的内存表现:
在这里插入图片描述
Flutter自带的 Image加载图片的时候在AS中查看Graphics的内存表现会飙升,我这里一共加载20张图片滑动到底部后相比确实差了很多。因为两种实现方式的图片存在于内存的位置不一样,若是从总得内存占有量来说 Texture确定表现得更加优秀点。

可是,当你在混合开发中一张图片已经加载完成原生会直接复用,多是从内存读取也有多是从sdcard读取,不只加载速度快并且也能为用户省很多流量,闲鱼文章也提到采用外接纹理的方式确实能有效的复用原生图片,总得来说能够有效的解决了闲鱼文章开头的三个问题。

相关文章
相关标签/搜索