天天看点

Android性能优化篇(二)——内存泄露                      Android性能优化篇(二)——内存泄露

                      Android性能优化篇(二)——内存泄露

题记:中午和小伙伴聊了聊天,反思了这段时间自己的获与得,与更加明确了前进的方向,加油吧,小废废。

一、Android Profiler

1、功能介绍

2、实例分析

二、LeakCanary

1、使用

2、对比Andorid Profiler

三、常见的内存泄露问题

1、由于Context导致内存泄露

(1)原因:

(2)解决方案:

2、由于注册监听器但是没有取消注册

(1)原因:

(2)解决方案:

3、非静态内部类和匿名内部类

(1)原因:

(2)解决方案:

4、Handler引发的内存泄露

(1)原因:

(2)解决方案:

5、线程导致内存泄露

内存泄露是开发中一个十分常见的问题,也是一个值得重视的问题,是产生OOM的主要原因。在接下来的章节我们将结合实例和检测工具来讲解我们日常中常见的内存泄露问题。工欲善其事必先利其器,首先我们先来学习一些常见的内存泄露工具。

一、Android Profiler

1、功能介绍

Android Profiler是Android studio自带的工具,可以动态检测内存、CPU、网络等,是一款十分强大的组件。如下图

Android性能优化篇(二)——内存泄露                      Android性能优化篇(二)——内存泄露

上图中我们可以看到左侧为我们当前运行的进程,右侧则为动态检测的参数。这里我们双击选择Memory,如下

是不是看起来很复杂,不要慌,接下来带你了解它。

首先上图

Android性能优化篇(二)——内存泄露                      Android性能优化篇(二)——内存泄露

1、指明当前执行的进程

2、纵坐标:占用的内存

3、横坐标:当前执行的时间

4、指明当前是在哪个Activity下,并且当前Activity处于哪个声明周期,是否会有点击或者触摸事件

5、垃圾回收和测量一段时间中javaHeap中的内存变化。其中对于垃圾回收按钮,我们可以点击后,这样系统会执行gc,之后我们分析gc后还有哪些没有释放,如果有,那么就可能存在内存泄露,是我们分析内存泄露十分重要的按钮

6、heap类型:

app heap:当前app使用的heap

image heap:磁盘上当前app的内存映射拷贝

Zygote heap:zygote进程中的heap

7:查看方式

Android性能优化篇(二)——内存泄露                      Android性能优化篇(二)——内存泄露

by class:通过类名来查看

by package:通过包名来查看

by callStack:通过callaback来查看

8、内存类型

9、Allocations:表示当前检测的时间段内heap中为某个类的实例分配内存内存的个数,注意,这里的结果取决于你选择的时间段。例如

Android性能优化篇(二)——内存泄露                      Android性能优化篇(二)——内存泄露

我们在第一次进入MainActiviy时创建了一个MainActivity实例,所以这里Allocations中为1

10、Deallocations:表示当前检测的时间内内存中某个类的实例被回收的的个数

Android性能优化篇(二)——内存泄露                      Android性能优化篇(二)——内存泄露

例如我们退出应用,并且点击强制GC,可以看到有一个MainActivty的实例占用内存被回收,当前heap中deallocation为1,此时totalCount为0

11、totalCount:表示当前检测时间内内存中该类的实例的个数一共为多少个。

在某一段时间内,Allocations<=totalCount,因为可能在这段时间以前就已经创建了部分对象,totalCount记录了这部分对象,而Allocations则只记录当前的对象数目

12、ShallowSize:所有实例占用的内存大小

2、实例分析

现在结合Android启动模式来说明一下这个工具如何分析与使用。

首先定义两个Activity,SwitchActivity和MainActivity,其中SwitchActivty可以跳转到MainActivity,我们的操作是,从SwitchActivity跳转到MainActivity,分析内存;然后连按两次返回并退出,执行强制GC,分析内存。

MainActivity代码如下:

@Override

protected void onCreate(Bundle savedInstanceState)

{

super.onCreate(savedInstanceState);

Intent intent = new Intent(this,MainActivity.class);

startActivity(intent);

}
           

在onCreate中会启动自己。

(1)MainActivity设为标准启动模式

选择从SwitchActivity刚跳转到MainActivity进行分析,如下

可以看到,这里我们在onCreate()调用Intent启动MainActivity自己,会一直生成对象,在检测时间内,一共生成了36个,所以Totalcount为36,而同时这段时间内在内存中创建了35个这样的对象,因此Allocations为35.

(2)mainActivity设置为SingleTask

同样选择从SwitchActivity刚跳转到MainActivity进行分析,如下

可以看到这里只创建了一个MainActivity对象。

因此也证明了标准模式下,每次启动Activity都会创建一个新的对象和新的栈。

而Singtask则会判断当前栈内是否该Activity的对象,有的话会将栈中它上面的Activity清除,使它位于栈顶。

二、LeakCanary

LeakCanary是Square基于MAT而开发的一套内存泄露检测工具。

1、使用

(1)在module的build.gradle中添加依赖:

debugCompile 'com.squareup.leakcanary:leakcanary-android:1.6.1'

releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
           

(2)自定义Applicaiton,继续系统的Application

public class LeakCanaryApplicaiton extends Application {

private RefWatcher refWatcher;

@Override

public void onCreate() {

super.onCreate();

refWatcher = setupLeakCanary();



}



private RefWatcher setupLeakCanary()

{

if(LeakCanary.isInAnalyzerProcess(this))

{

return RefWatcher.DISABLED;

}



return LeakCanary.install(this);

}



public static RefWatcher getReWeather(Context context)

{

LeakCanaryApplicaiton leakCanaryApplicaiton = (LeakCanaryApplicaiton) context.getApplicationContext();

return leakCanaryApplicaiton.refWatcher;

}

}
           

(2)定义一个Activity,存在内存泄露

public class MainActivity extends Activity

{

private LoadingView loadingView;

private Button btn_success, btn_failed;

private Button btn_reset;

private Bitmap back;

MyHandler myHandler;





@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

loadingView = findViewById(R.id.loadingview);

btn_success = findViewById(R.id.btn_success);

btn_failed = findViewById(R.id.btn_failed);

btn_reset = findViewById(R.id.btn_reset);

myHandler = new MyHandler(this);

myHandler.sendEmptyMessageDelayed(0, 1000 * 10);

back = BitmapFactory.decodeResource(getResources(), R.drawable.ic_launcher_background);

}

class MyHandler extends Handler

{

WeakReference<MainActivity> weakReference ;

public MyHandler(MainActivity mainActivity)

{

this.weakReference = new WeakReference<>(mainActivity);

}



@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

}

}



@Override

protected void onDestroy() {

super.onDestroy();

RefWatcher refWatcher = LeakCanaryApplicaiton.getReWeather(this);

if(myHandler!=null) {

refWatcher.watch(myHandler);

}

}

}
           

这里我们定义一个Handler的内部类,由于它持有外部类MainActivity的引用,因此会存在内存泄露。

而在onDestory()中我们获得检测对象RefWatcher 的实例,并且调用该实例的watch()来检测Handler对象。

(3)运行程序,反复操作几次,可以看到Notifacation提示

Android性能优化篇(二)——内存泄露                      Android性能优化篇(二)——内存泄露

同时在桌面上会有一个对应的LeakCanary的图标

Android性能优化篇(二)——内存泄露                      Android性能优化篇(二)——内存泄露

点击LeakCanary图标进入应用,如下:

Android性能优化篇(二)——内存泄露                      Android性能优化篇(二)——内存泄露

可以看到,LeakCanary会根据操作的时间,检测到不同时间段内发生的内存泄露,选择其中一个进入:

Android性能优化篇(二)——内存泄露                      Android性能优化篇(二)——内存泄露

可以发现这里会有详细的说明,提示是那里出现了问题。

2、对比Andorid Profiler

优点:LeakCanary相比Profiler而言,比较直观,易于操作,测试apk不需要和as连接诶;而Profiler则相对复杂,需要连接设备实时检测

缺点:Android Profiler不仅仅可以分析当前应用的内存泄露,同时还可以分析其他应用的内存泄露,而且可以直观的分析到对象的创建和回收,并且可以直接定位到代码的位置。

LeakCanary需要和代码耦合,某个程度上来说已经改变了代码结构,并且无法截取需要的时间段来分析,也无法手动GC。

三、常见的内存泄露问题

1、由于Context导致内存泄露

(1)原因:

这样的场景一般发生过在单例等持有当前Activity的Context引用的场景,由于Activity被外部类持有引用,导致Activcity发生内存泄露而无法回收

例如:

在Activity中调用工具类如下:

SharedUtils.getInstance(mContext).getInt(Constansts.RUN_COUNT)
           

这里的mContext对应的就是Activity本身,这样的情况下由于Activity的引用被工具类持有,就会发生内存泄露。

(2)解决方案:

通常我们可以将Activity的Context换为生命周期更长的Applicaiotn的Context

2、由于注册监听器但是没有取消注册

(1)原因:

我们通常会在Activity中注册对应的监听器,但是有时候我们只是注册监听器,而没有在oNStop中取消注册,从而导致Activity的引用被外部类持有而导致内存泄露。

例如:

在Activity中注册监听器

ChatManager.getInstance().setCallBackListener(this);
           

这里我们传入了this,也就是Activity的实例,但是之后没有取消注册

(2)解决方案:

在onStop中取消注册,如下:

ChatManager.getInstance().setCallBackListener(null);
           

3、非静态内部类和匿名内部类

(1)原因:

非静态内部类和匿名内部类会持有外部的引用,如果他们长期持有外部类的引用,那么可能会导致内存泄露。

例如:

匿名内部类如下:

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

new Thread(new Runnable()

{

public void run()

{

while(true)

{

}

}

})

}
           

这里我们定义一个内部类Thread,而在run()方法中定义一个无限循环,因此线程会一直执行,也就会一直持有外部类的引用,从而导致内存泄露。

非静态内部类

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

MyThread thread = new MyThread();

thread.start();

}

}



class MyThread extends Thread

{

public void run()

{

while(true)

{

}

}
           

这里我们定义一个非静态内部类MyThread,在该类内部会持有外部类的引用。

(2)解决方案:

采用静态内部类来,例如:

static class MyThread extends Thread

{

public void run()

{

while(true)

{

}

}
           

因为静态内部类是相对于外部类而言,而不是外部类的对象,因此不会持有外部类的引用。

4、Handler引发的内存泄露

(1)原因:

本质上来说,原因其实与3相同

常见的泄露场景有两种:

  • 使用Handler的对象,但是使用了延时消息,而延时消息中会持有一个target,也就是外部了类的对象引用

如下:

Handler myHandler= new Handler()

{

}

myHandler.sendEmptyMessageDelayed(0,1000*5);
           
  • 使用Handler的非静态内部类,这里和3相同,不再举例

(2)解决方案:

使用静态自定义Handler内部类和弱引用

如下:

static class MyHandler extends Handler

{

WeakReference<MainActivity> weakReference ;

public MyHandler(MainActivity mainActivity)

{

this.weakReference = new WeakReference<>(mainActivity);

}



@Override

public void handleMessage(Message msg) {

super.handleMessage(msg);

}

}
           

5、线程导致内存泄露

一些长期执行的任务,如果持有外部类的引用,那么就可能导致Activity已经退出,但是线程仍然在执行,从而导致该Activty的引用还在,从而导致内存泄露。

具体看2中提到的。