一、背景
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 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