android开发修改第三方内存,Android内存优化

GC算法

当内存不足时,系统就会触发GC,GC采用垃圾标记算法为跟搜索算法

289b4cf733b4

跟搜索算法.png

从图中可以看书obj4是科大的对象,标识他正在被引用,一次不会标记为可回收对象。obj5,obj6,obj7都是不可达对象,obj5跟obj6虽然互相引用,但是因为他们到gcRoots是不可达的,所以他们也被标记为gc回收对象

内存泄漏的原因

1.由开发者自己编码造成的内存泄漏

2.第三方框架在成泄漏

3.由Android系统或者第三方room造成内存泄漏

以上三种情况2,3是不可控的,我们只能控制1。

内存泄漏场景

1.非静态内部类导致内存泄露

在java中,内部类虽然和外部类都写在同一个java文件中,但是编译完成后,还是会生成各自的class文件,之所以内部类对象可以访问外部类对象中的成员(包括成员变量和成员方法),是因为内部类对象持有指向外部类对象的引用

public class Outer {

int outerField = 0;

public class Inner{

void InnerMethod(){

int i = outerField;

}

}

反编译Outer&Inner.class文件

class Outer$Inner{

final Outer this$0;

public Outer$Inner(Outer outer){

this.this$0 = outer;

super();

}

void InnerMethod(){

int i = this.this$0.outerField;

}

我们会发现:

编译器自动为内部类添加一个成员变量, 这个成员变量的类型和外部类的类型相同, 这个成员变量就是指向外部类对象(this)的引用;

编译器自动为内部类的构造方法添加一个参数, 参数的类型是外部类的类型, 在构造方法内部使用这个参数为内部类中添加的成员变量赋值;

在调用内部类的构造函数初始化内部类对象时,会默认传入外部类的引用。

这也就解释了内部类为什么能访问外部类的成员了。

看到这里我们能想到,由于非静态内部类(包括匿名内部类)默认就会持有外部类的引用,那么当非静态内部类对象的生命周期比外部类对象的生命周期长时,就会导致内存泄露。

解决办法定义一个静态的类

2.Handle中的内存泄漏

一般的人都会这么写handle

public class MainActivity extends AppCompatActivity {

private Handler mHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

if (msg.what == 1) {

// TODO

}

}

};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

notifySomething();

也许有人会说,mHandler并未作为静态变量持有Activity引用,生命周期可能不会比Activity长,应该不一定会导致内存泄露呢,显然不是这样的!

熟悉Handler消息机制的童鞋都知道,mHandler会作为成员变量保存在发送的消息msg中,代码如下:

public static Message obtain(Handler h) {

Message m = obtain();

m.target = h;

return m;

即msg持有mHandler的引用,而mHandler是Activity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理(MessageQueue和Looper都是与线程相关联的,MessageQueue是Looper引用的成员变量,而Looper是保存在ThreadLocal中的)。那么当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。

解决方法可以调用handler.removeCallbacksAndMessages(null)。来移除消息队列,从而解决,但是这样的话,队列中的消息就不会被处理了

上面在介绍GC的垃圾回收机制时有说到,强引用会造成GC回收不了,而弱引用不会,那我们在解决这种要使用内部类,但又要规避内存泄露时,一般都会采用静态内部类+弱引用的方式:

private static class MyHandler extends Handler {

private WeakReference mRef;

public MyHandler(MainActivity activity) {

mRef = new WeakReference<>(activity);

}

@Override

public void handleMessage(Message msg) {

MainActivity activity = mRef.get();

if (activity != null) {

if (msg.what == 1) {

// 做相应逻辑

}

}

}

}

3.未正确的使用Context

再有用到Context的地方,尽量使用Application的Context来进行使用,这样可以避免

4.静态VIew

使用静态的view可以避免每次启动Activity都去渲染view,但是静态view会持有Activity的引用,导致Activity无法回收,所以在OnDestory方法中,将view制成NULL

5.监听器没有关闭

很多的系统服务,如TelephoneyMannager,SensorManager等,都需要register和unregister监听,我们需要确保在核实的时候,及时unregister这些监听器,自己手动添加的Listener,要记得在核实的时候及时移除Listerner

6.资源未关闭

如使用Cursor,File等,他们往往都使用了缓冲,会造成内存泄漏。因此,在资源对象不使用的时候,一定要确保他们都close了,并且引用置为null.

7集合未清理

在实际开发过程中难免会有把对象添加到集合容器(比如 ArrayList)中的需求,如果在一个对象使用结束之后未将该对象从该容器中移除掉,就会造成该对象不能被正确回收,从而造成内存泄漏,解决办法当然就是在使用完之后将该对象从容器中移除。

8BitMap对象

临时创建的某个相对比较大的BitMap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的Bitmap,这样能够更快释放原始Bitmap所占用的空间。避免静态变量持有较大的BitMap对象或者其他大的数据对象,如果已经持有,要尽快质控该静态变量。

9WebView

不同的Android版本的webview都有差异,加上厂商定制的rom,webview存在着很大的兼容性问题。只要使用一次webview,内存就不会被释放

通常的解决方法是将webView单独开一个进程,然后使用Aidl与主进程进行通信。然后根据业务要求,销毁webview进程

内存的检测

1.adb shell && Memory Usage

以通过命令 adb shell dumpsys meminfo [package name] 来将指定 package name 的内存信息打印出来,这种模式可以非常直观地看到 Activity 未释放导致的内存泄漏:

或者也可以通过 Android studio 的 Memory Usage 功能进行查看,最后的结果是一样的:

2.利用finalise方法

上面介绍finalise方法时有说过:当GC准备回收一个Java Object(所有Java对象都是Object的子类)的时候,GC会调用这个Object的finalize方法。也就意味着如果某个对象比如activity泄漏的话,那么在退出这个activity时,GC不会调用这个activity的finalize方法,我们可以利用这一点,重写activity的finalise方法,在里面打印一段日志,如下所示:

我们balabala打开很多activity,再返回到主页,通过IDE手动触发几次GC操作,如果这时某个activity没有打印finalise方法里的日志时,说明这个activity就发生内存泄漏了。怎么样?是不是很简单粗暴!

需要注意一点的是,由于重写了finalize方法的对象要第二次GC才能会真正被GC回收,这也是一种泄漏,所以检查完内存泄漏后,别忘了删除重写的finalise方法。

3.Allocation Tracker

Android studio 还自带一个 Allocation Tracker 工具,功能和 DDMS 中的基本差不多,这个工具可以监控一段时间之内的内存分配:

在内存图中点击途中标红的部分,启动追踪,再次点击就是停止追踪,随后自动生成一个 .alloc 文件,这个文件就记录了这次追踪到的所有数据,然后会在右上角打开一个数据面板:

4.Android Memory Monitor

Memory Monitor 是 Android Studio 自带的一个监控内存使用状态的工具,入口如下所示:

// TODO 这里需要图片

在 Android Monitor 点开之后 logcat 的右侧就是 Monitor 工具,其中可以检测内存、CPU、网络等内容,我们这里只用到了 Memory Monitor 功能,点击红色箭头所指的区域,就会 dump 此时此刻的 Memory 信息,并且生成一个 .hprof 文件,dump 完成之后会自动打开这个文件的显示界面,如果没有打开,可以通过点击最左侧的 Capture 界面或者 Tool Window 里面的 Capture 进入 dump 的 .hprof 文件列表:

首先左上角的下拉框,可以选择 App Heap、Image Heap 和 Zygote Heap,对应的就是上篇博客讲到的 Allocation Space,Image Space 和 Zygote Space,我们这里选择 Allocation Space,然后第二个选择 PackageTreeView 这一项,展开之后就能看见一个树形结构了,然后继续展开我们应用包名的对应对象,就可以很清晰的看到有多少个 Activity 对象了,然后可以点击展开右侧的 Analyzer Tasks 项,勾选上需要检测的任务,然后系统就会给你分析出结果:

从分析的结果可以看到泄漏的 Activity 有两个,非常直观,然后点开其中一个,观察下面的 ReferenceTree 选项:

可以看到 Thread 对象持有了 SecondActivity 对象的引用,也就是 GC Root 持有了该 Activity 的引用,导致这个 Activity 无法回收,问题的根源我们就发现了,接下来去处理它就好了。

5.MAT

MAT(Memory Analyzer Tools)是一个 Eclipse 插件,它是一个快速、功能丰富的 Java heap 分析工具,它可以帮助我们查找内存泄漏和减少内存消耗,MAT 插件的下载地址:Eclipse Memory Analyzer Open Source Project,上面通过 Android studio 生成的 .hprof 文件因为格式稍有不同,所以需要经过一个简单的转换,然后就可以通过 MAT 去打开了:   通过 MAT 去打开转换之后的这个文件:

用的最多的就是 Histogram 功能,点击 Actions 下的 Histogram 项就可以得到 Histogram 结果:

我们可以在左上角写入一个正则表达式,然后就可以对所有的 Class Name 进行筛选了,很方便,顶栏展示的信息 “Objects” 代表该类名对象的数量,剩下的 “Shallow Heap” 和 “Retained Heap” 则和 Android Memory Monitor 类似。咱们接着点击 SecondActivity,然后右键:

在弹出来的菜单中选择 List objects->with incoming references 将该类的实例全部列出来:

通过这个列表我们可以看到 SecondActivity@0x12faa900 这个对象被一个 this$00x12c65140 的匿名内部类对象持有,然后展开这一项,发现这个对象是一个 handler 对象:

快速定位找到这个对象没有被释放的原因,可以右键 Path to GC Roots->exclude all phantom/weak/soft etc. references 来显示出这个对象到 GC Root 的引用链,因为强引用才会导致对象无法释放,所以这里我们要排除其他三种引用:

这么处理之后的结果就很明显了:

一个非常明显的强引用持有链,GC Root 我们前面的博客中说到包含了线程,所以这里的 Thread 对象 GC Root 持有了 SecondActivity 的引用,导致该 Activity 无法被释放。

MAT 还有一个功能就是能够对比两个 .hprof 文件,将两个文件都添加到 Compare Basket 里面:    添加进去之后点击右上角的 ! 按钮,然后就会生成两个文件的对比:

同样适用正则表达式将需要的类筛选出来:

结果也很明显,退出 Activity 之后该 Activity 对象未被回收,仍然在内存中,或者可以调整对比选项让对比结果更加明显:

也可以对比两个对象集合,方法与此类似,都是将两个 Dump 结果中的对象集合添加到 Compare Basket 中去对比,找出差异后用 Histogram 查询的方法找出 GC Root,定位到具体的某个对象上。

6.LeakCanary

LeakCanary可能是上面几个工具中最好用的了

使用后如下图

289b4cf733b4

image.png

可以检测到某个Activity有内存泄漏,然后LeakCanary就会给出提示

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值