Flutter for OHOS插件的一次内存泄漏问题排查

昨天,周五下午,突然告诉我之前适配的一个Flutter插件 Fijkplayer(适配OHOS平台),存在内存泄漏问题,要排查下。
今天加班解决,通过一上午+一中午的排查和修复,终于解决了,下面做一个总结。

本次内存泄漏排查发现几个典型问题

1、napi持有的js回调函数未释放:

ets侧有注册回调函数给Napi接口,注册定义如下:

_setMessageListener(xcomponentId: string, messageCallBack:Function): void;

如果回收时,cpp未释放对这个回调函数的持有,那么就会导致内存泄漏。

这个库对napi传入的js回调方法,做了两种不同的处理:

1.1 直接使用 napi_ref(对象句柄)保存这个js回调函数,那么释放时delete这个napi_ref即可:

// 1.1.1 创建napi_ref保存回调方法,对应定义 napi_ref callBackRefMessage_;
napi_create_reference(env, callback, 1, &callBackRefMessage_);

// 1.1.2 回收时,删除这个napi_ref并置空
napi_delete_reference(env, callBackRefMessage_);
callBackRefMessage_ = nullptr;

1.2 把回调函数封装到线程安全函数中,这时候释放只需删除这个线程安全函数即可

// 1.2.1 napi函数中,创建一个线程安全函数,里面使用了napi_value callback这个参数
napi_value IJKPlayerSurfaceNapi::setMessageListener(napi_env env, napi_callback_info info) {
    size_t argc = PARAM_COUNT_2;
    napi_value args[PARAM_COUNT_2] = {nullptr};
    napi_get_cb_info(env, info, &argc, args, NULL, NULL);

    // 读取回调函数
    napi_value callback = args[1];
    
    uint64_t surfaceId;
    NapiUtil::JsValueToUint64(env, args[INDEX_0],  surfaceId);
    
    napi_value resource_name;
    napi_create_string_utf8(env, "tsfn", NAPI_AUTO_LENGTH, &resource_name);
    napi_status status = napi_create_threadsafe_function(
        env,
        // 传入回调函数
        callback,
        nullptr,
        resource_name,
        0,
        1,
        nullptr,
        nullptr,
        nullptr,
        // JS 线程回调
        [](napi_env env, napi_value js_callback, void* context, void* data) {
            CallbackMessage* msg = static_cast<CallbackMessage*>(data);
            napi_value argv[4];

            napi_create_int32(env, msg->what, &argv[0]);
            napi_create_int32(env, msg->arg1, &argv[1]);
            napi_create_int32(env, msg->arg2, &argv[2]);

            if (msg->obj != nullptr) {
                napi_create_string_utf8(env, msg->obj, NAPI_AUTO_LENGTH, &argv[3]);
                napi_call_function(env, nullptr, js_callback, 4, argv, nullptr);
            } else {
                napi_call_function(env, nullptr, js_callback, 3, argv, nullptr);
            }

            delete msg;
        },
        &IJKPlayerSurfaceNapi::getInstance(surfaceId)->tsfnMessage_
    );
    //....
}


// 1.2.2 释放时,删除这个线程安全函数,并置空
if (instance->tsfnMessage_) {
    napi_release_threadsafe_function(instance->tsfnMessage_, napi_tsfn_abort);
    instance->tsfnMessage_ = nullptr;
}

2、MethodChannel未取消监听

插件初始化的时候,在MethodChannel中传入了this:

  constructor(id: number, binding: FlutterPluginBinding) {
    //...
    this.channel = new MethodChannel(binding.getBinaryMessenger(), "befovy.com/fijkplayer/" + this.id);
    this.channel.setMethodCallHandler(this);
    this.eventChannel = new EventChannel(binding.getBinaryMessenger(), "befovy.com/fijkplayer/event/" + this.id);
    this.eventChannel.setStreamHandler(this);
    //...
  }

所以释放时,需要置空:

 release() {
    this.channel?.setMethodCallHandler(null);
    this.channel = null;
    this.eventChannel?.setStreamHandler(null);
    this.eventChannel = null;
    //...
  }

3、js箭头函数闭包内this导致的互相引用,无法释放

比如下列代码:

// 在箭头函数内,使用了this
this.listener = (type:number)=>{
    this.onTypeUpdate(type);
}


// 释放时,要置空这个箭头函数,不然闭包持有this,会导致this无法释放
this.listener = null;

OHOS应用如果分析内存泄漏

1、IDE工具的堆快照抓取

具体方法请参考:官方文档:使用Snapshot检测虚拟机内存泄漏

CreateSession

简要步骤:
1、选择要抓取快照的进程;
2、选择Snapshot,点击Create Session;
3、开始session记录;
4、点击相机图标,截取一个快照;
5、应用操作进入要检测页面,进行一些可能导致内存泄漏的操作;
6、退出检测页面,点击几次扫地图标,主动GC;
7、再次截取一个快照;
8、选择一个较新快照,点击Comparison,选择一个之前的快照。

2、对比快照,找到未释放的新增对象

ComparisionSession

1、可以看到这一次的对比,有13个IjkMediaPlayer对象未释放;
2、而通过引用链,可以发现是messageCallBack这个对象没有释放。

3、修改后,重新对比快照

经过前面的问题分析后,重新对比快照

ComparisionSession_new

之前无法回收的对象,现在可以正常回收了,搞定收工!

posted @ 2026-03-28 16:55  getmoon  阅读(4)  评论(0)    收藏  举报