[关闭]
@linux1s1s 2017-01-22T08:20:28.000000Z 字数 8486 阅读 4487

Android 内存泄露 小结

AndroidMemory 2015-05


阅读这篇博文请阅读 Android 内存泄露 (一)Android 内存泄露 (二)两篇相关的博文。此篇博文主要给出一些内存泄露的Demo,目的是让内存泄露案例更直观易懂,以期在工作中更好的防范内存泄露。

CallBack对象的引用

先看一下代码

  1. private static Drawable sBackground;
  2. @Override
  3. protected void onCreate(Bundle state){
  4. super.onCreate(state);
  5. TextView label =new TextView(this);
  6. label.setText("Leaks are bad");
  7. if(sBackground ==null){
  8. sBackground = getDrawable(R.drawable.large_bitmap);
  9. }
  10. label.setBackgroundDrawable(sBackground);
  11. setContentView(label);
  12. }

上面的代码片段会有 memory-leaks 情况发生吗?
为了回答问题,我们直接追踪setBackgroundDrawable(...)方法。

  1. public void setBackgroundDrawable(Drawable background) {
  2. // ... ...
  3. background.setCallback(this);
  4. // ... ...
  5. }

注意到这里Drawable类有个设置回调的方法,为了说明问题,我们从比较旧的SDK版本说起,然后再对比新的SDK版本,看看这么一个设置回调方法有神马变动。

Froyo & GingerBread 版本setCallback方法

  1. public final void setCallback(Callback cb) {
  2. mCallback = cb;
  3. }

之后的版本是这样的

  1. public final void setCallback(Callback cb) {
  2. mCallback = new WeakReference<Callback>(cb);
  3. }

看到上面两处代码,是不是感觉Google也有马失前蹄的时候,Google的失误从另外一个侧面给我们提供了一个解决内存泄露的方案---对于长生命周期的实例请使用软引用防止内存泄露。所以上面的问题,需要分开来说,对于GingerBread版本之前是存在内存泄露的,而对于该版本之后,已经修复了该泄漏问题。

System Service对象的引用

我们先来看一下Context的实现类ContextImpl部分实现代码

  1. class ContextImpl extends Context {
  2. private final static String TAG = "ContextImpl";
  3. private final static boolean DEBUG = false;
  4. ...
  5. static {
  6. registerService(CONNECTIVITY_SERVICE, new ServiceFetcher() {
  7. public Object createService(ContextImpl ctx) {
  8. IBinder b = ServiceManager.getService(CONNECTIVITY_SERVICE);
  9. return new ConnectivityManager(IConnectivityManager.Stub.asInterface(b));
  10. }});
  11. ...
  12. }
  13. ...
  14. }

上面代码片段是ContextImpl类其中的部分实现,这里重点突出了ConnectivityManager的注册部分,除此之外,系统还有其他服务,这些系统服务在ContextImpl实例初始化的时候,就为此Context注册了一些列系统服务,当我们在App中需要监听网络状况时可以方便的注册Receiver就可以了,就想下面这样。

  1. private class ConnectionReiver extends BroadcastReceiver
  2. {
  3. @Override
  4. public void onReceive(Context context, Intent intent)
  5. {
  6. //首先通过Context实例得到上面注册的系统网络服务
  7. final ConnectivityManager cm = (ConnectivityManager) context
  8. .getSystemService(Context.CONNECTIVITY_SERVICE);
  9. final NetworkInfo networkInfo = cm.getActiveNetworkInfo();
  10. //通过networkInfo实例可以判断当前是否处于连接网络状态
  11. if ((networkInfo != null) && (networkInfo.isConnected()))
  12. {
  13. if (mHasShowDisconnection)
  14. {
  15. //如果当前网络处于连接状态,结束原先通过该MainActivity启动的DisconnectInternetActivity,
  16. finishActivity(DisconnectInternetActivity.REQUESTCODE_DISSCONNECTINTERNET);
  17. mHasShowDisconnection = false;
  18. }
  19. }
  20. else
  21. {
  22. //如果当前网络处于断网状态,那么启动DisconnectInternetActivity,这里有另外一个知识点,MainActivity通过设置request标志启动DisconnectInternetActivity,也可以通过这个request标志结束DisconnectInternetActivity
  23. Intent startIntent = new Intent(MainActivity.this,
  24. DisconnectInternetActivity.class);
  25. intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
  26. startActivityForResult(
  27. startIntent,
  28. DisconnectInternetActivity.REQUESTCODE_DISSCONNECTINTERNET);
  29. mHasShowDisconnection = true;
  30. }
  31. }
  32. }

接下来看看注册的过程

  1. public class MainActivity extends Activity
  2. {
  3. private ConnectionReiver mConnectionReiver;
  4. @Override
  5. protected void onCreate(Bundle savedInstanceState)
  6. {
  7. //直接注册ConnectionReiver,通过Filter标志位 ConnectivityManager.CONNECTIVITY_ACTION监听系统网络状态
  8. registerReceiver(mConnectionReiver = new ConnectionReiver(),
  9. new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
  10. }
  11. }

如果代码写到这里,请问上面有木有啥问题?
讨论这个问题前,先说一下生命周期问题,系统服务生命周期大于App生命周期这点毫无疑问,而上面代码长生命周期持有短生命周期的引用,会导致短生命周期实例所持有的资源释放不了,由此引发内存泄露。

为了避免此类泄露问题,我们应该在每次调用类似registerReceiver方法的时候,顺手加上注销unregisterReceiver方法。

  1. @Override
  2. protected void onDestroy()
  3. {
  4. if (mConnectionReiver != null)
  5. {
  6. unregisterReceiver(mConnectionReiver);
  7. mConnectionReiver = null;
  8. }
  9. }

但是有许多时候,如果我们加上面的注销方法反而会抛出 java.lang.IllegalArgumentException: Receiver not registered ...
这个让我们丈二和尚摸不着头脑,这里说一下原因:

receiver对象没有registerReceiver()成功(没有调用到),于是unregister的时候提示出错:

所以我们对unregisterReceiver包装一下,确保不会引起此类异常:

  1. private void unregisterReceiverSafe(BroadcastReceiver receiver) {
  2. try {
  3. getContext().unregisterReceiver(receiver);
  4. } catch (IllegalArgumentException e) {
  5. // ignore
  6. }
  7. }

Handler对象的引用

由于在Android开发中Handler的使用很频繁,一不小心就会导致内存泄露,关于这方面的典型例子可以参考 Android 内存泄露 (一),不过这里小结一下
造成Handler内存泄露无非有两种情况

  1. 内部类

    • Handler使用的比较多,经常需要在Activity中创建内部类,所以这种场景还是很多的。
    • 内部类持有外部类Activity的引用,当Handler对象有Message在排队,则无法释放,进而导致Activity对象不能释放。

    • 如果是声明为static,则该内部类不持有外部Acitivity的引用,则不会阻塞Activity对象的释放。

    • 如果声明为static后,可在其内部声明一个弱引用(WeakReference)引用外部类。

      1. public class MainActivity extends Activity {
      2. private CustomHandler mHandler;
      3. @Override
      4. protected void onCreate(Bundle savedInstanceState) {
      5. super.onCreate(savedInstanceState);
      6. mHandler = new CustomHandler(this);
      7. }
      8. static class CustomHandlerextends Handler {
      9. // 内部声明一个弱引用,引用外部类
      10. private WeakReference<MainActivity > activityWeakReference;
      11. public MyHandler(MyActivity activity) {
      12. activityWeakReference= new WeakReference<MainActivity >(activity);
      13. }
      14. // ... ...
      15. }
      16. }
  2. Handler生命周期和Activity不同步

    • 其实不单指内部类,而是所有Handler对象,如何解决上面说的Handler对象有Message在排队,而不阻塞Activity对象释放?解决方案也很简单,在Activity onStop或者onDestroy的时候,取消掉该Handler对象的Message和Runnable。通过查看Handler的API,它有几个方法:removeCallbacks(Runnabler)和removeMessages(int what)等。
      1. // 一切都是为了不要让mHandler拖泥带水
      2. @Override
      3. public void onDestroy() {
      4. mHandler.removeMessages(MESSAGE_1);
      5. mHandler.removeMessages(MESSAGE_2);
      6. mHandler.removeMessages(MESSAGE_3);
      7. mHandler.removeMessages(MESSAGE_4);
      8. // ... ...
      9. mHandler.removeCallbacks(mRunnable);
      10. // ... ...
      11. }

    或者干脆点,移除所有的runable和message

    1. @Override
    2. public void onDestroy() {
    3. /**
    4. * Remove any pending posts of callbacks and sent messages whose
    5. * <var>obj</var> is <var>token</var>. If <var>token</var> is null,
    6. * all callbacks and messages will be removed.
    7. */
    8. mHandler.removeCallbacksAndMessages(null);
    9. }

Thread对象的引用

这部分可以参考 Android 内存泄露 (二)

AsyncTask

我们先来看看SDK抛出的一大推英文说明

/**
*

AsyncTask enables proper and easy use of the UI thread. This class allows to
* perform background operations and publish results on the UI thread without
* having to manipulate threads and/or handlers.


*
*

AsyncTask is designed to be a helper class around {@link Thread} and {@link Handler}
* and does not constitute a generic threading framework. AsyncTasks should ideally be
* used for short operations (a few seconds at the most.) If you need to keep threads
* running for long periods of time, it is highly recommended you use the various APIs
* provided by the java.util.concurrent pacakge such as {@link Executor},
* {@link ThreadPoolExecutor} and {@link FutureTask}.

上面大概意思说一下:AsyncTask适用于短耗时操作,最多几秒钟。如果你想长时间耗时操作,请使用其他java.util.concurrent包下的API,比如Executor, ThreadPoolExecutor 和 FutureTask.
所以AsyncTask具有一定的使用范围,如果在长时间耗时操作使用AsyncTask时,尤其要注意和Activity保持同步,针对这点,你可以对所谓的AsyncTask进行包装改良,详情请看 AsyncTask 改良

TimerTask对象

TimerTask对象在和Timer的schedule()方法配合使用的时候极容易造成内存泄露。
```java
private void startTimer(){
if (mTimer == null) {
mTimer = new Timer();
}

if (mTimerTask == null) { 
    mTimerTask = new TimerTask() { 
        @Override 
        public void run() { 
            // todo
        } 
    }; 
} 

if(mTimer != null && mTimerTask != null ) 
    mTimer.schedule(mTimerTask, 1000, 1000); 

}

泄露的点是,忘记cancel掉Timer和TimerTask实例。cancel的时机同cursor篇说的,在合适的时候cancel。
java
private void cancelTimer(){
if (mTimer != null) {
mTimer.cancel();
mTimer = null;
}
}

##Observer对象
Observer对象的泄露,也是一种常见、易发现、易解决的泄露类型。
java
// 其实也非常简单,只不过ContentObserver是系统的例子,有必要单独拿出来提示一下大家,不可掉以轻心
private final ContentObserver mSettingsObserver = new ContentObserver(new Handler()) {
@Override
public void onChange(boolean selfChange, Uri uri) {
// todo
}
};

@Override
public void onStart() {
super.onStart();

// register the observer
getContentResolver().registerContentObserver(Settings.Global.getUriFor(
        xxx), false, mSettingsObserver);

}

@Override
public void onStop() {
super.onStop();

// unregister it when stoping
getContentResolver().unregisterContentObserver(mSettingsObserver);

}

看完示例,我们来看看病例:
java
private final class SettingsObserver implements Observer {
public void update(Observable o, Object arg) {
// todo ...
}
}

mContentQueryMap = new ContentQueryMap(mCursor, Settings.System.XXX, true, null);
mContentQueryMap.addObserver(new SettingsObserver());
```
这里把匿名内部类当做参数传过去相当不合适,所以,有些懒是不能偷的,有些语法糖是不能吃的。解决方案就是,在不需要或退出的时候delete这个Observer。

  1. private Observer mSettingsObserver;
  2. @Override
  3. public void onResume() {
  4. super.onResume();
  5. if (mSettingsObserver == null) {
  6. mSettingsObserver = new SettingsObserver();
  7. }
  8. mContentQueryMap.addObserver(mSettingsObserver);
  9. }
  10. @Override
  11. public void onStop() {
  12. super.onStop();
  13. if (mSettingsObserver != null) {
  14. mContentQueryMap.deleteObserver(mSettingsObserver);
  15. }
  16. mContentQueryMap.close();
  17. }

注意一点,不同的注册方法,不同的反注册方法。

/*
addCallback <==> removeCallback
registerReceiver <==> unregisterReceiver
addObserver <==> deleteObserver
registerContentObserver <==> unregisterContentObserver
... ...
*/

Dialog对象

先看一下异常信息

android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@438afa60 is not valid; is your activity running?

一般发生于Handler的MESSAGE在排队,Activity已退出,然后Handler才开始处理Dialog相关事情。

关键点就是,怎么判断Activity是退出了,有人说,在onDestroy中设置一个FLAG。我很遗憾的告诉你,这个错误很有可能还会出来。

解决方案是:使用isFinishing()判断Activity是否退出。

  1. Handler handler = new Handler() {
  2. public void handleMessage(Message msg) {
  3. switch (msg.what) {
  4. case MESSAGE_1:
  5. // isFinishing == true, 则不处理,尽快结束
  6. if (!isFinishing()) {
  7. // 不退出
  8. // removeDialog()
  9. // showDialog()
  10. }
  11. break;
  12. default:
  13. break;
  14. }
  15. super.handleMessage(msg);
  16. }
  17. };

上面的内存泄露,只是冰山一角,这些泄露大部分原因都是相同的,几乎90%的原因是 长生命周期持有短生命周期的引用,导致GC无法有效回收内存。
以上是内存泄露的情况,另外如下想进一步了解如何查看内存泄露,可以查看博客 Android 内存分析(一)以及(二)

参考:http://www.cnblogs.com/qianxudetianxia/p/3645106.html 适当做了修改 特此说明

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注