Android内存优化全解

要学习Android的内存优化,首先要了解Java虚拟机。Android基于的是Dalvik虚拟机,简称DVM,与Java虚拟机JVM并不一样。另外,Android 4.4以后基于的是ART虚拟机;

1、DVM:DVM是基于寄存器的,它没有基于栈的虚拟机在拷贝数据而使用的大量的出入栈指令,同时指令更紧凑更简洁。执行的字节码是.dex形式的,DVM会用dx工具将所有的.class文件转换为一个.dex文件,然后DVM会从该.dex文件读取指令和数据。执行顺序为:.java文件 –>.class文件-> .dex文件

2、JVM:基于栈读写数据,执行顺序为: .java文件 -> .class文件 -> .jar文件

3、ART:Android在4.4时候推出ART,在5.0以后正式采用ART的,在ART中,系统在安装应用时会进行一次预编译(AOT,ahead of time),将字节码预先编译成机器码并存储在本地,这样应用每次运行时就不需要执行编译了,运行效率也大大提升。


内存泄漏向来是内存优化的重点,因此在Android开发中,如何避免、发现和解决内存泄漏就变得尤为重要。

首先,我们应该避免可控的内存泄漏:

Android内存优化全解

从上图看以看出,Obj4是可达的对象,表示它正被引用,因此不会标记为可回收的对象。Obj5、Obj6和Obj7都是不可达的对象,其中Obj5和Obj6虽然互相引用,但是因为他们到GC Roots是不可达的所以它们仍旧会标记为可回收的对象。

内存泄漏产生的原因,主要分为三大类:
1.由开发人员自己编码造成的泄漏。
2.第三方框架造成的泄漏。
3.由Android 系统或者第三方ROM造成的泄漏。

而往往第二、第三种内存泄漏不可控,那么我们应该首先做到在编码中尽量减少由于自身原因造成的泄漏

内存泄漏的几大场景

1 非静态内部类的静态实例

非静态内部类会持有外部类实例的引用,如果非静态内部类的实例是静态的,就会间接的长期维持着外部类的引用,阻止被系统回收。


public class SecondActivity extends AppCompatActivity {
private static Object inner;
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.bt_next);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
createInnerClass();
finish();
}
});
}
void createInnerClass() {
class InnerClass {
}
inner = new InnerClass();//1
}
}

当点击Button时,会在注释1处创建了非静态内部类InnerClass的静态实例inner,该实例的生命周期会和应用程序一样长,并且会一直持有SecondActivity 的引用,导致SecondActivity无法被回收。

2 匿名内部类的静态实例

和前面的非静态内部类一样,匿名内部类也会持有外部类实例的引用。


public class AsyncTaskActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async_task);
button = (Button) findViewById(R.id.bt_next);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startAsyncTask();
finish();
}
});
}
void startAsyncTask() {
new AsyncTask<Void, Void, Void>() {//1
@Override
protected Void doInBackground(Void... params) {
while (true) ;
}
}.execute();
}
}

在注释1处实例化了一个AsyncTask,当AsyncTask的异步任务在后台执行耗时任务期间,AsyncTaskActivity 被销毁了,被AsyncTask持有的AsyncTaskActivity实例不会被垃圾收集器回收,直到异步任务结束。
解决办法就是自定义一个静态的AsyncTask,如下所示。


public class AsyncTaskActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_async_task);
button = (Button) findViewById(R.id.bt_next);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startAsyncTask();
finish();
}
});
}
void startAsyncTask() {
new MyAsyncTask().execute();
}
private static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
while (true) ;
}
}
}

与AsyncTask类似的还有TimerTask,这里就不再举例。

3 Handler内存泄漏

Handler的Message被存储在MessageQueue中,有些Message并不能马上被处理,它们在MessageQueue中存在的时间会很长,这就会导致Handler无法被回收。如果Handler 是非静态的,则Handler也会导致引用它的Activity或者Service不能被回收。


public class HandlerActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
button = (Button) findViewById(R.id.bt_next);
final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHandler.sendMessageDelayed(Message.obtain(), 60000);
finish();
}
});
}
}

Handler 是非静态的匿名内部类的实例,它会隐性引用外部类HandlerActivity 。上面的例子就是当我们点击Button时,HandlerActivity 会finish,但是Handler中的消息还没有被处理,因此HandlerActivity 无法被回收。
解决方法就是要使用一个静态的Handler内部类,Handler持有的对象要使用弱引用,并且在Activity的Destroy方法中移除MessageQueue中的消息,如下所示。


public class HandlerActivity extends AppCompatActivity {
private Button button;
private MyHandler myHandler = new MyHandler(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
button = (Button) findViewById(R.id.bt_next);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
myHandler.sendMessageDelayed(Message.obtain(), 60000);
finish();
}
});
}
public void show() {
}
private static class MyHandler extends Handler {
private final WeakReference<HandlerActivity> mActivity;
public MyHandler(HandlerActivity activity) {
mActivity = new WeakReference<HandlerActivity2>(activity);
}
@Override
public void handleMessage(Message msg) {
if (mActivity != null && mActivity.get() == null) {
mActivity.get().show();
}
}
}
@Override
public void onDestroy() {
if (myHandler != null) {
myHandler.removeCallbacksAndMessages(null);
}
super.onDestroy();
}
}

MyHandler是一个静态的内部类,它持有的 HandlerActivity对象使用了弱引用,并且在onDestroy方法中将Callbacks和Messages全部清除掉。
如果觉得麻烦,也可以使用避免内存泄漏的Handler开源库WeakHandler

4 未正确使用Context

对于不是必须使用Activity Context的情况(Dialog的Context就必须是Activity Context),我们可以考虑使用Application Context来代替Activity的Context,这样可以避免Activity泄露,比如如下的单例模式:


public class AppSettings {
private Context mAppContext;
private static AppSettings mAppSettings = new AppSettings();
public static AppSettings getInstance() {
return mAppSettings;
}
public final void setup(Context context) {
mAppContext = context;
}
}

mAppSettings作为静态对象,其生命周期会长于Activity。当进行屏幕旋转时,默认情况下,系统会销毁当前Activity,因为当前Activity调用了setup方法,并传入了Activity Context,使得Activity被一个单例持有,导致垃圾收集器无法回收,进而产生了内存泄露。
解决方法就是使用Application的Context:


public final void setup(Context context) {
mAppContext = context.getApplicationContext();
}

5 静态View

使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致Activity无法被回收,解决的办法就是在onDestory方法中将静态View置为null。


public class SecondActivity extends AppCompatActivity {
private static Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = (Button) findViewById(R.id.bt_next);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
}

6 WebView

不同的Android版本的WebView会有差异,加上不同厂商的定制ROM的WebView的差异,这就导致WebView存在着很大的兼容性问题。WebView都会存在内存泄漏的问题,在应用中只要使用一次WebView,内存就不会被释放掉。通常的解决办法就是为WebView单开一个进程,使用AIDL与应用的主进程进行通信。WebView进程可以根据业务需求,在合适的时机进行销毁。

7 资源对象未关闭

资源对象比如Cursor、File等,往往都用了缓冲,不使用的时候应该关闭它们。把他们的引用置为null,而不关闭它们,往往会造成内存泄漏。因此,在资源对象不使用时,一定要确保它已经关闭,通常在finally语句中关闭,防止出现异常时,资源未被释放的问题。

8 集合中对象没清理

通常把一些对象的引用加入到了集合中,当不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就会更加严重。

9 Bitmap对象

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

10 监听器未关闭

很多系统服务(比如TelephonyMannager、SensorManager)需要register和unregister监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的Listener,要记得在合适的时候及时remove这个Listener。


接下来,就是使用内存分析工具对代码中可能存在的内存泄漏进行分析了。我们常用的内存泄漏分析工具有LeakCanary、MAT等等。我主要在项目中使用LeakCanary较多,LeakCanary的使用方法也简单,具体如下:

首先配置build.gradle:


dependencies {
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.2'
releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.2'
}

接下来在Application的onCreate()方法中加入如下代码。


if (LeakCanary.isInAnalyzerProcess(this)) {//1
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
但是以上代码仅仅能检测activity的内存泄漏,因此改写上面的方法,使用RefWatcher来监控检测其他类的泄漏


private RefWatcher refWatcher;
@Override
public void onCreate() {
super.onCreate();
refWatcher= setupLeakCanary();//在oncreate中初始化并设置RefWatcher
}
private RefWatcher setupLeakCanary() {
if (LeakCanary.isInAnalyzerProcess(this)) {
return RefWatcher.DISABLED;
}
return LeakCanary.install(this);
}
public static RefWatcher getRefWatcher(Context context) {
LeakApplication leakApplication = (LeakApplication) context.getApplicationContext();
return leakApplication.refWatcher;
}

install方法会返回RefWatcher用来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。
最后为了举例,我们在一段存在内存泄漏的代码中引入LeakCanary监控,如下所示。


public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LeakThread leakThread = new LeakThread();
leakThread.start();
}
class LeakThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(6 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = LeakApplication.getRefWatcher(this);//1
refWatcher.watch(this);
}
}

MainActivity存在内存泄漏,原因就是非静态内部类LeakThread持有外部类MainActivity的引用,LeakThread中做了耗时操作,导致MainActivity无法被释放。
在注释1处得到RefWatcher,并调用它的watch方法,watch方法的参数就是要监控的对象。当然,在这个例子中onDestroy方法是多余的,因为LeakCanary在调用install方法时会启动一个ActivityRefWatcher类,它用于自动监控Activity执行onDestroy方法之后是否发生内存泄露。这里只是为了方便举例,如果想要监控Fragment,在Fragment中添加如上的onDestroy方法是有用的。
运行程序,这时会在界面生成一个名为Leaks的应用图标。接下来不断的切换横竖屏,这时会闪出一个提示框,提示内容为:“Dumping memory app will freeze.Brrrr.”。再稍等片刻,内存泄漏信息就会通过Notification展示出来,比如三星S8的通知栏如下所示。
Android内存优化全解

Notification中提示了MainActivity发生了内存泄漏, 泄漏的内存为787B。点击Notification就可以进入内存泄漏详细页,除此之外也可以通过Leaks应用的列表界面进入,列表界面如下图所示。

Android内存优化全解

内存泄漏详细页如下图所示。
Android内存优化全解

点击加号就可以查看具体类所在的包名称。整个详情就是一个引用链:MainActiviy的内部类LeakThread引用了LeakThread的this$0this$0的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是详情最后一行所给出的MainActiviy的实例,这将会导致MainActivity无法被GC,从而产生内存泄漏。


参考链接:

http://blog.csdn.net/itachi85/article/details/77826112

http://blog.csdn.net/itachi85/article/details/73522042