浅析Handler机制原理

Android的UI线程主要负责处理用户的按键事件、用户触屏事件以及屏幕绘图事件等,耗时操作放在后台进程进行。
那么,UI线程与后台线程之间必然需要进行通信,于是就引入了Handler机制,也就是Android线程间的消息传递机制。
在阅读了Android Handler机制相关的源代码后,做了如下笔记:

一、Handler机制中的主要角色

Handler流程图.png

1.Handler

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. 
Each Handler instance is associated with a single thread and that thread's message queue.
 When you create a new Handler it is bound to a Looper. 
It will deliver messages and runnables to that Looper's message queue and execute them on that Looper's thread.

There are two main uses for a Handler: 
(1) to schedule messages and runnables to be executed at some point in the future; 
and (2) to enqueue an action to be performed on a different thread than your own.

以上是官方的解释,大概意思如下:
Handler对象和单个线程和这个线程的消息队列相关联,当你创建Handler的时候,他就绑定了一个Looper。(个人理解:就是说Handler,Looper,Thread,MessageQueue这些东西是耦合在一块的)

Handler主要有两个作用:
1.安排messages和runnables在将来某个时间点被执行
2.将一个操作加入队列,在别的线程执行。

官方说的太抽象,可以参考Handler流程图中步骤2和4。

2.Looper

Class used to run a message loop for a thread.
 Threads by default do not have a message loop associated with them;
 to create one, call prepare in the thread that is to run the loop, and then loop to have it process messages until the loop is stopped.
Most interaction with a message loop is through the Handler class.

Looper用来为一个线程进行消息循环,默认的线程是没有关联Looper的,你得调用prepare方法,然后再调用loop方法让它处理messages,直到循环被中止。

参考Handler流程图中步骤3。

3.MessageQueue

Low-level class holding the list of messages to be dispatched by a Looper. 
Messages are not added directly to a MessageQueue, but rather through Handler objects associated with the Looper.
You can retrieve the MessageQueue for the current thread with Looper.myQueue().

包含由Looper调度的消息列表的低级类。Messages不是直接被添加到MessageQueue的,而是通过关联了Looper的Handler对象来做这个事情。你可以用Looper.myQueue()来获取到当前线程的MessageQueue。

简而言之,MessageQueue就是一个消息队列,先进先出,用来管理message。

4.Message

Defines a message containing a description and arbitrary data object that can be sent to a Handler. 
This object contains two extra int fields and an extra object field that allow you to not do allocations in many cases.
While the constructor of Message is public, 
the best way to get one of these is to call Message.obtain() or one of the Handler.obtainMessage() methods,
 which will pull them from a pool of recycled objects.

Message包含两个额外的int字段和一个你在许多情况下不用做分配的Object字段。
获取Message最好的方法是调用Message.obtain()或者Handler.obtainMessage(),这两个方法将会从回收利用的池子里给你捞出一个message给你用,减少了内存开销。

二、Handler使用实例

获取百度首页的数据,并展示到TextView中

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    TextView tv;
    Handler mHandler;
    private static final int MSG_TO_MAIN = 111;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tv = findViewById(R.id.tv);

        //创建Handler对象
        mHandler = new Handler(Looper.getMainLooper()){
            @Override
            public void handleMessage(Message message){
                super.handleMessage(message);
                switch (message.what){
                    case MSG_TO_MAIN:
                        tv.setText((String)message.obj);
                        break;
                    default:
                        break;
                }
            }
        };

    }

    /**
     *
     * @param view R.id.button
     */
    public void requestData(View view) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    //使用okHttp进行网络请求
                    OkHttpClient  client = new OkHttpClient();
                    Request request = new Request.Builder()
                            .url("https://www.baidu.com")
                            .build();
                    Response response = client.newCall(request).execute();
                    String result = response.body().string();
                    //将result反馈给主线程
//                    Message message = Message.obtain();
                    Message message = mHandler.obtainMessage();
                    message.what = MSG_TO_MAIN;
                    message.obj = result;
                    mHandler.sendMessage(message);
//                    message.sendToTarget();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

这是一个后台线程向主线程发送消息的例子,值得注意的是,在这个例子里我们没有调用Looper.prepare()和Loop.loop(),这是因为主线程的looper是由Android环境创建的,我们不用自己调用。

三、Handler机制源码分析

以上内容,我们只是搞清楚了Handler机制的大概流程,但是具体实现又是怎么样的呢,下面我们带着问题追踪一下每一个步骤的源码:

Question1: Loop.prepare()是在干什么?

/**
 *Initialize the current thread as a looper. 
 *This gives you a chance to create handlers that then reference this looper, before actually starting the 
 *loop. 
 *Be sure to call loop() after calling this method, and end it by calling quit().
 */
public static void prepare() {
        prepare(true);
 }

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
 }

看到这里,它的大概意思是要将looper绑定到当前线程,也就是说一个线程有且只有一个looper。而且looper是线程隔离的,你有你的looper,我有我的looper,互不干涉。

然而,这只是我们的一个直观感受,它的真实面目是这样的吗,我们还能接着研究这一行代码:

   sThreadLocal.set(new Looper(quitAllowed));

要弄清楚这一行代码,就得搞清楚ThreadLocal,ThreadLocalMap和Thread之间的关系。


三者关系.png

ThreadLocal是一个泛型类,她负责维护内部的ThradLocalMap,ThreadLocalMap用于存储线程隔离的变量,比如Looper。

ThreadLocal结构.png

下面主要看一下ThradLocal的get和set方法:

//set
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}

//get
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
}

至此,我们对Looper.prepare()有了一个大概的了解,但是关于ThreadLocal还有很多问题值得探讨,本文旨在浅析Hanler机制的原理,所以跳过关于ThreadLocal的一些疑问,接着往下看。

Question2: Loooper.loop()发生了什么?

//关键代码
public static void loop() {
    final Looper me = myLooper();
    ...
    //死循环,一直处理
    for(; ;){
        if(!loopOnce(me, ident, thresholdOverride)){
            return;
        }
    }
}

接下来,我们就不得不看看loopOnce这个方法了

//仅展示关键代码
private static booloean loopOnce(final Looper me, final log ident , final int thresholdOverride){
    ...
    msg.target.dispatchMessage(msg);
    ...
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,657评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,889评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,057评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,509评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,562评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,443评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,251评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,129评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,561评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,779评论 3 335
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,902评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,621评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,220评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,838评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,971评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,025评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,843评论 2 354

推荐阅读更多精彩内容