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检测虚拟机内存泄漏

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

1、可以看到这一次的对比,有13个IjkMediaPlayer对象未释放;
2、而通过引用链,可以发现是messageCallBack这个对象没有释放。
3、修改后,重新对比快照
经过前面的问题分析后,重新对比快照

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

浙公网安备 33010602011771号