码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 腾讯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 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 c
         * 而且,每次使用 DexLoader都会再次加载,持有一个新的对象
         * 在退出时虽然提供onStop方法将此列表置空
         * 但是列表原持有的对象持有了Activity,导致无指针指向,内存泄露。
         * ?为什么不在最开始传入ApplicationContext呢
         * 因为这个context最终是由TbsReaderView类的openFile方法传入的
         * TbsReaderView类涉及UI绘制,要求必须传入Activity
         * 再进一步的最终通过DexClassLoader传给了TBSLog类
         * 

    * 解决思路:获取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 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

     

  • 相关阅读:
    【Linux学习】OpenCV+ROS 实现人脸识别(Ubantu16.04)
    Java之System.getProperty()的作用及使用说明
    网络协议:HTTPS
    c语言之字符串数组
    nvidia nx onnx转trt模型报错
    自然语言处理(一):RNN
    软信天成:元数据的干货分享!建议点赞收藏
    基于STM32动态密码锁(手机APP)_2022
    六、python Django REST framework增删改查[视图、扩展类、扩展类的子类、视图集]
    react嵌套路由
  • 原文地址:https://www.cnblogs.com/acesui/p/16243060.html
    • 最新文章
    • 沪漂五周年了:我越来越迷茫了
      Agentic Skill Routing 实战:别再把所有 Skill 塞进 AI Agent 上下文
      MySQL-Seconds_behind_master的精度误差
      [MAF预定义ChatClient中间件-03]CachingChatClient——利用缓存省钱省时间
      AI的至暗历史:从万众期待到被政府撤资,AI的两次死亡徘徊
      Agent OS :五种驯服不确定性的范式
      PortSwigger SQL注入LAB11
      数据库即时编译JIT
      [Begin]AI Learn Data Day 0
      深度学习进阶(二十七)现代 LLM 的核心架构设计其二:SwiGLU
    • 热门文章
    • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
      奉劝各位学弟学妹们,该打造你的技术影响力了!
      五年了,我在 CSDN 的两个一百万。
      Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
      面试官都震惊,你这网络基础可以啊!
      你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
      心情不好的时候,用 Python 画棵樱花树送给自己吧
      通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
      13 万字 C 语言从入门到精通保姆级教程2021 年版
      10行代码集2000张美女图,Python爬虫120例,再上征途
    小工具 小游戏
    Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1

    京公网安备 11010502049817号