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