Android 内存优化篇——夜用大片防侧漏

Handler的正确写法

正常情况下,本着方便快捷,省时省力的思想,我们会将Handler写成这副模样:

    private Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            // TODO you have message
        }
    };

但是如果你用的是Android Studio,那么他会报一片屎黄屎黄的颜色,那是因为Android Studio的Inspect Code检出出了这里可能会造成内存泄漏,Inspect Code可以检查你的代码并标记出可能出现问题的地方,后面的优化工具篇会讲到,回到正题,鼠标放在小灯泡的图标上,你就会看到这些东西:

Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

他主要是告诉你,这里你声明了一个内部类,他会阻碍GC回收垃圾,哔哩哔哩……
用代码说话:

public class SampleActivity extends Activity {
  private final Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // 发送一个10分钟后执行的一个消息
    mHandler.postDelayed(new Runnable() {
      @Override
      public void run() { }
    }, 600000);

    // 结束当前的Activity
    finish();
  }
}

当Activity结束后,在 Message queue 处理这个Message之前,它会持续存活着。这个Message持有Handler的引用,而Handler有持有Activity(SampleActivity)的引用,这个Activity所有的资源,在这个消息处理之前都不能也不会被回收,所以发生了内存泄露。

辣么,正确的写法应该是这样的:

private MyHandler handler = new MyHandler(this);

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 当参数为null的时候,可以清除掉所有跟次handler相关的Runnable和Message,我们在onDestroy中调用次方法也就不会发生内存泄漏了。
        handler.removeCallbacksAndMessages(null);
    }

    private void todoHandler(Message msg){
        // TODO handlerMessage
    }

    public static class MyHandler extends Handler{

        private final WeakReference<MainActivity> mActivity;

        public MyHandler(MainActivity activity) {
            mActivity = new WeakReference(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            MainActivity mainActivity = mActivity.get();
            if (mainActivity != null){
                mainActivity.todoHandler(msg);
            }
        }

    }

如果你觉得这么写太TM麻烦了,不用担心,嫌麻烦的不止你一个,总有受不了的,这里提供一个WeakHandler库 : GitHub

单例,不仅仅存在线程安全问题

单例模式深受广大开发者的喜爱,但从相对论的角度来看,肉吃多了也能死人。所以,在适合的情况下使用单例。而在单例的实现方式上,更多人关注线程安全方面的问题,但是内存泄漏问题同样重要。
在用LeakCanary检测APP内存泄漏的时候,发现N多地方出现了单例造成的内存泄漏。
下面来看一段反面教材:

public class XXXManager{  

    private static XXXManager instance;
    private Context context;

    private XXXManager(Context context) {
        this.context = context;
    }

    public static XXXManager getInstance(Context context) {
      if (instance != null) {
        instance = new XXXManager(context);
      }
      return instance;
    } 
}

XXXManager实例是存在于整个app生命周期的,如果是传入的Application完全没有问题,但如果你传入的是某个Activity,那么这个不幸的Context就跟死了没法儿投胎一样的跟着XXXManager,况且如果你在调用getInstance()之前这个实例已经存在了,那么你传入的这个Context并没有什么卵用。下面给出两个方案,我认为第二个会好一点。
方案1:

private XXXManager(Context context) {
    // 这样你持有的就是Application的Context了,没毛病~
    this.context = context.getApplication();
}

方案2:

    private static Context mContext;

    public static XXXManager getInstance(){
        if (mContext != null){
           if (instance == null){
               instance = new XXXManager(mContext);
           }
            return instance;
        }
        // 如果mContext = null 抛出异常提醒没有注册过
        try {
            throw new Exception("not register!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 在MyApplication中先注册
    public static void register(Context context){
        mContext = context;
    }

这里为了突出重点,忽略了线程安全,使用的时候需要注意到这一点。

买一送一,最后奉上一个单例的正确写法:
参考链接

public static SettingsDbHelper getInstance(Context context) {  
    SettingsDbHelper inst = sInst;  // <<< 在这里创建临时变量
    if (inst == null) {
        synchronized (SettingsDbHelper.class) {
            inst = sInst;
            if (inst == null) {
                inst = new SettingsDbHelper(context);
                sInst = inst;
            }
        }
    }
    return inst;  // <<< 注意这里返回的是临时变量
}

多线程的滥用——你用多少内存换来懒惰?

我相信很多人都这么干过:

 new Thread(new Runnable() {
      @Override
      public void run() {
        SystemClock.sleep(10000);
      }
    }).start();

这里的Runnable和前面提到的错误书写handler一样,是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成,那么将导致Activity的内存资源无法回收,造成内存泄漏。
同Handler的解决方法一样,你需要使用一个静态的内部类:

static class MyRunnable implements Runnable{
    @Override
    public void run() {
        SystemClock.sleep(10000);
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值