Flutter学习

flutter共享native资源的多种姿(fang)势(shi

2020-01-04  本文已影响0人  二休的歌

导语:flutter+native混合开发过程中,flutter可能需要共享native已有的资源,如app内置资源、下载好的数据、已缓存的内存数据等,这里介绍几种flutter共享native资源的方式,包括通常的channel、file,以及指针方式实现内存共享。以安卓为例。


使用flutter开发全新app时,资源一般是放置在flutter工程中,ios、android两端共享。但是在已有app中集成flutter进行flutter+native的混合开发过程中,为了能复用app已有资源,flutter经常需要向native拿取这些资源,如已内置的图片、文件等。本文主要介绍几种flutter向native拿取资源的方式。以android为例。

目录

  1. channel bytes流传输方式
  2. 文件路径方式
  3. 内存指针共享方式
  4. bitmap内存指针共享
  5. 修改flutter engine直接读取native内置其他assets资源方式

先上小菜,flutter如何与native进行通信?

//android java监听
final MethodChannel channel = new MethodChannel(flutterView, "your_method_name");
channel.setMethodCallHandler(new MethodCallHandler() {//注册监听
  @Override
  public void onMethodCall(MethodCall methodCall, Result result) {
    if(methodCall.method.equals("your_method_name")) {
      String arg1 = methodCall.argument("arg1");
      Map<String, Object> reply = new HashMap<String, Object>();
      reply.put("result", "haha2");
      result.success(reply);//返回值
    }
  }
//flutter dart 调用
const MethodChannel _channel = const MethodChannel("your_channel_name");
final Map<dynamic, dynamic> reply = await _channel.invokeMapMethod('your_method_name', {
              "arg1" : "haha"
            });
//flutter dart监听
const MethodChannel _channel = const MethodChannel("your_channel_name");
_channel.setMethodCallHandler((methodCall) async{//注册监听
  if(methodCall.method == "your_method_name"){
    return "haha";//返回
  }
  return null;
});
//android java调用
new MethodChannel(flutterView, "your_channel_name")
    .invokeMethod("your_method_name", your_args, new MethodChannel.Result(){//调用
        @Override
        public void success(Object o) {//返回值
        }
        @Override
        public void error(String s, String s1, Object o) {
        }
        @Override
        public void notImplemented() {
        }
    });

下面进入正题

1. channel bytes流传输方式

//android java侧读取资源,得到byte[],回传给flutter
//flutter method call resName => resId
InputStream inputStream = context.getResources().openRawResource(resId);
//从inputStream种读取资源,转成bytes
int size = inputStream.available();
byte[] bytes = new byte[size];
byte[] buffer = new byte[Math.min(1024, size)];
int index = 0;
int len = inputStream.read(buffer);//读取资源到byte[]
while (len != -1){
    System.arraycopy(buffer, 0, bytes, index, len);
    index += len;
    len = inputStream.read(buffer);
}
result.success(bytes);//把bytes写到channel种返回给flutter
inputStream.close();
//flutter调用,拿取byte[]。在flutter 侧 byte[]对应Uint8List
Uint8List data = await _channel.invokeMethod('getNativeImage', {
    "imageName" : "xxx",
  });
//flutter Image.memory api 可以把这些Uint8List/byte[]展示成图像

2. 文件路径方式

//android java读取内置drawable写入缓存目录/sdcard
//flutter method call resName => resId
InputStream inputStream = context.getResources().openRawResource(resId);
File parent = outFile.getParentFile();
if(!parent.exists()){
    parent.mkdirs();
}
FileOutputStream fos = new FileOutputStream(outFile);
byte[] buffer = new byte[1024];
int byteCount = inputStream.read(buffer);
while ((byteCount) != -1) {
    fos.write(buffer, 0, byteCount);
    byteCount = inputStream.read(buffer);
}
fos.flush();//刷新缓冲区
inputStream.close();
fos.close();
//return outFile path
//flutter 拿取到文件path 以  Image.file 展示图片

3. 内存指针共享方式

//android java侧拿取byte[]指针
jbyte *cData = env->GetByteArrayElements(bytes, &isCopy);
//android java代码
InputStream inputStream = context.getResources().openRawResource(resId);
int size = inputStream.available();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(size);
//...read inputStream to byteBuffer
long ptr = JniInterface.native_getByteBufferPtr(byteBuffer);
Map<String, Object> reply = new HashMap<String, Object>();
reply.put("rawDataAddress", ptr);
reply.put("rawDataLength", totalLength);
//cacheObj(nativeImageID, byteBuffer);//需要缓存一下,以保证flutter使用时没有被释放
result.success(reply);
inputStream.close();
//android jni 获取内存指针
Java_com_xxxx_JniInterface_native_1getByteBufferPtr(
        JNIEnv *env, jclass clazz, jobject byte_buffer) {
    jbyte *cData = (jbyte*)env->GetDirectBufferAddress(byte_buffer);//获取指针
    return (jlong)cData;
}
//flutter dart 把指针转换成dart数据结构Uint8List
import 'dart:ffi';
Pointer<Uint8> pointer = Pointer<Uint8>.fromAddress(
    rawDataAddress); //address是内存地址
Uint8List bytes = pointer.asExternalTypedData(
    count: rawDataLength);
//Uint8List bytes可以通过 Image.memory 接口显示图像
//建议参考MemoryImage重写一个ImageProvider把对native内存引用释放罗加入
//之前调用native获取指针,增加内存引用计数1
PaintingBinding.instance.instantiateImageCodec(bytes) ;
//之后通知减除内存引用1
//对于低版本不支持dart:ffi的估计是自定义engine了,可以自己添加接口,实现指针转Uint8List
const long address = tonic::DartConverter<long>::FromDart(Dart_GetNativeArgument(args, 0));
void* ptr = reinterpret_cast<char*>(address);
const int bytes_size = tonic::DartConverter<int>::FromDart(Dart_GetNativeArgument(args, 1));
tonic::DartInvoke(callback_handle,{
    tonic::DartConverter<tonic::Uint8List>::ToDart(reinterpret_cast<const u_int8_t*>(ptr), bytes_size)
});

4. bitmap内存指针共享

//android jni代码。java bitmap object 转 pixels内存指针
Java_com_xxxx_JniInterface_native_1getBitmapPixelDataMemoryPtr(
        JNIEnv *env, jclass clazz, jobject bitmap) {
    AndroidBitmapInfo bitmapInfo;
    int ret;
    if ((ret = AndroidBitmap_getInfo(env, bitmap, &bitmapInfo)) < 0) {
        LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret);
        return 0;
    }
    // 读取 bitmap 的像素数据块到 native 内存地址
    void *addPtr;
    if ((ret = AndroidBitmap_lockPixels(env, bitmap, &addPtr)) < 0) {
        LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
        return 0;
    }
    //unlock,保证不因这里获取地址导致bitmap被锁定
    AndroidBitmap_unlockPixels(env, bitmap);
    return (jlong)addPtr;
}
//android java调用,并返回给flutter内存指针信息
long address = JniInterface.getBitmapPixelDataMemoryPtr(bitmap);
if (address != 0) {
    Map<String, Object> reply = new HashMap<String, Object>();
    reply.put("pixelsDataAddress", address);
    reply.put("pixelsDataWidth", bitmap.getWidth());
    reply.put("pixelsDataHeight", bitmap.getHeight());
    //cacheObj(nativeImageID, bitmap);//需要缓存一下,以保证flutter使用时没有被释放
    result.success(reply);
}
//flutter 侧使用
Pointer<Uint8> pointer = Pointer<Uint8>.fromAddress(pixelsDataAddress); //address是内存地址
int bytesCount = pixelsDataHeight * pixelsDataWidth * 4;
Uint8List bytes = pointer.asExternalTypedData(count: bytesCount);//pixels bytes data
ui.PixelFormat format = ui.PixelFormat.rgba8888;

5. 修改flutter engine直接读取native内置其他assets资源方式

Image.asset
  AssetImage
    AssetBundleImageProvider#load
       AssetBundleImageProvider#_loadAsync
          asset_bundle.dart#PlatformAssetBundle#load
             defaultBinaryMessenger.send('flutter/assets', asset_name)
                engine.cc#HandlePlatformMessage  //flutter engine层
                   engine.cc#HandleAssetPlatformMessage
                      asset_manager_->GetAsMapping(asset_name)//返回mapping,包含内存指针和size
                         apk_asset_provider.cc#APKAssetProvider::GetAsMapping
//apk_asset_provider.cc中的实现
std::unique_ptr<fml::Mapping> APKAssetProvider::GetAsMapping(
    const std::string& asset_name) const {
  std::stringstream ss;
  ss << directory_.c_str() << "/" << asset_name;  //dir是flutter_assets,asset_name是flutter层开发指定,合起来flutter_assets/asset_name
  //这是flutter侧添加的资源在android apk中的位置,打包在native assets目录下
  //AAssetManager_open是android jni接口,位于android/asset_manager_jni.h
  AAsset* asset =
      AAssetManager_open(assetManager_, ss.str().c_str(), AASSET_MODE_BUFFER);
  if (!asset) {
    return nullptr;
  }
  return std::make_unique<APKAssetMapping>(asset);//最终通过AAsset_getBuffer读取数据
}
image.pngimage.png
std::unique_ptr<fml::Mapping> APKAssetProvider::GetAsMapping(
    const std::string& asset_name) const {
  std::stringstream ss;
  if(asset_name.size() > 3 && asset_name.compare(0, 3, "../") == 0){
    ss << asset_name.substr(3);//支持 ../ 读取native assets下资源
  } else {
    ss << directory_.c_str() << "/" << asset_name;//默认方式
  }
  AAsset* asset =
      AAssetManager_open(assetManager_, ss.str().c_str(), AASSET_MODE_BUFFER);
  if (!asset) {
    return nullptr;
  }
  return std::make_unique<APKAssetMapping>(asset);
}
Image image = Image.asset(
  "../earth.jpg", //默认../格式是中不到资源的,报错
  fit: BoxFit.fill,
  width: 200,
);

最后,总结一下

最最后感谢阅读~~

参考资料链接

上一篇 下一篇

猜你喜欢

热点阅读