Kong Jing

Android内存泄漏处理经验

10 January 2017

在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。 内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

如果持有对象的强引用,垃圾回收器是无法再内存中回收这个对象,例如最容易引发内存泄露的Context

Static Activities

Activity中全局进程的static变量,判断没有清空对Activity的强引用。

static Activity activity;
void setStaticActivity() {
  activity = this;
}

View saButton = findViewById(R.id.sa_button);
saButton.setOnClickListener(new View.OnClickListener() {
  @Override public void onClick(View v) {
    setStaticActivity();
    nextActivity();
  }
});

Static Views

在经常使用到Activity的时候,保存一个静态的实例还是很有用的。强制延长活动的生命周期是不必要的。如果View初始化耗费大量资源,可以将它变为static,view设置为null则可以释放内存。

static view;
void setStaticView() {
  view = findViewById(R.id.sv_button);
}

View svButton = findViewById(R.id.sv_button);
svButton.setOnClickListener(new View.OnClickListener() {
  @Override public void onClick(View v) {
    setStaticView();
    nextActivity();
  }
});

Inner Classes

在Activity中有一个内部类,持有了一个静态的引用。

private static Object inner;
void createInnerClass() {
    class InnerClass {
    }
    inner = new InnerClass();
}

View icButton = findViewById(R.id.ic_button);
icButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        createInnerClass();
        nextActivity();
    }
});

Anonymous Classes

匿名类维持了外部的引用。在异步任务中执行的Activity被销毁后不会回收。

void startAsyncTask() {
    new AsyncTask<Void, Void, Void>() {
        @Override protected Void doInBackground(Void... params) {
            while(true);
        }
    }.execute();
}

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View aicButton = findViewById(R.id.at_button);
aicButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        startAsyncTask();
        nextActivity();
    }
});

Handlers

定义匿名的Runnable,用匿名类Handler执行,Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列中,在消息队里未被处理之前,Activity实例不会被销毁。

void createHandler() {
    new Handler() {
        @Override public void handleMessage(Message message) {
            super.handleMessage(message);
        }
    }.postDelayed(new Runnable() {
        @Override public void run() {
            while(true);
        }
    }, Long.MAX_VALUE >> 1);
}


View hButton = findViewById(R.id.h_button);
hButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        createHandler();
        nextActivity();
    }
});

Threads

两个线程进行循环,或者使用定时器。

void spawnThread() {
    new Thread() {
        @Override public void run() {
            while(true);
        }
    }.start();
}

View tButton = findViewById(R.id.t_button);
tButton.setOnClickListener(new View.OnClickListener() {
  @Override public void onClick(View v) {
      spawnThread();
      nextActivity();
  }
});

Timer Tasks

只要是匿名类的实例,不管是不是在工作线程,都会持有Activity的引用,导致内存泄漏。

void scheduleTimer() {
    new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            while(true);
        }
    }, Long.MAX_VALUE >> 1);
}

View ttButton = findViewById(R.id.tt_button);
ttButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        scheduleTimer();
        nextActivity();
    }
});

Sensor Manager

通过Context.getSystemService(int name)可以获取系统服务,服务吃用了Context之后,如果在Activity销毁的时候没有注销这些监听器,那么会导致内存泄露。

void registerListener() {
       SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
       Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
       sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}

View smButton = findViewById(R.id.sm_button);
smButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        registerListener();
        nextActivity();
    }
});

这些是容易发生内存泄露的情况。

LeakCanary

一个内存泄露检测库LeakCanary

在 build.gradle:

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

Application类中:

public class ExampleApplication 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);
    // Normal app init code...
  }
}

然后可以在手机设备上面使用,查看生成的内存泄露的应用,可以查看具体产生内存泄露的位置。

如果没有在安装后出现新的图标,那么就要安装Debug版本的apk。

Android Studio工具检查

Android Studio 中的Monitor中Dump java heap可以检查当前进程中存在的内存泄露。然后再生成的.hprof文件中,点击Analyzer Tasks中detected leaked activities,然后可以看到出现的内存泄露。

对于底层so包出现的内存泄漏,导致APP不能正常使用,可以使用shell控制台执行so包中的文件,来将内存泄漏的方法交给系统来执行。

Compound interest is the most powerful force in the universe

— Kong Jing