Android开发经验谈Android技术知识Android开发

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

2019-10-13  本文已影响0人  真心czx

目录

  1. 前言及消息机制简述
  2. Handler的日常使用
  3. HandlerThread和IntentService中的Handler
  4. 消息机制源码剖析

一、前言及概述

1、前言

这篇剖析是17年为了应对面试而复习后记在有道云上的,是时候拿出来完善下并共享给各位指点下了。

不管是小白还是老手,入门还是复习,我觉得都是会有所收获的。

来看看这几个问题:

  1. 主线程的Looper什么时候quit?(新手真的会问的)
  2. 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的日常使用

使用一般两种情况

  1. 线程内自己给自己发消息,写到这懵了一下,为啥要给自己发???比如刚刚的button点击之后我直接执行所需要执行的操作不就好了?干嘛再抛出一个消息,去排队执行!哦,想起来了!排队!不仅排队,还能插队以及延时执行。
    指定在将来的某个时间点去执行消息。也就是延时执行。

  2. 线程间的通信,典型就是非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=0message
那么当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中的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);
        }
    }

以前我一直有个未知之谜,上述代码中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)
        }
    }
}
上一篇 下一篇

猜你喜欢

热点阅读