閒魚"同款"的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讀取,不只加載速度快並且也能爲用戶省很多流量,閒魚文章也提到採用外接紋理的方式確實能有效的複用原生圖片,總得來說能夠有效的解決了閒魚文章開頭的三個問題。

相關文章
相關標籤/搜索