前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >flutter图片加载内存优化,我只是很馋原生缓存的图片而已

flutter图片加载内存优化,我只是很馋原生缓存的图片而已

原创
作者头像
brzhang
修改2019-12-05 16:08:12
7.3K3
修改2019-12-05 16:08:12
举报
文章被收录于专栏:玩转全栈玩转全栈

本文讲述的是在混栈开发模式下的flutter图片加载内存优化,如果你的项目是一个纯净的flutter工程,就是不属于以原生接入flutter的方式,那么这篇文章对你也有一定的指导意义。

如果你的项目是纯净的flutter,那么优化的方向可以考虑有一下几种优化方式:

  1. 使用cached_network_image
  2. 在1的基础上进行按尺寸加载,比如本来要加载http://xxxx.jpg ,实际上可以借助优图等工具进行裁剪,按需加载,比如http://xxx.jpg?imageMogr2/cut/{width}x{height}x{x}x{y}
  3. 在flutter上自己做图片缩放,降低内存。

如果,你使用的是混栈开发模式,就是所谓的在原生的基础上接入flutter,那么在成功接入flutter之后,你肯定会碰到这样一个困扰,就是flutter这边的图片加载如何利用原生那边已经缓存好的图片数据。因为如果不利用的话,比如同样一张图片,在原生层加载了一次,然后,在flutter这边的业务,假如也需要加载同样一张图,而且是相同尺寸,那将会占用两份内存,这个开销是很不划算的,那么如何解决,请继续本文阅读。首先先看一个效果,图的上半部分是利用原生ImageView加载图片,可以看到内存快找中找不到Image这个class,flutter整体占用内存也比原生要低一些

利用原生加载图片和不利用对比效果
利用原生加载图片和不利用对比效果

所以,做到这一步,下面利用原生已经缓存好的图片就不是什么难事了,众所周知,原生图片缓存框架不要太多太好用,Android中有比较著名的Glide,iOS中的有SDWebImage,有了我们上面的成果,我们就可以复用原生已有的图片内存了。

复用原生内存
复用原生内存

所以,我们如何做到flutter利用原生imageView加载图片的呢?

首先,我们了解到flutter为我们提供了一个PlatformView,在Android端叫做AndroidView,在iOS端叫做UIKitView。这个PlatformView就是flutter官方为了解决flutter层去使用原生空间而提供的一个控件。所以,我们只需要做一个插件去封装原生的ImageView即可。

当然,我们需要注意的是,我要实现我们的目的,flutter层必须告知原生层图片加载所需要的信息:

  1. 图片的尺寸,是为了告知原生层我这个图片需要渲染多大的尺寸,同时也是为了图片加载库加载出合理的尺寸的图片。
  2. 图片的url,很显然为了加载图片。

那么,该如何操作呢?

通过PlatformView Android端的文档,我们了解到:

The Android view object is created using aPlatformViewFactory. Plugins can register platform view factories withPlatformViewRegistry#registerViewFactory.

插件可以使用下面的方式注册:

代码语言:txt
复制
public static void registerWith(Registrar registrar) {
    registrar.platformViewRegistry().registerViewFactory("imageView", ImageViewFactory(registrar.messenger()));
}

因此,我们就想,可以通过封装插件的方式,来提供出这样一个PlatformView,这样,flutter层就可以做到一套代码来使用双平台原生加载了。

所以,且看Android这边的实现方式。首先,我们需要创建一个插件工程,创建的方法

代码语言:txt
复制
flutter create --org com.example --template=plugin -i objc -a java flutter_image_view

关于插件详细的教程可以参考官方文章介绍;

有了插件工程,我们按照AndroidView的文档,我们需要有一个PlatFormViewFactory。

代码语言:txt
复制
public class FlutterImageViewFactory extends PlatformViewFactory {

    private final BinaryMessenger messenger;

    public FlutterImageViewFactory(BinaryMessenger messenger) {
        super(StandardMessageCodec.INSTANCE);
        this.messenger = messenger;
    }

    @Override
    public PlatformView create(Context context, int id, Object o) {
        return new FlutterImageView(context, messenger, id);
    }
}

create方法的返回就是我们原生提供给 Flutter的PlatFormView的实现,看看具体实现代码

代码语言:txt
复制
public class FlutterImageView implements PlatformView, MethodChannel.MethodCallHandler {

    private final ImageView imageView;
    private final MethodChannel methodChannel;
    private Context context;


    FlutterImageView(Context context, BinaryMessenger messenger, int id) {
        imageView = new ImageView(context);
        this.context = context;
        methodChannel = new MethodChannel(messenger, "com.tencent.igame/flutter_image_view_" + id);
        methodChannel.setMethodCallHandler(this);
    }

    @Override
    public View getView() {
        return imageView;
    }

    @Override
    public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
        switch (methodCall.method) {
            case "setUrl":
                setUrl(methodCall, result);
                break;
            case "setSize":
                setSize(methodCall, result);
            default:
                result.notImplemented();
        }

    }

    private void setUrl(MethodCall methodCall, MethodChannel.Result result) {
        String url = (String) methodCall.arguments;
        //这里使用glide加载图片
        Glide.with(context).load(url).centerCrop().into(imageView);
        result.success(null);
    }

    @SuppressWarnings("ConstantConditions")
    private void setSize(MethodCall methodCall, MethodChannel.Result result) {
        if (methodCall.argument("width") != null && methodCall.argument("height") != null) {
            int width = methodCall.argument("width");
            int height = methodCall.argument("height");
            ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(width, height);
            imageView.setLayoutParams(layoutParams);
        }
        result.success(null);
    }

    @Override
    public void dispose() {
    }
}

通过messenger,我们可以将flutter发送的消息传递到原生这边,可以看到setUrl这里,我们使用了Glide来加载图片了。那么,我们不禁要问,这个messenger是怎么从flutter那边传递到原生这边的,实际上,我们创建插件工程的时候,在.android目录,和.ios目录早就留好了接口了,我们只需要通过registrar.platformViewRegistry().registerViewFactory即可

代码语言:txt
复制
public class TipFlutterImageViewPlugin implements MethodCallHandler {
    /**
     * Plugin registration.
     */
    public static void registerWith(Registrar registrar) {
        final MethodChannel channel = new MethodChannel(registrar.messenger(), "tip_flutter_image_view");
        channel.setMethodCallHandler(new TipFlutterImageViewPlugin());
        registrar
                .platformViewRegistry()
                .registerViewFactory(
                        "com.tencent.igame/flutter_image_view", new FlutterImageViewFactory(registrar.messenger()));
    }

    @Override
    public void onMethodCall(MethodCall call, Result result) {

    }
}

然后整个过程就OK了。

切换效果
切换效果

可以看到,正如PlatFormView文档所的那样,使用原生view嵌入到flutter代价是有点昂贵到,从原生切回flutter图片展示是秒显示,而从flutter切回原生有延时,但是我们获得的收益是利用了原生图片加载框架中缓存的图片(当然是原生那边已经加载过同样一张图的情况下),以时间换空间,该插件使用在较少图片加载的页面,如果页面中图片较多,可以考虑使用外接纹理Texture方案。

如果需要使用本插件,可以看到代码,传送门

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 所以,我们如何做到flutter利用原生imageView加载图片的呢?
  • 那么,该如何操作呢?
  • 有了插件工程,我们按照AndroidView的文档,我们需要有一个PlatFormViewFactory。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档