10 January 2017
在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。 内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。
如果持有对象的强引用,垃圾回收器是无法再内存中回收这个对象,例如最容易引发内存泄露的Context
。
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();
}
});
在经常使用到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();
}
});
在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();
}
});
匿名类维持了外部的引用。在异步任务中执行的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();
}
});
定义匿名的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();
}
});
两个线程进行循环,或者使用定时器。
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();
}
});
只要是匿名类的实例,不管是不是在工作线程,都会持有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();
}
});
通过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。
在 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 中的Monitor中Dump java heap可以检查当前进程中存在的内存泄露。然后再生成的.hprof文件中,点击Analyzer Tasks中detected leaked activities,然后可以看到出现的内存泄露。
对于底层so包出现的内存泄漏,导致APP不能正常使用,可以使用shell控制台执行so包中的文件,来将内存泄漏的方法交给系统来执行。
— Kong Jing