目录
- 前言及消息机制简述
- Handler的日常使用
- HandlerThread和IntentService中的Handler
- 消息机制源码剖析
一、前言及概述
1、前言
这篇剖析是17年为了应对面试而复习后记在有道云上的,是时候拿出来完善下并共享给各位指点下了。
不管是小白还是老手,入门还是复习,我觉得都是会有所收获的。
来看看这几个问题:
- 主线程的Looper什么时候quit?(新手真的会问的)
- UI为什么“只能”在主线程刷新?
2、概述
消息机制简述:
在Android 中,消息机制属于必须掌握基本技能,从点击应用按钮开始,它就无处不在!
Andriod
线程间的通信(消息机制)是通过Handler
的运行机制(包括Handler、Message、MessageQueue、Looper
)来实现的!
主线程在初始时就创建了一个
Looper
对象,这个Looper
会直接使用while(true)
进行无限循环从其对应的MessageQueue
中读取Message
并执行,没有消息则进入阻塞并等待新消息的到来。
而Message
是主线程或其它线程通过Handler
来往MessageQueue
中添加的
举个简单的例子,先不论我们在启动app的过程,Handler扮演的角色。先看看我们在启动之后的。看下面代码:
//Activity上的一个按钮
btn_1.setOnClickListener {
textView1.text = "哈哈哈" // 1
Handler().sendEmptyMessage(0) // 2
}
点击一个按钮,触发了onClick()
方法,问,是怎么触发的呢?Handler有用到吗?
textView1.text = "哈哈哈"
这句也会有Handler的参与吗?
这里首先明确一点就是:onClick()
方法是主线程执行的。
所以必然是在我们点击按钮之后,某个地方有用主线程的Handler往MessageQueue中添加了一个Message。onClick()方法体才得以执行。反之亦然。
即逻辑鬼才反推,所以onClick()方法是主线程执行的是正确的。
至于 textView1.text = "哈哈哈"
这一句表面上没有,但实际上,这里是在更新UI,更新UI就要刷新屏幕,刷新屏幕就会有主线程的配合( 比如textView1.onDraw()
的执行),那也就会有Handler的存在~
至于Handler().sendEmptyMessage(0)
...嗯,就不多说了。。
凡任何在主线程执行的代码,其源头必然是通过有经过Handler处理。
当然,排除在主线程的Looper创建之前的那为数不多的漏网之鱼。
二、Handler的日常使用
使用一般两种情况
-
线程内自己给自己发消息,写到这懵了一下,为啥要给自己发???比如刚刚的button点击之后我直接执行所需要执行的操作不就好了?干嘛再抛出一个消息,去排队执行!哦,想起来了!排队!不仅排队,还能插队以及延时执行。
即 指定在将来的某个时间点去执行消息。也就是延时执行。 -
线程间的通信,典型就是非UI线程将消息传递给主线程让其更新UI
这里顺便引出的问题就是为什么不直接在非UI线程更改UI?
答:UI的刷新更改只能由主线程来完成,子线程需要通过消息传递给主线程来完成对UI的更改。
而之所以只能UI线程更改:
- UI控制是线程不安全的,多线程并发使UI控件处于不可控的状态。
- 为何不多线程然后上锁?一是会将UI逻辑变得复杂,二是上锁显然降低UI的执行效率。
界面流畅度大过天!
实际上!android 界面规则是:Window由哪个线程创建,就只能通过该线程更新。
此Window包括(Activity、Dialog以及各种通过WindowManager.addView()的窗口)
ViewRootImp.java
void checkThread() { //View在刷新绘制之前会先判断当前正在操作的线程
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
有些本来只是在搜索Handler的用法的小白开始os了,还BB,赶紧 show me the code~, 不然换个链接查找资料了
来了来了,简易的API解说:
1.主线程中:
重写一遍上面的描述:
主线程在初始时就创建了一个
Looper
对象,这个Looper
会直接使用while(true)
进行无限循环从其对应的MessageQueue
中读取Message
并执行,没有消息则进入阻塞并等待新消息的到来。
而Message
是主线程或其它线程通过Handler
来往MessageQueue
中添加的
所以在主线程中,我们需要处理的就是使用Handler往MessageQueue添加Message.
有下面几个常用Api:
val message = Message()
val message2 = Message.obtain() //从系统缓存的MessagePool中取出回收的Message对象,建议使用
message.what = 2 //message的标志,用于开发者判断message的类型
message.arg1 = 1 // message可选附带的参数, int类型
message.arg2 = 2 // int类型参数
message.obj = "参数3" // obj 为任意类型
message.data = Bundle() // 可选附带的参数,Bundle()
handler.sendEmptyMessage(1) // 发送what = 1的 Message
handler.sendEmptyMessageDelayed(1, 5000) // 延迟5s处理
handler.sendMessage(message)
handler.sendMessageDelayed(message, 5000)
handler.post(Runnable {
//本质依然是Message, Message还有个 Runnable变量,系统直接执行Runnable中代码,无需设置what之类的
})
handler.postDelayed(Runnable {
}, 5000)
上述是将消息添加到消息队列中,比如handler().sendEmptyMessageDelayed(0, 5000)
发送了一个what=0
的 message
。
那么当Looper
取出这条消息的时候,由谁来执行呢?又执行什么那些代码呢?
其实这也是Handler
的职责f范围内,handler在创建的时候,是需要继承handlerMessage(msg: Message)
方法的
当消息被取出来之后,就会自动调用handler.handlerMessage(msg: Message)
,然后执行你所要做的操作。
val handler = object : Handler() {
override fun handleMessage(msg: Message) {
when (msg.what) {
0 -> {
Log.i(TAG, "handleMessage: 0")
}
1 -> {
Log.i(TAG, "handleMessage: 1")
}
else -> {
}
}
}
}
所以就是只要
使用handler.sendMessage()
然后通handler.handlerMessage()
处理消息
上手是不是感觉简单到爆!感谢谷歌。
当然这里需要注意一个内存泄漏的问题!毕竟定义变量handler
时重写handlerMessage()
之后就变成了一个匿名内部类,那么会持有外部类变量,一般来说就是Activity,发送延迟消息可能引发内存泄漏。简单点做法,在Activity.onDestory()
时handler.removeCallbacksAndMessages(null)
将所有与此Handler有关的消息从消息队列移除即可。
其他就不展开了,建议骚年自行搜索“android 内存泄漏”来学习一个系列的内存泄漏的处理!
2. 继续看线程间通信的处理。
简单的说,那就是假设
handler
对象在线程A被定义,那就不管handler.sendMessage()
在其他的哪条线程执行,handlerMessage()
就会在handler
定义的线程A执行。
当然这样说是不严谨的!
因为这又与Looper
有关了。Handler
在创建的时候,需要获取本线程的Looper
,这样才能准确的将Message
投入Looper
所对应的MessageQueue
中。
注意:每条线程只能通过Looper.prepare()
去创建本线程的Looper
,而且只能创建一次!也就是一个线程对应一个Looper,多了会怎样呢?那就只有💥💥💥
所以上面之所以说不严谨是因为Handler的常用构造函数有:
public Handler() //默认使用当前线程的Looper。 所以当前线程必须已经有先执行过Looper.prepare()
public Handler(Looper looper) // 本线程创建Handler的时候可以传入其他线程的Looper,那么handlerMessage()会在Looper所在的线程里执行。
所以普通线程给主线程传递消息是,两种方式:
1.直接使用在主线程创建的Handler变量
class BlankFragment : Fragment() {
val mHandler = object :Handler() {
override fun void handleMessage() {
....
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val thread = Thread({
mHandler.sendEmptyMessage(0)
})
thread.start()
}
}
2. 在工作线程中创建使用主线程Looper的Handler
class BlankFragment : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val thread = Thread({
// 在主线程执行的Handler
val handler = Handler(Looper.getMainLooper())
//当然这里也可以跟上面一样,通过handleMessage()来处理。
handler.post({
testBtn.visibility = View.VISIBLE
})
})
thread.start()
}
}
ps:第二种是真香,在Rxjava、LiveData等可用于异步通信的库中用得风生水起,就又比如Glide中异步加载图片之后通知主线程去显示。
另外上述代码仅仅是为举栗子方便,这种使用匿名线程可得格外注意,一不小心又是个内存泄漏。
而使用非UI线程创建Looper的具体使用可以参见HandlerThread
。
ok,骚年如果你只是来查Handler的api的使用,我想上述的描述已经满足了你的要求。
又认真想了想,如果是真小白的话。估计也看不懂kotlin代码🤦♂️
三、HandlerThread 和IntentService
1、HandlerThread
HandlerThread是一个封装了Looper的Thread。代码量就一百来行,小白可以直接看源码上手。
HandlerThread : Thread
//覆盖run方法,创建Looper
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
当我们需要非UI线程来实现一个异步消息队列以及处理延时消息。
或者主线程中将一些耗时操作进行异步操作
HandlerThread是我们选择之一,使用也非常简单。
// 创建HandlerThread,参数为线程名。
val thread = HandlerThread("HandlerThread_name")
thread.start() // start之后,looper自动进入循环
//创建使用HandlerThread中Looper的Handler
val mHandler = object :Handler(thread.looper) {
override fun handleMessage(msg: Message?) {
msg?.let {
when(msg.what) {
0 -> {
Log.i(TAG, "handleMessage 0")
}
else -> {
}
}
}
}
}
//这句可以在任意线程调用,然后,在HandlerThread中被执行
mHandler.sendEmptyMessage(0)
//当要退出线程时使用
thread.quit() //立即退出(如果有消息正在执行,会先继续执行消息)
thread.quitSafely() // 等消息队列消息全部执行完再退出,当如果消息执行速度小于插入消息的速度。。。那线程就一直执行下去咯
2、IntentService
HandlerThread虽然很简单,但现在的需求中应用很少,毕竟外面成熟的异步框架太强了。除非确实有需求在单条线程上起一个消息队列来执行很多的消息。
不过HandlerThread在源码中有一个典型的用法,那就是 IntentService
。
IntentService可以说成是一种一次性的Service,用完即走的那种。
-
IntentService通过结合HandlerThread完成一些异步非UI操作,并在操作完成后,自动销毁Service。
-
IntentService是个抽象类,必须继承后实现
onHandleIntent(intent: Intent?)
方法,当通过context.startService(intent)
启动IntentService后,会调用onHandleIntent()
来做对应的操作。 -
onHandleIntent(intent) 是在HandlerThread中被执行的,至于为什么要通过HandlerThread而不是普通的Thread?是因为startService(intent)多次,如果是普通线程,那么就会建立多条线程,而通过则HandlerThread可以自动往消息队列添加,并等待被执行。
IntentService中的Handler
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
- 当消息队列中消息全部执行完之后,那么IntentService会自动退出。下一次的startService() 会重新创建一个IntentService。
以前我一直有个未知之谜,上述代码中onHandleIntent()之后就会立马执行执行stopSelf(),服务就应该停止了啊!毕竟已经mServiceLooper.quit()了. 为什么还会把消息队列执行完?
网上文章翻了很多篇都没有提到,源码也看了很多遍,折腾一番才发现问题出在stopSelf(msg.arg1)的参数中msg.arg1,接下来就简单多了,当做课后作业,大家自己去源码翻一翻吧。
IntentService.java
@Override
public void onDestroy() {
mServiceLooper.quit();
}
使用IntentService的简单使用可直接看下面这个例子(Android Studio 生成模板代码)
至于更为详细的IntentService源码分析,我就不分析了。。
class MyIntentService : IntentService("MyIntentService") {
// 需要实现
override fun onHandleIntent(intent: Intent?) {
when (intent?.action) {
ACTION_FOO -> {
val param1 = intent.getStringExtra(EXTRA_PARAM1)
val param2 = intent.getStringExtra(EXTRA_PARAM2)
handleActionFoo(param1, param2)
Thread.sleep(3000)
}
ACTION_BAZ -> {
val param1 = intent.getStringExtra(EXTRA_PARAM1)
val param2 = intent.getStringExtra(EXTRA_PARAM2)
handleActionBaz(param1, param2)
}
}
}
/**
* Handle action Foo in the provided background thread with the provided
* parameters.
*/
private fun handleActionFoo(param1: String, param2: String) {
Log.i(TAG, "handleActionFoo ")
}
/**
* Handle action Baz in the provided background thread with the provided
* parameters.
*/
private fun handleActionBaz(param1: String?, param2: String?) {
Log.i(TAG, "handleActionBaz ")
}
companion object {
private const val TAG = "MyIntentService"
/**
* Starts this service to perform action Foo with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
// TODO: Customize helper method
@JvmStatic
fun startActionFoo(context: Context, param1: String?, param2: String?) {
val intent = Intent(context, MyIntentService::class.java).apply {
action = ACTION_FOO
putExtra(EXTRA_PARAM1, param1)
putExtra(EXTRA_PARAM2, param2)
}
context.startService(intent)
}
/**
* Starts this service to perform action Baz with the given parameters. If
* the service is already performing a task this action will be queued.
*
* @see IntentService
*/
// TODO: Customize helper method
@JvmStatic
fun startActionBaz(context: Context, param1: String, param2: String) {
val intent = Intent(context, MyIntentService::class.java).apply {
action = ACTION_BAZ
putExtra(EXTRA_PARAM1, param1)
putExtra(EXTRA_PARAM2, param2)
}
context.startService(intent)
}
}
}
网友评论