腾讯tbs 内存泄露

一、背景

TBS(腾讯浏览服务)是腾讯提供的移动端webview体验的整套解决方案(https://x5.tencent.com/docs/index.html),可以用于移动端加载doc、xls、pdf等文档,实现文档浏览加载服务。

Android端详细使用方法可参考:https://blog.csdn.net/czj1998/article/details/122494381

使用TbsReaderView 的openFile方法加载文件;

在关闭页面前执行TbsReaderView 的onStop方法执行内存清理,防止下次加载出现异常。

二、现象

结束文件浏览,执行TbsReaderView 的onStop方法后,发现浏览文件的Activity发生泄露,内存无法释放。

三、原因

(1)内存泄露发生在tbs sdk内部,从网络加载到本地jar包
         具体包为:data\data\包名\app_tbs_64\core_share\tbs_jars_fusion_dex.jar
(2)TBS使用dex加载器加载了本地jar包的方法,发生泄露的地方具体为com.tencent.tbs.log.TBSLog类的静态成员变量 List<Abstracte> c
         而且,每次使用 DexLoader都会再次加载,持有一个新的对象,在退出时虽然提供onStop方法将此列表置空,但是列表原持有的对象持有了Activity,导致无指针指向,内存泄露。
(3)为什么不在最开始传入ApplicationContext呢?因为这个context最终是由TbsReaderView类的openFile方法传入的,TbsReaderView类涉及UI绘制,要求必须传入Activity。进一步的,通过CLassLoader加载jar包方法,最终通过DexClassLoader传给了TBSLog类的内存泄露静态变量。

四、解决方法

(1)获取TBSLog的静态成员
(2)在执行它本身的清理方法之前,通过静态成员反射到内存泄露的类
(3)将持有的Activity置空,避免变成无指向的对象

五、代码

《腾讯tbs 内存泄露》
《腾讯tbs 内存泄露》

/**
     * tbs 存在内存泄露
     * 发生在tbs sdk内部,而且是tbs从网络加载到本地jar包
     * 存放在:data\data\包名\app_tbs_64\core_share\tbs_jars_fusion_dex.jar
     * 然后使用dex加载器加载的方法
     * 具体为com.tencent.tbs.log.TBSLog类
     * 静态成员 List<Abstracte> c
     * 而且,每次使用 DexLoader都会再次加载,持有一个新的对象
     * 在退出时虽然提供onStop方法将此列表置空
     * 但是列表原持有的对象持有了Activity,导致无指针指向,内存泄露。
     * ?为什么不在最开始传入ApplicationContext呢
     * 因为这个context最终是由TbsReaderView类的openFile方法传入的
     * TbsReaderView类涉及UI绘制,要求必须传入Activity
     * 再进一步的最终通过DexClassLoader传给了TBSLog类
     * <p>
     * 解决思路:获取TBSLog的静态成员
     * 在执行它本身的清理方法之前
     * 通过静态成员反射到内存泄露的类
     * 将持有的Activity置空
     * 避免变成无指向的对象
     */
    private void clearTbsLogContext() {
        try {
            DexLoader dexLoader = getTbsDexLoader();

            Class<?> tbsLogClass = dexLoader.loadClass("com.tencent.tbs.log.TBSLog");
            Field[] fields = tbsLogClass.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                Field field = fields[i];
                //找静态对象
                if (Modifier.isStatic(field.getModifiers())) {
                    //是私有变量
                    if (Modifier.isPrivate(field.getModifiers())) {
                        field.setAccessible(true);
                        Object fieldObject = field.get(null);
                        //不直接通过成员名反射获取对象,是因为
                        //tbs的包使用了混淆
                        //不能保证下载的包的对象名是一致的
                        //使用对象类型来尝试匹配吧
                        if (fieldObject instanceof List) {
                            List<Object> objectList = (List) fieldObject;
                            for (int j = 0; j < objectList.size(); j++) {
                                Object leakObject = objectList.get(j);
                                if (leakObject != null) {
                                    Field[] leakFields = leakObject.getClass().getDeclaredFields();
                                    for (int m = 0; m < leakFields.length; m++) {
                                        Field leakField = leakFields[m];
                                        //找到持有的context
                                        if (leakField.getType().equals(Context.class)) {
                                            leakField.setAccessible(true);
                                            //释放持有的context
                                            leakField.set(leakObject, null);
                                            leakField.setAccessible(false);
                                        }
                                    }
                                }
                            }
                        }
                        field.setAccessible(false);
                    }
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * tbsSDK提供了DexLoader对象来加载它下载的包
     * 通过反射直接获取它生成的对象吧
     * 避免还要根据路径、包名来加载dex或jar
     *
     * @return DexLoader
     */
    private DexLoader getTbsDexLoader() {
        DexLoader dexLoader = null;
        try {
            Field[] fields = mTbsReaderView.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (field.getType().equals(ReaderWizard.class)) {
                    field.setAccessible(true);
                    ReaderWizard wizard = (ReaderWizard) field.get(getTbsReaderView());
                    Field[] wizardFields = wizard.getClass().getDeclaredFields();
                    for (Field wizardField : wizardFields) {
                        //匹配到TbsReaderView对象已生成的DexLoader
                        if (wizardField.getType().equals(DexLoader.class)) {
                            wizardField.setAccessible(true);
                            dexLoader = (DexLoader) wizardField.get(wizard);
                            wizardField.setAccessible(false);
                            break;
                        }
                    }
                    field.setAccessible(false);
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return dexLoader;
    }

View Code

 

点赞