腾讯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包
         具体包为:datadata包名app_tbs_64core_sharetbs_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包      * 存放在:datadata包名app_tbs_64core_sharetbs_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

 

发表评论

相关文章