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

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

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

Image(
  image: NetworkImage("https://www.xxx.com/xx.jpg")
)

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


在这里插入图片描述

特别是在混合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对象了:

  Widget build(BuildContext context) {
    return Texture(textureId: _textureId);
  }

上面完成了第一步,接下来就是如何复用原生的图片下载功能了从而将得到图片传给Flutter端显示。
在Android原生开发中基本上大家都在使用Fresco或者Glide来加载图片(当然有自家的图片库),不过最终的目的都是得到图片,然后通过textureId传给Flutter端。我这里直接展示图片下载成功后的回调,代码实现:

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差不多就这样子结束了,运行起来看到的效果如下:

在这里插入图片描述

这样子实现有什么好处?
在完成了图片下载功能后,自然要跟Flutter自带的Image方式进行比较。首先在滑动的过程中两者实现方式都差不多,闲鱼的文章中对比了下内存优化了不少,我这里也对比下内存:
自带的Image的内存表现:
在这里插入图片描述

采用Texture的内存表现:
在这里插入图片描述

Flutter自带的Image加载图片的时候在AS中查看Graphics的内存表现会飙升,我这里一共加载20张图片滑动到底部后相比确实差了不少。由于两种实现方式的图片存在于内存的位置不同,如果从总得内存占有量来讲Texture肯定表现得更加优秀点。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容