避免可控的内存泄漏
内存泄漏是内存优化的重点,如何避免、发现和解决内存泄漏十分重要
1. 何为内存泄漏
每个应用程序都需要内存来完成工作,为了确保Android系统的每个应用都有足够的内存,Android系统需要有效地管理内存分配。内存不足时Android运行就会触发GC,GC采用的垃圾标记算法为根搜索算法。而内存泄漏就是指没有用的对象从GC Roots是可达的,导致GC无法回收该对象。一般产生内存泄漏的原因有三大类
- 开发人员自己编码造成的内存泄漏
- 由三方框架造成的泄漏
- 由Android系统或者第三方ROM造成的泄漏
通常来说,第二种和第三种是不可控的,但第一种情况是可控的,下面举例常见的内存泄漏场景
2. 内存泄漏的场景
非静态内部类的静态实例
非静态内部类会持有外部类实例的引用,如果非静态内部类的实例是静态的,就会间接长期维持外部类的引用,阻止被系统回收,代码如下
public class MainActivity extends AppCompatActivity {
private static Object inner;
private Button button;
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button = findViewById(R.id.button_test);
button.setOnClickListener(v -> {
createInnerClass();
finish();
});
}
void createInnerClass(){
class InnerClass{
}
inner = new InnerClass();
}
}
当点击Button时,会在new InnerClass()处创建非静态内部类InnerClass的静态实例inner,该实例的生命周期会和应用程序一样长,并且一直持有SecondActivity的引用,导致SecondActivity无法被回收
多线程相关的匿名内部类/非静态内部类
和前面的非静态内部类意义,匿名内部类也会持有外部类实例的引用,多线程相关的类有AsyncTask类,Thrad类和实现Runnable接口的类等,他们的匿名内部类/非静态内部类如果做耗时操作就会引起内存泄漏,以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 = findViewById(R.id.button_async);
button.setOnClickListener(v -> {
startAsyncTask();
finish();
});
}
void startAsyncTask(){
new AsyncTask<Void, Void, Void>(){
@Override protected Void doInBackground(Void... voids) {
while(true);
}
}.execute();
}
}
在startAsyncTask方法里实例化一个AsyncTask,其异步任务在后台执行耗时操作期间,整个Activity被销毁,被AsyncTask持有的Activity实例不会被垃圾回收,直到异步任务结束。同理,自定义的AsyncTask如果是非静态内部类也会发生内存泄漏,解决的办法就是自定义一个静态的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 = findViewById(R.id.button_async);
button.setOnClickListener(v -> {
startAsyncTask();
finish();
});
}
void startAsyncTask(){
new MyAsyncTask().execute();
}
private static class MyAsyncTask extends AsyncTask<Void, Void, Void>{
@Override protected Void doInBackground(Void... voids) {
while (true);
}
}
}
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 = findViewById(R.id.button_handler);
final Handler handler = new Handler(){
@Override public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
}
};
button.setOnClickListener(v -> {
handler.sendMessageDelayed(Message.obtain(), 6000);
finish();
});
}
}
Handler是非静态的匿名内部类的实例,它会隐性引用外部类HandlerActivity,这个例子就是当我们点击button时,HandlerActivity会结束,但是Handler中的消息还没有被处理,因此HandlerActivity无法被回收。解决的方案有两个,一个是使用静态Handler内部类,Handler持有的对象要用弱引用,如下
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 = findViewById(R.id.button_handler);
button.setOnClickListener(v -> {
myHandler.sendMessageDelayed(Message.obtain(), 6000);
finish();
});
}
public void show(){}
private static class MyHandler extends Handler{
private final WeakReference<HandlerActivity> activityWeakReference;
public MyHandler(HandlerActivity activity){
activityWeakReference = new WeakReference<>(activity);
}
@Override public void handleMessage(Message message){
if(activityWeakReference != null && activityWeakReference.get() == null){
activityWeakReference.get().show();
}
}
}
}
MyHandler是一个静态内部类,它持有的HandlerActivity对象使用了弱引用,这样就避免了内存泄漏。
还有一个解决方案就是在onDestry生命周期回调中移除MessageQueue中的消息,如下所示
@Override public void onDestroy(){
if(myHandler != null){
myHandler.removeCallbacksAndMessages(null);
}
super.onDestroy();
}
在即将销毁时清除Callbacks和Messages,采用这个方法有可能不能完全清除Handler中的消息,因此还是建议用第一种方式
未正确使用Context
对于不是必须使用Activity的Context的情况,比如Dialog的Context必须使用Activity的Context。我们可以考虑使用Application Context来代替Activity的Context,就可以避免Activity泄漏,比如如下单例模式
public class AppSettings {
private Context appContext;
private static AppSettings appSettings = new AppSettings();
public static AppSettings getInstance(){
return appSettings;
}
public final void setup(Context context){
appContext = context;
}
}
appSettings作为静态对象,其生命周期会长于Activity。当屏幕旋转时,在默认情况下,系统会销毁当前Activity,因为当前Activity调用了setup方法,并传入了Activity的Context,使得Activity被一个单例持有,导致垃圾收集器无法回收,进而产生了内存泄漏,解决方法就是使用Application的Context,如下
public final void setup(Context context){
appContext = context.getApplicationContext();
}
静态View
使用静态View可以避免每次启动Activity都去读取并渲染View,但是静态View会持有Activity的引用,导致Activity无法被回收,解决的办法就是在onDestroy方法中将静态View置为null,代码如下
public class ViewActivity extends AppCompatActivity {
private static Button button;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view);
button = findViewById(R.id.button_view);
button.setOnClickListener(v -> {
finish();
});
}
@Override protected void onDestroy() {
button = null;
super.onDestroy();
}
}
WebView
不同的Android版本里WebView会有差异,不同厂商定制的ROM的WebView也会有差异,这就导致WebView存在很大的兼容性问题,WebView都会存在内存泄漏问题,在应用中使用一次WebView,内存就不会被释放,通常的解决办法就是为WebView单开一个进程,使用AIDL与应用主进程进行通信,WebView进程可以根据业务需求在合适的时机进行销毁
资源对象未关闭
资源对象比如Cursor、File等,往往都使用了缓冲,会造成内存泄漏,因此在资源对象不使用时,一定要确保关闭其引用并置为null,通常在finally语句中进行资源关闭操作
集合中对象未清理
通常把一些对象的引用加入到了集合中,当不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,那情况就会更加严重
BitMap对象
临时创建的某个相对比较大的bitmap对象,在经过变换得到新的bitmap对象之后,应该尽快回收原始的bitmap,这样能够更快释放原始bitmap所占用的空间。避免静态变量持有比较大的bitmap对象或者其他大的数据对象,如果已经持有,要尽快置空该静态变量
监听器未关闭
很多系统服务(比如TelephonyMannager、SensorManager)需要register和unregister监听器,我们需要确保在合适的时候及时unregister那些监听器。自己手动add的Listener,要记得在合适的时候及时remove这个Listener
Memory Monitor
这是AS中内置的内存分析工具,在编辑器下方的Profile中打开,比如我们运行上面案例中任意一个代码,可以看到类似如下的内存变动图
这个工具主要有如下作用
- 实时显示可用的和分配的Java内存图表
- 实时显示垃圾收集事件
- 启动垃圾收集事件
- 快速测试应用程序的缓慢是否与过度的垃圾收集事件有关
- 快速测试应用程序崩溃是否与内存耗尽有关
1. 大内存申请与GC
从上图可以看出,分配的内存急剧上升,这就是大内存分配场景,我们要判断是否是合理内存开销,并且对这种大数据进行优化,减少性能损耗。接下来是急剧下降,这表示垃圾收集事件,用于释放内存
2. 内存抖动
内存抖动一般指在很短的时间内发生了多次内存分配和释放,严重的内存抖动还会导致应用程序卡顿。内存抖动出现原因主要是短时间频繁的创建对象(可能在循环中创建对象),内存为了应对这种情况,也会频繁的进行GC。非并行GC在进行时,其他线程都会被挂起,等待GC操作完成后恢复工作。如果是频繁的GC就会产生大量的暂停时间,这会导致界面绘制时间减少,从而使得多次绘制一帧的时长超过了16ms,产生的现象就是界面卡顿。综合起来就产生了内存抖动,会产生锯齿状的抖动图
LeakCanary
分析内存问题可以使用MAT,一个基于Eclipse的插件,但会有一些难度,并且效率也不是很高,对于一个内存泄漏问题,可能要进行多次排查和对比。为了能够简单迅速的发现内存泄漏,Square公司基于MAT开源了LeakCanary
1. 使用LeakCanary
首先配置build.gradle
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
}
接下来在Application加入如下代码
public class LeakApplication extends Application {
@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
}
}
if条件处的代码用来进行过滤操作,如果当前的进程是用来给LeakCanary进行堆分析的则return,否则会执行LeakCanary的install方法。这样我们就可以使用LeakCanary了,如果检测到某个Activity有内存泄露,LeakCanary就会给出提示
2. LeakCanary应用举例
上面的案例只能检测Activity的内存泄漏,如果还需要检测其他类的内存泄漏,我们就需要RefWatcher来进行监控。改写Application,如下
public class LeakApplication 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 getRefWatcher(Context context){
LeakApplication leakApplication = (LeakApplication) context.getApplicationContext();
return leakApplication.refWatcher;
}
}
install方法会返回RefWatcher来监控对象,LeakApplication中还要提供getRefWatcher静态方法来返回全局RefWatcher。最后为了举例,我们在一段存在内存泄漏的代码中引入LeakCanary监控,如下
public class LeakActivity extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak);
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);
refWatcher.watch(this);
}
}
LeakActivity存在内存泄漏就是因为非静态内部类LeakThread持有外部类的引用,在LeakThread中做耗时操作导致Activity无法释放。拿到refWatcher对象后调用watch方法观察要监控的对象this。当然此演示中onDestroy方法是多余的,因为LeakCanary在调用install方法后会启动一个ActivityRefWatcher类,用于自动监控Activity执行onDestroy方法之后是否发生内存泄漏。这里为了方便举例,如果想监控Fragment,就在Fragment中添加如上的onDestroy方法即可。接下来运行程序,就会在界面上生成Leaks的应用图标。通过不断的切换横竖屏销毁和创建Activity,会闪出一个提示框,通过Notification展示出内存泄漏信息,点击就进入了内存泄漏详情页,会展示一个具体的引用链
MainActiviy的内部类LeakThread引用了LeakThread的this$0,this$0的含义就是内部类自动保留的一个指向所在外部类的引用,而这个外部类就是详情最后一行所给出的MainActiviy的实例,这将会导致MainActivity无法被GC,从而产生内存泄漏
当然,解决方案自然就是将内部类声明为static即可,就不会给出内存泄漏提示了