Android内存泄漏原因
从Java上看,主要有3个原因:
listener:设置监听,在一个类中设置成员变量并把另一个类作为listener给set进去:mListener = setListener(listener);,但在不需要时没有set为null,导致一直持有该对象强引用无法被回收。外部的类没有被回收,所以内部的listener也没有被回收。
inner-class:内部类持有外部的引用,例如持有Acticity的this,解决方法是对应的需要什么就传什么,避免持有外部引用无法被销毁。
thread:无论是内部类的thread还是外部的thread,根本原因还是由1、2造成,例如等待网络回调等,导致持有对象无法被销毁。
从Android上看,主要有2个原因:
全局进程(process-global)的static变量。这个无视应用的状态,持有Activity的强引用。
活在Activity生命周期之外的线程。没有清空对Activity的强引用。
Android内存泄漏的可能的八种情况
以下情况需关注是否会内存泄漏:Static Activities、Static Views、Inner Classes、Anonymous Classes、Handler、Threads、TimerTask、Sensor Manager
一般内存泄漏:忘记释放分配的内存,例如cursor
逻辑内存泄漏:当应用不再需要这个对象或结束生命周期,仍未释放该对象的所有引用
在Android中,导致潜在内存泄漏的陷阱:
全局进程的static变量,这个无视应用的状态,持有Activity的强引用
活在Activity生命周期外的线程,没有清空对Activity的强引用
Static Activities
在类中定义了静态Activity变量,把当前运行的Activity实例赋值于这个静态变量。这个静态变量在Activity生命周期结束后没有被清空就导致了内存泄漏,因为static变量是贯穿这个应用的生命周期的,所以被泄漏的Activity就会一直存在于应用的进程中,不会被垃圾回收器回收。
1 2 3 4 5 6 7 8 9 10 11 12
| static Activity activity; void setStaticActivity() { activity = this; } View sButton = findViewById(R.id.button); sButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setStaticActivity(); nextActivity(); } });
|
解决办法:弱引用不会阻止对象的内存释放,所以即使有弱引用的存在,该对象也可以被回收。
1 2 3 4
| private static WeakReference<MainActivity> activityReference; void setStaticActivity() { activityReference = new WeakReference<MainActivity>(this); }
|
Static Views
常发生在单例模式中,如果Activity经常被用到,那么在内存中保存一个实例是很实用的。但脱离Activity生命周期的操作是很危险的。
特殊情况:如果一个View初始化耗费大量资源,而且在一个Activity生命周期内保持不变,那么可以把它变成static加载到视图树上(View Hierarchy)。但在Activity被onDestroy()时,应当将这个static view置null。(虽然但是,不建议使用static view)
1 2 3 4 5 6 7 8 9 10 11 12
| static View view; void setStaticView() { view = findViewById(R.id.button); } View sButton = findViewById(R.id.button); sButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setStaticView(); nextActivity(); } });
|
解决办法:弱引用是个有效的解决方法,然而还有另一种方法是在生命周期结束时清除引用,Activity#onDestroy()方法就很适合把引用置空。
1 2 3 4 5 6 7 8 9 10 11
| private static View view; @Override public void onDestroy() { super.onDestroy(); if (view != null) { unsetStaticView(); } } void unsetStaticView() { view = null; }
|
Inner Classes
Activity中有个内部类,这样做可以提高可读性和封装性。但如果这个内部类持有一个静态变量的引用,便很容易内存泄漏(除非销毁时置null)。内部类的优势之一就是可以访问外部类,不幸的是,导致内存泄漏的原因,就是内部类持有外部类实例的强引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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(); } });
|
解决办法:开发者必须注意少用非静态内部类,因为非静态内部类持有外部类的隐式引用,容易导致意料之外的泄漏。然而内部类可以访问外部类的私有变量,只要我们注意引用的生命周期,就可以避免意外的发生。避免静态变量。
1 2 3 4 5 6 7
| private Object inner; void createInnerClass() { class InnerClass {
} inner = new InnerClass(); }
|
Anonymous Classes
如果匿名类也维护了外部类的引用,在Activity中定义了匿名的AsyncTask,就容易发生内存泄漏。当异步任务在后台执行耗时任务期间,Activity被销毁了(用户退出、系统回收),这个被AsyncTask持有的Activity实例就不会被GC回收,直到异步任务结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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(); } });
|
解决办法:匿名类和内部类一样,只要不跨越生命周期是完全没问题的。但是,这些类是用于产生后台线程的,这些Java线程是全局的,而且持有创建者的引用(即匿名类的引用),而匿名类又持有外部类的引用。线程是可能长时间运行的,所以一直持有Activity的引用导致当销毁时无法回收。我们不能通过移除静态成员变量解决,因为线程是于应用生命周期相关的。为了避免泄漏,必须舍弃简洁偷懒的写法,把子类声明为静态内部类。(静态内部类不持有外部类的引用,打破了链式引用。)
1 2 3 4 5 6 7 8 9
| private static class NimbleTask extends AsyncTask<Void, VoiVoid> { @Override protected Void doInBackground(Void... params) { while(true); } } void startAsyncTask() { new NimbleTask().execute(); }
|
Handler
定义了匿名的Runnable,用匿名类Handler执行。Runnable内部类会持有外部类的隐式引用,被传递到Handler的消息队列MessageQueue中,在Message消息没有被处理之前,Activity销毁导致了内存泄漏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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(); } });
|
解决办法:匿名类和内部类一样,只要不跨越生命周期是完全没问题的。但是,这些类是用于产生后台线程的,这些Java线程是全局的,而且持有创建者的引用(即匿名类的引用),而匿名类又持有外部类的引用。线程是可能长时间运行的,所以一直持有Activity的引用导致当销毁时无法回收。我们不能通过移除静态成员变量解决,因为线程是于应用生命周期相关的。为了避免泄漏,必须舍弃简洁偷懒的写法,把子类声明为静态内部类。(静态内部类不持有外部类的引用,打破了链式引用。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private static class NimbleHandler extends Handler { @Override public void handleMessage(Message message) { super.handleMessage(message); } } private static class NimbleRunnable implements Runnable { @Override public void run() { while(true); } } void createHandler() { new NimbleHandler().postDelayed(new NimbleRunnable(), Long.MAX_VALUE >> 1); }
|
Threads
通过Thread和TimerTask来展现内存泄漏
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 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(); } });
|
解决办法:使用静态内部类,不持有外部类的引用,打破了链式引用。但是,如果坚持使用匿名类,只要在生命周期结束时中断线程就可以。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| private Thread thread; @Override public void onDestroy() { super.onDestroy(); if (thread != null) { thread.interrupt(); } } void spawnThread() { thread = new Thread() { @Override public void run() { while (!isInterrupted()) { } } }; thread.start(); }
|
TimerTask
只要是匿名类的实例,不管是不是在工作线程,都会持有Activity的引用,导致内存泄漏。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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(); } });
|
解决办法:匿名类和内部类一样,只要不跨越生命周期是完全没问题的。但是,这些类是用于产生后台线程的,这些Java线程是全局的,而且持有创建者的引用(即匿名类的引用),而匿名类又持有外部类的引用。线程是可能长时间运行的,所以一直持有Activity的引用导致当销毁时无法回收。我们不能通过移除静态成员变量解决,因为线程是于应用生命周期相关的。为了避免泄漏,必须舍弃简洁偷懒的写法,把子类声明为静态内部类。(静态内部类不持有外部类的引用,打破了链式引用。)
1 2 3 4 5 6 7 8 9
| private static class NimbleTimerTask extends TimerTask { @Override public void run() { while(true); } } void scheduleTimer() { new Timer().schedule(new NimbleTimerTask(), Long.MAX_VALUE >> 1); }
|
Sensor Manager
通过Context.getSystemService(int name)可以获取系统服务。这些服务工作在各自的进程中,帮助应用处理后台任务,处理硬件交互。如果需要使用这些服务,可以注册监听器,这会导致服务持有了Context的引用,如果在Activity销毁的时候没有注销这些监听器,会导致内存泄漏。
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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(); } });
|
解决办法:在Activity结束时注销监听器
1 2 3 4 5 6 7 8 9 10 11 12
| private SensorManager sensorManager; private Sensor sensor; @Override public void onDestroy() { super.onDestroy(); if (sensor != null) { unregisterListener(); } } void unregisterListener() { sensorManager.unregisterListener(this, sensor); }
|