Handler 使用以及源码全面解析(一)

目录

  1. 前言及消息机制简述
  2. Handler的日常使用
  3. HandlerThread和IntentService中的Handler
  4. Handler 使用以及源码全面解析(二)

一、前言及概述

1、前言

这篇剖析是以前做的小笔记,是时候拿出来更新完善下并共享给各位指点下了。

不管是小白还是老鸟,入门还是复习,我觉得这两篇看下来,都是会有所收获的。当然老鸟还是直接看第二篇吧。

这第一篇来说,先来看看这几个问题:

  1. 主线程的Looper什么时候quit(),无限循环为什么不会导致ANR?
  2. UI为什么“只能”在主线程刷新?
  3. 主线程怎么跟其它线程通信?

2、概述

消息机制简述:

在Android 中,消息机制属于必须掌握基本技能,从点击应用按钮开始,它就无处不在!

Andriod线程间的通信(消息机制)是通过Handler的运行机制(包括Handler、Message、MessageQueue、Looper)来实现的!

主线程在初始时就创建了一个Looper对象,这个 Looper会直接使用for(;;)进行无限循环从其对应的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创建之前的那为数不多的漏网之鱼。
这里回答第一个问题,主线程的无限循环为什么不会导致ANR,以及什么退出循环?

  1. ANR首先是因为主线程在限定的时间内没有开始或完成某些操作A,比如触摸事件或者收到广播后的执行动作。而这些事件A都是通过Handler往主线程的消息队列中添加消息,也就是当主线程在执行某些其他操作B时候,导致在排队中的某个事件A超时未能得到执行,从而导致ANR。
    所以ANR跟Looper的无限循环是没有直接关系的。
    至于主线程如果退出了循环,那么就再也收不到任何事件了,那么应用也该挂了。

二、Handler的日常使用

使用一般两种情况

  1. 线程内自己给自己发消息,指在将来的某个时间点去执行消息。也就是延时执行。
  1. 线程间的通信,典型就是非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会直接使用for(;;)进行无限循环从其对应的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()

发消息的三种方式

    1.第一种方式:发送 messager.what = 1的 Message
        handler.sendEmptyMessage(1)  
        //handler.sendEmptyMessageDelayed(1, 5000)  // 延迟5s处理

    2. 第二种方式 :  普通方式
        val message = Message.obtain()
        messager.what = 1
        handler.sendMessage(message) 
        //handler.sendMessageDelayed(message, 5000)

    3. 第三种方式 :       
        handler.post(Runnable {   
            //本质依然是Message, Message还有个 Runnable变量,系统直接执行Runnable中代码,无需设置what之类的
        })
        handler.postDelayed(Runnable {
        }, 5000)

上述三种方式都是将消息添加到消息队列中,比如handler().sendEmptyMessageDelayed(0, 5000)发送了一个what=0message
那么当Looper取出这条消息的时候,由谁来执行呢?又执行什么那些代码呢?
如果是第三种方式,那么消息取出来后,就直接执行Runable内的内容。
否则则还是需要Handler来处理。handler在创建的时候,重写handleMessage(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 -> {

                }
            }
        }
    }

当消息被取出来之后,就会自动调用handle.handlerMessage(msg: Message),执行你所要做的操作。
所以对于handler的使用上手就是只要
使用handler.sendMessage() 然后通handler.handlerMessage()处理消息
上手是不是感觉简单到爆!

当然这里需要注意一个内存泄漏的问题!
毕竟定义变量handler时重写handleMessage()之后就变成了一个匿名内部类,那么会持有外部类变量(一般来说就是Activity), 那么当我们发送延迟消息可能引发内存泄漏。

  1. 简单点做法,在Activity.onDestory()handler.removeCallbacksAndMessages(null)将所有与此Handler有关的消息从消息队列移除即可。
  2. 把Handler变量定义为静态内部类即可,如果在handlerMessage()需要使用到Activity的东西,Handler内持有Activity的弱引用来处理。
class MainActivity : AppCompatActivity() {

    class MyHandler(context: MainActivity) : Handler() { // kotlin默认静态内部类
        private val activity: WeakReference<MainActivity> = WeakReference(context)
        override fun handleMessage(msg: Message?) {
            when(msg?.what) {
                1 -> {
                    activity.get()?.let {
                        // do Something
                    }
                }

            }
            super.handleMessage(msg)
        }
    }
    
    val myHandler = MyHandler(this)

    ...
}

2. 继续看线程间通信的处理。
Handler有两个构造方法:

public Handler()  //默认使用当前线程的Looper。 
public Handler(Looper looper) 

Handler在创建的时候,需要获取本线程的Looper, 这样才能准确的将Message投入Looper所对应的MessageQueue中。如果还没有创建Looper,就会发生异常崩溃。

注意:每条线程只能通过Looper.prepare()去创建本线程的Looper,而且只能创建一次!也就是一个线程对应一个Looper,多了会怎样呢?那就只有💥💥💥

那就是假设handler对象在线程A被定义,那就不管handler.sendMessage()在其他的哪条线程执行,handlerMessage()只会在 handler所对应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()来处理。
           // 在Activity中 还有个 runOnUiThread()方法。内部用的也是handler.post()
            handler.post({
                testBtn.visibility = View.VISIBLE
            })
        })
      thread.start()
    }
}
ps:第二种是真香,在Rxjava、LiveData等可用于异步通信的库中用得风生水起,就又比如Glide中异步加载图片之后通知主线程去显示。

另外上述代码仅仅是为举栗子方便,这种使用线程可得格外注意,一不小心又是个内存泄漏。
毕竟 Thread大括号内  是个实现了 Runable接口的匿名类!

而使用非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 handlerThread = HandlerThread("HandlerThread_name")
        handlerThread.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 -> {
                            
                        }
                    }
                    
                }
            }
        }
        //这句可以在任意线程调用,handleMessage会在HandlerThread中被执行
        mHandler.sendEmptyMessage(0)

      //当要退出线程时使用
      handlerThread.quit() //立即退出(如果有消息正在执行,会先继续执行消息)
      handlerThread.quitSafely() // 等消息队列消息全部执行完再退出,当如果消息执行速度小于插入消息的速度。。。那线程就一直执行下去咯
2、IntentService

HandlerThread虽然很简单,但现在的需求中应用很少,毕竟外面成熟的异步框架太强了。除非确实有需求在单条线程上起一个消息队列来执行很多的消息。

不过HandlerThread在Android源码中有一个典型的用法,那就是 IntentService。是的,就是四大组件之一的Service。
IntentService可以说成是一种一次性的Service,用完即走的那种。

  • IntentService通过结合HandlerThread完成一些异步耗时操作,并在操作完成后,自动销毁Service

  • IntentService是个抽象类,必须继承后实现onHandleIntent(intent: Intent?)方法,当通过 context.startService(intent)启动IntentService后,会调用 onHandleIntent(intent: Intent?)来做对应的操作。

  • 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); // 主动终止 Service
        }
    }
  • 当消息队列中消息全部执行完之后,那么IntentService会自动退出。下一次的startService() 会重新创建一个IntentService。

以前我一直有个未知之谜,上述代码中onHandleIntent()之后就会立马执行执行stopSelf(msg.arg1),服务就应该停止了啊!毕竟已经mServiceLooper.quit()了. 为什么还会把消息队列执行完?

IntentService.java
    @Override
    public void onDestroy() {
        mServiceLooper.quit();
    }

网上文章翻了很多篇都没有提到,源码也看了很多遍,折腾一番才发现问题出在stopSelf(msg.arg1)的参数中msg.arg1,接下来就简单多了,当做课后作业,大家自己去源码翻一翻吧。

使用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)
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容

  • 一、提出问题 面试时常被问到的问题: 简述 Android 消息机制 Android 中 Handler,Loop...
    崽子猪阅读 1,516评论 0 10
  • 系列文章Android面试攻略(1)——Android基础Android面试攻略(2)——异步消息处理机制Andr...
    黎清海阅读 1,346评论 0 10
  • 1、谈谈消息机制Hander作用、有哪些要素、流程是怎样的? 作用:当子线程中进行耗时操作后需要更新UI时,通过H...
    sssssss_阅读 887评论 0 0
  • 异步消息处理线程启动后会进入一个无限的循环体之中,每循环一次,从其内部的消息队列中取出一个消息,然后回调相应的消息...
    cxm11阅读 6,422评论 2 39
  • 移动开发中,我们要处理好主线程与子线程之间的关系,耗时的操作应安排到子线程中,避免阻塞主线程,导致ANR异步处理技...
    凯玲之恋阅读 1,442评论 0 0