Handler 相关知识,面试必问

做Android开发肯定离不开跟Handler打交道,它通常被我们用来做主线程与子线程之间的通信工具,而Handler作为Android中消息机制的重要一员也确实给我们的开发带来了极大的便利。可以说只要有异步线程与主线程通信的地方就一定会有Handler。在面试中Handler也是经常被问的一个点,那么本篇文章就以问答的方式,带你了解一下关于Handler的重要知识点。

1.一个线程有几个Looper?几个Handler?

一个Thread只能有一个Looper,一个MessageQueen,可以有多个Handler

以一个线程为基准,他们的数量级关系是:Thread(1):Looper(1):

MessageQueue(1):Handler(N)

2.Handler内存泄漏原因?以及最佳解决方案?

泄露原因:

Handler允许我们发送延时消息,如果在延时期间用户关闭了Activity,那么该Activity会泄露。这个泄露是因为Message会持有Handler,而又因为Java的特性,内部类会持有外部类,使得Activity会被Handler持有,这样最终就导致Activity泄露。

解决方案

1.最直接的思路就是避免使用非静态内部类。使用Handler的时候,放在一个新建的文件中来继承Handler或者使用静态的内部类来替代。静态内部类不会隐含的持有外部类的引用,因此这个activity也就不会出现内存泄漏问题。

2.如果你需要在Handler内部调用外部Activity的方法,你可以让这个Handler持有这个Activity的弱引用,这样便不会出现内存泄漏的问题了。

3.另外,对于匿名类Runnable,我们同样可以设置成静态的,因为静态内部类不会持有外部类的引用。

4.注意:如果使用Handler发送循环消息,最好是在Activity的OnDestroy方法中调用mLeakHandler.removeCallbacksAndMessages(null);移除消息。(这不是解决内存泄漏的方法)

5.两种解决办法如下:

弱引用(WeakReference

publicclassSampleActivityextendsActivity{

/**

*Instancesofstaticinnerclassesdonotholdanimplicit

*referencetotheirouterclass.

*弱引用的方式

*/

privatestaticclassMyHandlerextendsHandler{

privatefinalWeakReference<SampleActivity>mActivity;

publicMyHandler(SampleActivityactivity){

mActivity=newWeakReference<SampleActivity>(activity);

}

@Override

SampleActivityactivity=mActivity.get();

if(activity!=null){

//toSomething

}

}

}

静态

//定义成static的,因为静态内部类不会持有外部类的引用privatefinalMyHandlermHandler=newMyHandler(this);

privatestaticfinalRunnablesRunnable=newRunnable(){

@Override

publicvoidrun(){//tosomething}

};

@Override

protectedvoidonCreate(BundlesavedInstanceState){

super.onCreate(savedInstanceState);

mHandler.postDelayed(sRunnable,1000*60*10);

finish();

}}

3.为何主线程可以newHandler?如果想要在子线程中newHandler要做些什么准备?

每一个handler必须要对应一个looper,主线程会自动创建Looper对象,不需要我们手动创建,所以主线程可以直接创建handler。在newhandler的时候没有传入指定的looper就会默认绑定当前创建handler的线程的looper,如果没有looper就报错。因为在主线程中,Activity内部包含一个Looper对象,它会自动管理Looper,处理子线程中发送过来的消息。而对于子线程而言,没有任何对象帮助我们维护Looper对象,所以需要我们自己手动维护。

所以要在子线程开启Handler要先创建Looper,并开启Looper循环

如果在子线程中创建了一个Handler,那么就必须做三个操作:

1.prepare();

2.loop();

3.quit();

4.子线程中维护的Looper,消息队列无消息的时候的处理方案是什么?有什么用?

在Handler机制里面有一个Looper,在Looper机制里面有一个函数,叫做quitSafely()和quit()函数,这两个函数是调用的MessageQueue的quit()。

/**

*Quitsthelooper.

<p>

*Causesthe{@link#loop}methodtoterminatewithoutprocessingany

*moremessagesinthemessagequeue.

*</p><p>

*Anyattempttopostmessagestothequeueafterthelooperisaskedtoquitwillfail.

*Forexample,the{@linkHandler#sendMessage(Message)}methodwillreturnfalse.

*</p><pclass="note">*Usingthismethodmaybeunsafebecausesomemessagesmaynotbedelivered

*beforethelooperterminates.Considerusing{@link#quitSafely}insteadtoensure

*thatallpendingworkiscompletedinanorderlymanner.

*</p>*

*@see#quitSafely

*/publicvoidquit(){mQueue.quit(false);}/

***Quitstheloopersafely.

*<p>

*Causesthe{@link#loop}methodtoterminateassoonasallremainingmessages

*inthemessagequeuethatarealreadyduetobedeliveredhavebeenhandled.

*Howeverpendingdelayedmessageswithduetimesinthefuturewillnotbe

*deliveredbeforetheloopterminates.

*</p><p>

*Anyattempttopostmessagestothequeueafterthelooperisaskedtoquitwillfail.

*Forexample,the{@linkHandler#sendMessage(Message)}methodwillreturnfalse.

*</p>

*/publicvoidquitSafely(){mQueue.quit(true);}

再进入到MessageQueue的quit()函数。

voidquit(booleansafe){

if(!mQuitAllowed){

thrownewIllegalStateException("Mainthreadnotallowed to

quit.");

}synchronized(this){

if(mQuitting){

return;

}

mQuitting=true;

if(safe){

removeAllFutureMessagesLocked();

}else{

removeAllMessagesLocked();

}

//WecanassumemPtr!=0becausemQuittingwaspreviously

false.

nativeWake(mPtr);

}

}

它会remove消息,把消息队列中的全部消息给干掉。把消息全部干掉,也就释放了内存

privatevoidremoveAllFutureMessagesLocked(){

finallongnow=SystemClock.uptimeMillis();

Messagep=mMessages;

if(p!=null){

if(p.when>now){

removeAllMessagesLocked();

}else{

Messagen;

for(;;){

n=p.next;

if(n==null){

return;

}

if(n.when>now){

break;

}

p=n;

}

p.next=null;

do{

p=n;

n=p.next;

p.recycleUnchecked();

}while(n!=null);

}

}

}

而在quit()函数的最后一行,有一个nativeWake()函数

//WecanassumemPtr!=0becausemQuittingwaspreviouslyfalse.

nativeWake(mPtr);

这个函数的调用,就会叫醒等待的地方,醒来之后,就接着往下执行。

//native的方法,在没有消息的时候回阻塞管道读取端,只有

nativePollOnce返回之后才能往下执行

//阻塞操作,等待nextPollTimeoutMillis时长

nativePollOnce(ptr,nextPollTimeoutMillis);

往下执行后,发现Messagemsg=mMessages;是空的,然后就执行了这个,就接着往下走。if(msg!=null){

......

}else{

//No more messages.

//没有消息,nextPollTimeoutMillis复位

nextPollTimeoutMillis=-1;

}

然后又调用了这个方法,并且return了null。

//Processthequitmessagenowthatallpendingmessageshavebeenhandled.

//如果消息队列正在处于退出状态返回null,调用dispose();

释放该消息队列

if(mQuitting){

dispose();

returnnull;}

所以说,这个时候Looper就结束了(跳出了死循环),则达成了第二个作用:释放线程。

4.既然可以存在多个Handler往MessageQueue中添加数据(发消息时各个Handler可能处于不同线程),那它内部是如何确保线程安全的?

这里主要关注MessageQueue的消息存取即可,看源码内部的话,在往消息队列里面存储消息时,会拿当前的MessageQueue对象作为锁对象,这样通过加锁就可以确保操作的原子性和可见性了。消息的读取也是同理,也会拿当前的MessageQueue对象作为锁对象,来保证多线程读写的一个安全性。

5.我们使用Message时应该如何创建它?

创建的它的方式有两种:

一种是直接new一个Message对象,另一种是通过调用Message.obtain()的方式去复用一个已经被回收的Message,当然日常使用者是推荐使用后者来拿到一个Message,因为不断的去创建新对象的话,可能会导致垃圾回收区域中新生代被占满,从而触发GC。

Message中的sPool就是用来存放被回收的Message,当我们调用obtain后,会先查看是否有可复用的对象,如果真的没有才会去创建一个新的Message对象。

补充:主要的Message回收时机是:

1.在MQ中removeMessage后;

2.单次loop结束后;

3.我们主动调用Message的recycle方法后

6.Looper死循环为什么不会导致应用卡死?

Launch桌面的图标第一次启动Activity时,会最终走到ActivityThread的main方法,在main方法里面创建Looper和MessageQueue处理主线程的消息,然后Looper.loop()方法进入死循环,我们的Activity的生命周期都是通过Handler机制处理的,包括onCreate、onResume等方法,下面是loop方法循环。主线程的主要方法就是消息循环,一旦退出消息循环,那么你的应用也就退出了,Looer.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。

造成ANR的不是主线程阻塞,而是主线程的Looper消息处理过程发生了任务阻塞,无法响应手势操作,不能及时刷新UI。

阻塞与程序无响应没有必然关系,虽然主线程在没有消息可处理的时候是阻塞的,但是只要保证有消息的时候能够立刻处理,程序是不会无响应的。

总结:应用卡死压根与这个Looper没有关系,应用在没有消息需要处理的时候,它是在睡眠,释放线程;卡死是ANR,而Looper是睡眠。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值