Moosphan / Android-Daily-Interview

:pushpin:每工作日更新一道 Android 面试题,小聚成河,大聚成江,共勉之~

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

2019-03-08: 自定义 Handler 时如何有效地避免内存泄漏问题?

Moosphan opened this issue · comments

2019-03-08: 自定义 Handler 时如何有效地避免内存泄漏问题?
commented

1.自定义的静态handler
2.可以加一个弱引用
3.还有一个主意的就是当你activity被销毁的时候如果还有消息没有发出去 就remove掉吧
4.removecallbacksandmessages去清除Message和Runnable 加null 写在生命周的ondestroy()就行

弱引用会不会出现问题

commented

不加弱引用的时候 如果GC不了 那岂不是GG了

不加弱引用的时候 如果GC不了 那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

commented

不加弱引用的时候如果GC不了那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

我竟无言以对 流弊流弊

不加弱引用的时候 如果GC不了 那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

Your this kind of train of thought is incorrect, upstairs consciousness is how to avoid the happening of the problem, your train of thought is to do not have a problem namely, it is not my business.The train of thought decides the way out, big brother, see the gap?

不加弱引用的时候如果GC不了那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

我竟无言以对 流弊流弊

不加弱引用的时候如果GC不了那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

我竟无言以对 流弊流弊

God, can we drink together?

commented

不加弱引用的时候如果GC不了那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

我竟无言以对流弊流弊

不加弱引用的时候如果GC不了那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

我竟无言以对流弊流弊

天哪,我们可以一起喝酒吗?

Sure

God, can we drink together?

Sure !

使用弱引用是对的,静态内部类也是对的。
问题2:不使用这些方法,Handler直接持有Activity的引用是否一定会导致内存泄漏?

  1. 如果Handler中没什么耗时操作,任务完成也就释放了Activity引用。
  2. 如果Handler中是一个2秒的操作,在Activity退出的2s后,释放了Activity的指针,这种情况属于短时间的内存泄漏?
  3. Handler中是啥死循环的话,就内存泄露了。

个人写的Handler,且没有耗时操作。没必要保护。
公司项目对于代码规范的话,还是加上保护比较好。避免后续有同事,加上了耗时操作,出现问题。

是的,正如楼上所说,其实正常情况下是不会内存泄漏的,除非handler队列等待太久(什么情况会这样呢?anr?延时太久?)
理解到泄漏的原因(handler持有了activity,然后message持有handler,然后MQ持有message),持有链了解清楚了也就好解决了:handler持有activity可以通过弱引用或者内部静态类等方式解决,removecallbacksandmessages则是清除后面的引用。
其实却是内存大,泄漏点也没啥,只是规范使然,就像过马路最好走斑马线,不然说不定哪天就被撞死了

commented

对我而言,handler改为弱引用是一改而论(大家只考虑在activity问题,handler引用了activity),解决问题可以传activity弱引用给handler就行,或者在onresume恢复,onpause移除,谁能保证ondestroy能执行??万一没执行呢?如果在service后台用到handler,难道我也弱引用?合理使用handler,要明白为什么泄漏,不是所有场景都能用弱引用,没记错 高版本内存回收策略会先回收弱软引用,,,再说了,内存高就高,,,,,用户区能存活多久。。。

我觉得解决方法很简单
1.就是上面说的用静态内部handler,持有activity的弱引用,在 onDestory 的时候removeCallbacksAndMessages(null)
2.如果不是静态内部handler,在onDestory的时候removeCallbacksAndMessages(null)就好了

而泄露的根本原因就是你activity退出以后这个message还没有被处理完,如果这个message处理完你什么都不做也不会泄露,不过这个handler可能大部分执行的是一个类似联网的异步任务,所以activity退出出现内存泄漏就是因为这个message还没被处理完

多谢沐风大佬的邀请,本人会一一回复面试题,抛砖引玉,借花献佛,如果有错误,还请各位指出。

在Android系统中,Handler是一个消息发送和处理机制的核心组件之一,与之配套的其他主要组件还有Looper和Message,MessageQueue。
Message和Runnable类是消息的载体。MessageQueue是消息等待的队列。Looper则负责从队列中取消息。

Handler有两个主要作用:

  • 1.安排调度(scheule)消息和可执行的runnable,可以立即执行,也可以安排在某个将来的时间点执行。

  • 2.让某一个行为(action)在其他线程中执行。

Handler是由系统所提供的一种异步消息处理的常用方式,一般情况下不会发生内存泄露。

Handler为什么可能造成内存泄漏。这里的内存泄漏,常常指的是泄漏了Activity等组件。

public class ShanActivity extends Activity{
    public Handler handler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
             
    }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
	}
}

这有什么问题呢。问题在于该Handler的实例采用了内部类的写法,它是ShanActivity这个实例的内部类,在Java中,关于内部类有一个特点:在java中,非静态的内部类和匿名内部类都会隐式的持有一个外部类的引用。所以,该handler实例持有了ShanActivity的一个引用。

生命周期较短的组件引用了生命周期较长的组件。Handler就是一种典型的示例,以上面的代码举例。ShanActivity可能会被泄漏,也就是该组件没有用了,比如调用了finish()后,垃圾回收器却迟迟没有回收该Activity。原因出在该实例的handler内部类引用了它,而该handler实例可能被MessageQueue引用着。

从上面的说法中,可以思考得到相应的解决方法:

  • 1.保证Activity被finish()时该线程的消息队列没有这个Activity的handler内部类的引用。这个场景是及其常见的,因为handler经常被用来发延时消息。一个补救的办法就是在该类需要回收的时候,手动地把消息队列中的消息清空:mHandler.removeCallbacksAndMessages(null);

  • 2.要么让这个handler不持有Activity等外部组件实例,让该Handler成为静态内部类。(静态内部类是不持有外部类的实例的,因而也就调用不了外部的实例方法了)

  • 3.在2方法的基础上,为了能调用外部的实例方法,传递一个外部的弱引用进来)

  • 4.将Handler放到抽取出来放入一个单独的顶层类文件中。

这里需要了解一下关于Java里面引用的知识:

强引用(Strong Reference) 默认引用。如果一个对象具有强引用,垃圾回收器绝不会回收它。在内存空 间不足时,Java虚拟机宁愿抛出OutOfMemory的错误,使程序异常终止,也不会强引用的对象来解决内存不足问题。
软引用(SoftReference) 如果内存空间足够,垃圾回收器就不会回收它,如果内存空间不足了,就会回收这些对象的内存。
弱引用(WeakReference) 在垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
虚引用(PhantomReference) 如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

第三种,需要一些额外的代码,比较通用。

public class ShanActivity extends Activity {
private static class MyHandler extends Handler {
private final WeakReference<ShanActivity> mActivity;
public MyHandler(ShanActivity activity) {
  mActivity = new WeakReference<ShanActivity>(activity);
}

@Override
public void handleMessage(Message msg) {
  ShanActivity activity = mActivity.get();
  if (activity != null) {
     //do Something
  }
}
}

第四种方式,抽取做单独封装。

/**
 * 实现回调弱引用的Handler
 * 防止由于内部持有导致的内存泄露
 * 传入的Callback不能使用匿名实现的变量,必须与使用这个Handle的对象的生命周期一 
 * 致否则会被立即释放掉了
 */
public class WeakRefHandler extends Handler {
    private WeakReference<Callback> mWeakReference;
    
    public WeakRefHandler(Callback callback) {
        mWeakReference = new WeakReference<Handler.Callback>(callback);
    }
    
    public WeakRefHandler(Callback callback, Looper looper) {
        super(looper);
        mWeakReference = new WeakReference<Handler.Callback>(callback);
    }
    
    @Override
    public void handleMessage(Message msg) {
        if (mWeakReference != null && mWeakReference.get() != null) {
            Callback callback = mWeakReference.get();
            callback.handleMessage(msg);
        }
    }
}

由于是弱引用,当该类需要被回收时,可以直接被回收掉。

WeakRefHandler的使用时如下:

    private Handler.Callback mCallback = new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch(msg.what){
            }
            return true;
        }
    };
    private Handler mHandler = new WeakRefHandler(mCallback);

本人最近在学习C,个人感觉应该使用第一个解决方法,因为你要深刻理解你的代码是干什么的,在哪干活的,还有在什么时候应该回收——仅是个人看法,如有不同,请忽略。
文章参考:
Android 内存泄漏和OOM分析

Android 内存泄漏和OOM分析二

默认的handler 在处理信息时可以延迟处理, 那么就会导致activity关闭时 有可能消息还未处理完毕 ,此时未被处理的消息会持有handler, handler又会持有activity, 从而导致内存泄漏。
解决办法就是 静态内部类化handler ,弱引用activity,并且在activity退出时 ,将handler绑定的Message还未执行完毕的消息 全部remove掉。

上面都回答了很多解决方案了,我就说下实操中一些注意事项,可能会用到。
1、注意时序。很多时候我们都是使用Handler和其他类配合使用,销毁时候会调用removecallbacksandmessages清空,这时候要注意先去执行情况操作,再去销毁其他类,如果颠倒过程就可能出现消息还没清空,却执行了其他类功能,但此时已销毁其他类。

加油吧同学们, 学无止境啊

不加弱引用的时候 如果GC不了 那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

如果所有的app都跟你一样想的话,那肯定都崩溃了

不加弱引用的时候 如果GC不了 那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

你知道什么是ROM什么是RAM吗?

不加弱引用的时候 如果GC不了 那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

你知道什么是ROM什么是RAM吗?

当你说出这句话时,给人的感觉是你不知道啥是RAM 啥是ROM

不加弱引用的时候 如果GC不了 那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

你知道什么是ROM什么是RAM吗?

当你说出这句话时,给人的感觉是你不知道啥是RAM 啥是ROM

hahaha

自定义handler对象,传入LifecycleOwner对象,监听activity的ondestroy回调,在用ondestroy的时候把消息remove掉。具体使用:https://github.com/AlanCheen/Pandora/blob/master/pandora-basic/src/main/java/me/yifeiyuan/pandora/LifecycleHandler.java

不加弱引用的时候 如果GC不了 那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

你知道什么是ROM什么是RAM吗?

当你说出这句话时,给人的感觉是你不知道啥是RAM 啥是ROM

哈哈哈哈

内存泄漏发生的主要原因:当前对象需要被销毁时,由其他对象(包含外部对象或者非静态内部类)持有当前对象的引用,导致当前对象释放不了;也可以抽象的说成是生命周期长的对象持有生命周期短的对象的引用

自定义 handler 导致内存泄漏发生的主要原因:当 activity 调用 finish 方法时,由于消息队列中有没被执行完的 message,message 持有 handler 的引用,handler 作为内部类又持有外部 activity 的引用,导致activity无法被释放;

解决办法:

  1. 把 handler 定义为静态内部类,对外部 activity 的引用使用弱引用的方式
  2. 在 activity 里的 onDestroy 回调方法中,调用 handler的removeCallbacksAndMessages(null)方法,清除消息队列中message
  3. 把 handler 单独定义成一个类,不作为非静态内部类存在

直接清除队列里面的message不可以吗?

看了大家写了这么多,还是很有收获的,目前基本都在使用 kotlin 开发,看见如上的回答中,有一个 kotlin 版本,写法比较奇怪,说一下自己的看法:

private var mHandler: Handler? = WithoutLeakHandler(this)

companion object {
        const val CODE_LOAD_DATA: Int   = 101
        private class WithoutLeakHandler( activity: EditAddressActivity) : Handler(){
            private var mActivity: WeakReference<EditAddressActivity> = WeakReference(activity)

            override fun handleMessage(msg: Message) {
                super.handleMessage(msg)
                val activity = mActivity.get()
                when(msg.what){

                    CODE_LOAD_DATA -> {
                        //data load started

                    }

                }
            }
        }
    }

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mHandler?.sendEmptyMessage(CODE_LOAD_DATA)
    }`

奇怪的地方:居然把一个所谓的静态内部类定义到了 companion object 模块 ???(该模块只用来定义静态字段和静态方法)

kotlin 中应该怎么写呢:

class WeakHandler(activity: Activity) : Handler() {
        private val weakReference = WeakReference(activity)
        override fun handleMessage(msg: Message?) {
            weakReference.get()?.let {
                // doSomeThings
            }
        }
    }

kotlin 的内部类需要用 inner class 关键字申明,不适用 inner 关键字修饰的类,称之为 嵌套类,不持有外部类的引用,请放心使用哈

不加弱引用的时候 如果GC不了 那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

千里之堤,毁于蚁穴

Handler 引起的内存泄露相关知识点:

  1. Java 中内部类;
  2. Java 中 强引用、软引用、弱引用、虚引用;
  3. andorid.os.Handler 的使用,以及如何避免内存泄露。

1.自定义静态的Handler
2.非静态的Handler,可以加个弱引用(针对回调)
3.在onDestory时,调用removecallbacksandmessages(null)去清除Message和Runnable.

Handler内存泄露的根源:
1.内部类持有外部类的引用
2.Handler的message,没有清除
根据这俩点提出的解决方案:
1.将Handler自定义成静态类
2.清除Message

网友提的 弱引用算长知识了

1.将Handler自定义成静态类 弱引用Activity实例
2.清除Message

1、静态内部类
2、弱引用外部类实例
3、及时移除Message

通常大家都喜欢在ondestory的时候调用mHandler.removeCallbacksAndMessages(null)

  • 第一问:handler的源码 相关的了和执行流程,以及这个方法作用执行流程(自行Google)
  • 第二问:但是ondestory的时序问题 ondestory是一定会执行的吗
    个人解答:
    正常点击返回键的话onDestroy方法一定会执行。
    如果是后台强杀的话当前仅有一个activity,这时候,强杀,是会执行onDestroy方法的;如果栈里面的第一个没有销毁的activity会执行ondestroy方法,其他的不会执行。
    比如说:从mainactivity跳转到activity-A(或者继续从activity-A再跳转到activity-B),这时候,从后台强杀,只会执行mainactivity的onDestroy方法,activity-A(以及activity-B)的onDestroy方法都不会执行;
  • 第三问:ondestory 延迟执行怎么解决原因
    个人解答:
    执行完上一个 Activity 的 onResume 之后,Activity 的销毁时通过请求 ActivityManagerService 的 activityIdle() 方法进行,实现的是 MessageQueue.IdleHandler,IdleHandler 会等到 MessageQueue 中当前没有可执行的消息时才会执行,也就是说 Activity 会一直等待主线程消息队列中当前消息都处理完毕了才会进行销毁,这也就是 Activity 的销毁不是立即执行的根本原因;通常代码解决都会定义一个基类的activity 如下
@Override
    protected void onPause() {
        super.onPause();
        if (isFinishing()) {
            destroy();
        }
    }

通过标志位 来判断是否是destroy的状态来加快释放的速度,算是优化的一点

不加弱引用的时候 如果GC不了 那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

你知道什么是ROM什么是RAM吗?

当你说出这句话时,给人的感觉是你不知道啥是RAM 啥是ROM

dalvik.vm.heapsize 指定了堆空间大小。不是12G的ram应用就能用12G的

commented

不加弱引用的时候 如果GC不了 那岂不是GG了

现在手机都12G RAM,一个小小的handler就能让他崩溃吗

现在手机内存比我电脑的都高

handler.removeCallbacksAndMessages(null)清空消息队列

1.自定义的静态handler
2.可以加一个弱引用
3.还有一个主意的就是当你activity被销毁的时候如果还有消息没有发出去 就remove掉吧
4.removecallbacksandmessages去清除Message和Runnable 加null 写在生命周的ondestroy()就行

群里给出的答案以及讨论都很不错,我这里结合Profiler 直观的将Activity泄漏不同情况展示出来。

image

具体的可以参考我这篇文章自定义Handler内存泄漏 (图文版)