title | date | categories | tags | description | ||||
---|---|---|---|---|---|---|---|---|
Android中常见的内存泄漏 |
2017-07-11 |
|
|
|
由于单例的静态特性使得其生命周期跟应用Application的生命周期一样长,所以如果使用不恰当的话,很容易造成内存泄漏。比如:工具类CommonUtil, 当调用getInstance创建这个单例的时候, 由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:
- 如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。
- 如果此时传入的是Activity 的 Context, 当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了(Activity泄漏了)。
因此, 使用CommonUtil的时候要传入Application的Context , 或者将下面的getInstance方法改写成 instance = new CommonUtil(Context.getApplicationContext());
public class CommonUtil {
private static CommonUtil instance;
private Context context;
private CommonUtil(Context context) {
this.context = context;
}
public static CommonUtil getInstance(Context context) {
if (instance == null) {
instance = new CommonUtil(context);
}
return instance;
}
}
比如:
public class MainActivity extends AppCompatActivity {
private static TestResource sResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(sResource == null){
sResource = new TestResource();
}
//...
}
class TestResource {
//...
}
}
在Activity内部创建了一个非静态内部类TestResource的static对象(单例), 这样会造成内存泄漏, 因为在sResource对象中中会持有外部类MainActivity的引用, 而该对象又是一个static的, 该实例的生命周期和应用的一样长,这就导致了该静态实例mResource一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。正确的做法为: 将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例,如果需要使用Context,请使用ApplicationContext。
Android开发经常会在Activity、Fragment中使用多线程,此时如果你使用了匿名类,也容易造成内存泄漏
public class MainActivity extends Activity {
...
@Override
public void onCreate(Bundle savedInstanceState, PersistableBundle persistentState) {
super.onCreate(savedInstanceState, persistentState);
// 代码1
new (new Runnable() {
@Override
public void run() {
...
}
}).start();
// 代码2
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}.execute();
}
// 代码3
Runnable ref2 = new Runnable() {
@Override
public void run() {
}
};
...
}
代码1中的Runnable 和 代码2 中 的 AsyncTask 都是一个匿名内部类,因此它们的匿名对象对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。
代码3中的ref2使用了内部类, 内部类对象ref2会持有外部类的引用, 也就是说当前的Activity实例会被ref2持有, 如果在线程中执行一个很长时间的操作, 当Activity销毁后, 线程还在执行, 这时候因为ref2还持有Activity的引用, 所以Activity占用的内存不会被释放, 造成Activity泄漏。
正确的做法还是使用静态内部类的方式,如下:
static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private WeakReference<Context> weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
//——————
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();
这样就避免了Activity的内存资源泄漏,当然在Activity销毁时候也应该取消相应的任务AsyncTask::cancel(),避免任务在后台执行浪费资源。
Handler的使用造成的内存泄漏问题应该说最为常见了,平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理,对于Handler的使用代码编写一不规范即有可能造成内存泄漏,如下示例:
public class MainActivity extends AppCompatActivity {
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 加载网络数据
loadData();
}
private void loadData(){
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
这种创建Handler的方式会造成内存泄漏,由于mHandler是Handler的非静态匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,那么当这个Activity退出时消息队列中还有未处理的消息或者正在处理消息(因为handler.postDelay 可以延时发送消息),而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,所以导致该Activity的内存资源无法及时回收,引发内存泄漏,所以另外一种做法为:
public class MainActivity extends AppCompatActivity {
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
// 加载数据
loadData();
}
private void loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
}
推荐使用静态内部类 + WeakReference 这种方式。每次使用前注意判空。
软/弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收(上面例子中的Activity),Java虚拟机就会把这个软引用加入到与之关联的引用队列中。利用这个队列可以得知被回收的软/弱引用的对象列表,从而为缓冲器清除已失效的软/弱引用。
创建一个静态Handler内部类,然后对Handler持有的对象使用弱引用,这样在回收时也可以回收Handler持有的对象,这样虽然避免了Activity泄漏,不过Looper线程的消息队列中还是可能会有待处理的消息,所以我们在Activity的Destroy时或者Stop时应该移除消息队列中的消息,更准确的做法如下:
public class MainActivity extends AppCompatActivity {
private MyHandler mHandler = new MyHandler(this);
private TextView mTextView ;
private static class MyHandler extends Handler {
private WeakReference<Context> reference;
public MyHandler(Context context) {
reference = new WeakReference<>(context);
}
@Override
public void handleMessage(Message msg) {
MainActivity activity = (MainActivity) reference.get();
if(activity != null){
activity.mTextView.setText("");
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView)findViewById(R.id.textview);
loadData();
}
private void loadData() {
//...request
Message message = Message.obtain();
mHandler.sendMessage(message);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。
BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。在android中,资源性对象比如Cursor、File、Bitmap、视频等,系统都用了一些缓冲技术,在使用这些资源的时候,如果我们确保自己不再使用这些资源了,要及时关闭,否则可能引起内存泄漏。因为有些操作不仅仅只是涉及到Dalvik虚拟机,还涉及到底层C/C++等的内存管理,不能完全寄希望虚拟机帮我们完成内存管理。 在这些资源不使用的时候,记得调用相应的类似close()、destroy()、recycler()、release()等函数,这些函数往往会通过jni调用底层C/C++的相应函数,完成相关的内存释放。
-
BraodcastReceiver分为静态注册和动态注册, 静态注册这种方式的注册是常驻型的,也就是说当应用关闭后,如果有广播信息传来,MyReceiver也会被系统调用而自动运行。注册方式与静态注册相反,不是常驻型的,也就是说广播会跟随程序的生命周期。在实际应用中,我们在Activity或Service中注册了一个BroadcastReceiver,当这个Activity或Service被销毁时如果没有解除注册,系统会报一个异常,提示我们是否忘记解除注册了。所以,记得在特定的地方执行解除注册操作。
-
File 、Stream 要记得 close()
-
Cursor 要记得 close()
-
Bitmap 要记得recycle()
-
自定义控件中的自定义属性要记得 recycle()
比如:
- addOnGlobalLayoutListener后要记得remove
openImageView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
openImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
- register 后 要记得unregister , 因为观察者内部可能维护了一个List 或者Map
- 在andorid开发中,我们经常会在Activity的onCreate中注册广播接受器、EventBus等,如果忘记成对的使用反注册,可能会引起内存泄漏。开发过程中应该养成良好的相关,在onCreate或onResume中注册,要记得相应的在onDestroy或onPause中反注册。
- setOnClick 中, 监听完成后, 系统自动移除了, 所以不需要再自己移除
如果成员变量被声明为 static,那我们都知道其生命周期将与整个app进程生命周期一样。
这会导致一系列问题,如果你的app进程设计上是长驻内存的,那即使app切到后台,这部分内存也不会被释放。按照现在手机app内存管理机制,占内存较大的后台进程将优先回收,如果此app做过进程互保保活,那会造成app在后台频繁重启。当手机安装了你参与开发的app以后一夜时间手机被消耗空了电量、流量,你的app不得不被用户卸载或者静默。
这里修复的方法是:
不要在类初始时初始化静态成员。可以考虑lazy初始化。 架构设计上要思考是否真的有必要这样做,尽量避免。如果架构需要这么设计,那么此对象的生命周期你有责任管理起来。
1、finalize 方法被执行的时间不确定,不能依赖与它来释放紧缺的资源。时间不确定的原因是: 虚拟机调用GC的时间不确定 Finalize daemon线程被调度到的时间不确定
2、finalize 方法只会被执行一次,即使对象被复活,如果已经执行过了 finalize 方法,再次被 GC 时也不会再执行了,原因是:
含有 finalize 方法的 object 是在 new 的时候由虚拟机生成了一个 finalize reference 在来引用到该Object的,而在 finalize 方法执行的时候,该 object 所对应的 finalize Reference 会被释放掉,即使在这个时候把该 object 复活(即用强引用引用住该 object ),再第二次被 GC 的时候由于没有了 finalize reference 与之对应,所以 finalize 方法不会再执行。
3、含有Finalize方法的object需要至少经过两轮GC才有可能被释放。
我们通常会把一些对象装入到集合中,当不使用的时候一定要记得及时清理集合,让相关对象不再被引用。如果集合是static、不断的往里面添加东西、又忘记去清理,肯定会引起内存泄漏。