一、背景

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;
}

腾讯tbs 内存泄露的更多相关文章

  1. JVM内存管理概述与android内存泄露分析

    一.内存划分 将内存划分为六大部分,分别是PC寄存器.JAVA虚拟机栈.JAVA堆.方法区.运行时常量池以及本地方法栈. 1.PC寄存器(线程独有):全称是程序计数寄存器,它记载着每一个线程当前运行的 ...

  2. 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理

    [微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...

  3. 关于 Unity 项目中的 Mono 堆内存泄露

    关于 Unity 项目中的 Mono 堆内存泄露 题记:这是补一篇应该在将近一年前就应该写的记录,今天终于补上. 内存泄露是一个老话题了,之前我专门写过一篇 排查 Lua 虚拟机内存泄露 的文章,并且 ...

  4. (转)专项:Android 内存泄露实践分析

    今天看到一篇关于Android 内存泄露实践分析的文章,感觉不错,讲的还算详细,mark到这里. 原文发表于:Testerhome: 作者:ycwdaaaa ;  原文链接:https://teste ...

  5. Android内存泄露调试

    Android 内存泄漏调试 一.概述 如果我们编写的代码当中有太多的对内存使用不当的地方,难免会使得我们的设备运行缓慢,甚至是死机.为了能够使得 Android 应用程序安全且快速的运行, Andr ...

  6. 问题查询-tomcat内存泄露

    1.报警信息 内容: 微信服务器向公众号推送消息或事件后,开发者5秒内没有返回 次数: 5分钟 239次 错误样例: [OpenID=o][Stamp=1562718361][3rdUrl=url][ ...

  7. Android 开发学习进程0.28 腾讯TBS接入和相关问题

    TBS 的接入和使用 TBS 的接入 腾讯TBS是X5内核的升级版,可以当作webview 来打开 网页,可以以用来打开docx doc pdf 等文件,这里主要使用的是文件功能. 依赖接入 api ...

  8. java: web应用中不经意的内存泄露

    前面有一篇讲解如何在spring mvc web应用中一启动就执行某些逻辑,今天无意发现如果使用不当,很容易引起内存泄露,测试代码如下: 1.定义一个类App package com.cnblogs. ...

  9. 查看w3wp进程占用的内存及.NET内存泄露,死锁分析

    一 基础知识 在分析之前,先上一张图: 从上面可以看到,这个w3wp进程占用了376M内存,启动了54个线程. 在使用windbg查看之前,看到的进程含有 *32 字样,意思是在64位机器上已32位方 ...

随机推荐

  1. ZAB 协议?

    ZAB 协议是为分布式协调服务 Zookeeper 专门设计的一种支持崩溃恢复的原子广 播协议. ZAB 协议包括两种基本的模式:崩溃恢复和消息广播. 当整个 zookeeper 集群刚刚启动或者 L ...

  2. 如何将springboot工程打包成war包并且启动

    将项目打成war包,放入tomcat 的webapps目录下面,启动tomcat,即 可访问. 1.pom.xml配置修改 <packaging>jar</packaging> ...

  3. IPython是什么?

    参考:IPython 中常用的魔法命令 Ipython中的魔法命令总结 IPython 是一个 python 的交互式 shell,比默认的python shell 好用得多,支持变量自动补全,自动缩 ...

  4. 【C++】智能指针详解

    转自:https://blog.csdn.net/flowing_wind/article/details/81301001 参考资料:<C++ Primer中文版 第五版>我们知道除了静 ...

  5. 9_根轨迹_Part3_分离点/汇合点和根的性质

  6. PCB模块化布局系列之时钟电路设计(晶振、晶体)

    一.晶体在一个电路系统中, 时钟是必不可少的一部分.如人的心脏的作用,如果电路系统的时钟出错了,系统就会发生紊乱,因此在PCB 中设计,一个好的时钟电路是非常必要的.我们常用的时钟电路有:晶体.晶振. ...

  7. 记离线缓存(manifest)一大坑,断定其只适用于静态网站或离线应用

    今天看了离线缓存(manifest)方面的资料,兴冲冲地就想给自己的网站用上.待我把代码都写好部署上服务器,并测试过OK的时候,在SegmentFault刷了一把manifest方面的问答,才发现这个 ...

  8. React 可视化开发工具 shadow-widget 的非可视开发方法

    Shadow Widget 提倡在可视设计器中开发用户界面,输出转义标签,而非 JSX.许多童鞋可能不知道 SW 同样支持用 JSX 设计界面,开发体验比原生 React 编程好出很多,本文就介绍这方 ...

  9. Python爬虫报错:"HTTP Error 403: Forbidden"

    错误原因:主要是由于该网站禁止爬虫导致的,可以在请求加上头信息,伪装成浏览器访问User-Agent. 新增user-agent信息: headers = {'User-Agent':'Mozilla ...

  10. CCF201812-1小明上学

    题目背景 小明是汉东省政法大学附属中学的一名学生,他每天都要骑自行车往返于家和学校.为了能尽可能充足地睡眠,他希望能够预计自己上学所需要的时间.他上学需要经过数段道路,相邻两段道路之间设有至多一盏红绿 ...