Android 开发艺术探索笔记(十四) 之 Android 消息机制

一、Android 消息机制概述

Android 消息机制是指 Handler 的运行机制,Handler 的运行需要底层的 MessageQueue (消息队列)Looper(消息循环) 的支撑。Handler 主要是将一个任务切换到某个指定的线程中去执行。

二、Handler 的使用栗子

public class MainActivity extends Activity implements Button.OnClickListener {

    private TextView statusTextView;

    //uiHandler在主线程中创建,所以自动绑定主线程的 Looper
    private Handler uiHandler = new Handler();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        statusTextView = (TextView)findViewById(R.id.statusTextView);
        Button btnDownload = (Button)findViewById(R.id.btnDownload);
        btnDownload.setOnClickListener(this);
        System.out.println("Main thread id " + Thread.currentThread().getId());
    }

    @Override
    public void onClick(View v) {
        DownloadThread downloadThread = new DownloadThread();
        downloadThread.start();
    }

    class DownloadThread extends Thread{
        @Override
        public void run() {
            try{
                System.out.println("DownloadThread id " + Thread.currentThread().getId());
                System.out.println("开始下载文件");
                //此处让线程DownloadThread休眠5秒中,模拟文件的耗时过程
                Thread.sleep(5000);
                System.out.println("文件下载完成");
                //文件下载完成后更新UI
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("Runnable thread id " + Thread.currentThread().getId());
                        MainActivity.this.statusTextView.setText("文件下载完成");
                    }
                };
                uiHandler.post(runnable);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

Handler 是在创建的时候绑定实例化所在的线程的 Looper,所以当 Handler 发送消息的时候,所绑定的 Looper 就会在绑定的线程中取消息,从而将任务切换到制定的线程中执行。

三、Handler 机制分析

  • 1. ThreadLocal:一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储后,只有在指定的线程才能获取存储的数据,对于其他线程来说则无法获取到数据。

举个栗子:

public class MainActivity extends AppCompatActivity {

    private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBooleanThreadLocal.set(true);
        Log.i("TAG","[MainThread] 的值:"+mBooleanThreadLocal.get());

        new Thread("Thread#1"){
            @Override
            public void run() {
                mBooleanThreadLocal.set(false);
                Log.i("TAG","[Thread#1] 的值:"+mBooleanThreadLocal.get());
            }
        }.start();


        new Thread("Thread#2"){
            @Override
            public void run() {
                Log.i("TAG","[Thread#2] 的值:"+mBooleanThreadLocal.get());
            }
        }.start();
    }
}

打印结果:

04-06 08:30:01.403 2824-2824/com.innovator.handlertest I/TAG: [MainThread] 的值:true
04-06 08:30:01.408 2824-2849/com.innovator.handlertest I/TAG: [Thread#1] 的值:false
04-06 08:30:01.411 2824-2850/com.innovator.handlertest I/TAG: [Thread#2] 的值:null

可以看到在哪个线程设置了值后,在对应线程取出的数据不受其他线程的影响。原因就是:不同线程访问同一个 ThreadLocal 对象的 get(),ThreadLocal 内部会从各自的线程中取出一个数组,然后在从数组中根据当前 ThreadLocal 的索引去查找对应的 value 值。显然不同线程的数组是不同的。

  • 2. MessageQueue 的工作原理

Android 中的消息队列是指 MessageQueue,它主要包含两个操作:enqueueMessage(插入消息)next(读取删除消息)。原理主要是单链表的插入和删除操作。

  • 3. Looper 工作原理

    Looper 在 Android 消息机制中扮演着消息循环的角色,具体来说就是它会不停地从 MessageQueue 中查看是否有新消息,如果有新消息就会立刻处理,否则就一直阻塞在那里。

    我们都知道 Handler 工作需要 Looper,没有 Looper 的线程就会报错。创建 Looper 的方法如下:

    new Thread("Thread#3"){
            @Override
            public void run() {
                // 在子线程中创建 Looper
                Looper.prepare();
                Handler handler = new Handler();
                Runnable runnable = new Runnable() {
                    @Override
                    public void run() {
                        Log.i("TAG","Runnable 运行在:"+Thread.currentThread().getName());
                    }
                };
    
                handler.post(runnable);
                Looper.loop();
                Looper.myLooper().quit();
            }
        }.start();
    

打印结果:

Runnable 运行在:Thread#3

可见 Runnable 是运行在 Handler 实例化的线程,Looper.loop(); 是一个死循环操作,一直阻塞读取消息,所以如果在子线程中使用 Looper,记得要在处理完所有事情后调用 quit() 终止消息循环,否则子线程就一直处于等待的状态。

  • 4. Handler 工作原理

    Handler 的工作包括发送和接收两个过程,消息的发送可以通过 post() 或者 sendMessage() 来实现,这个过程其实是将一个消息插入队列,然后 MessageQueue 的 next() 就会返回这条消息给 Looper,Looper 收到消息后就开始处理,最终消息由 Looper 交由 Handler 处理,即 Handler 的 dispatchMessage() 被调用。

Handler 的 diapatchMessage()

public void dispatchMessage(Message msg){
    if(msg.callback != null){
        handleCallback(msg);
    }else{
        if(mCallback != null){
            if(mCallback.handleMessage(msg)){
                return;
            }
        }
        handleMessage(msg);
    }
}

首先检查 Message 的 callback 是否为 null,不为 null 就通过 handleCallback(msg); 来处理消息。 Message 的 callback 是一个 Runnable 对象,就是 Handler 的 post() 的参数,其实就是执行这里面的 run() 方法的内容。

其次就是检查 mCallBack 是否为空,不为空就调用 mCallBackhandleMessage()

所以在实例化 Handler 的时候有两种构造方法:

  • 使用 Callback 创建 Handler 实例,但不派生 Handler 的子类
mMainHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message message) {
                return false;
            }
        });
  • 派生 Handler 的子类
mMainHandler = new Handler() {

            @Override
            public void handleMessage(Message msg) {
                
            }

        };

四、Handler、Looper、MessageQueue的关系

Looper 负责的就是创建一个 MessageQueue,然后进入一个无限循环体。在这个循环体中不断从该 MessageQueue 中读取消息,而消息的创建者就是一个或多个 Handler 。

Looper 的 prepare():

public static final void prepare() {  
        if (sThreadLocal.get() != null) {  
            throw new RuntimeException("Only one Looper may be created per thread");  
        }  
        // 保证了一个线程只有一个 Looper,一个 MessageQueue
        sThreadLocal.set(new Looper(true));  
} 

这里可以看到 Looper 里面有一个 ThreadLocal 的成员变量,所以 Looper 能保证每个线程存放的 Looper 对象都不一样。

Looper 的构造方法:

private Looper(boolean quitAllowed) {  
        mQueue = new MessageQueue(quitAllowed);  
        mRun = true;  
        mThread = Thread.currentThread();  
}  

可以看到 Looper 实例化了一个 MessageQueue 对象并保存了当前的线程对象。到这里我们就更能确定 Looper 只与绑定的线程有关,这也是为了后面 Handler 能将任务切换到指定的线程做铺垫。

具体的流程:

1、首先 Looper.prepare() 在本线程中保存一个 Looper 实例,然后该实例中保存一个 MessageQueue 对象;因为 Looper.prepare() 在一个线程中只能调用一次,所以 MessageQueue 在一个线程中只会存在一个。

2、Looper.loop() 会让当前线程进入一个无限循环,不断从MessageQueue 的实例中读取消息,然后回调 msg.target.dispatchMessage(msg) 方法。

3、Handler 的构造方法,会首先得到当前线程中保存的 Looper 实例,进而与 Looper 实例中的 MessageQueue 相关联。

4、Handler 的sendMessage() 方法,会给 msg 的 target 赋值为
handler 自身,然后加入 MessageQueue 中。

5、在构造 Handler 实例时,我们会重写 handleMessage() 方法,也就是 msg.target.dispatchMessage(msg) 最终调用的方法。

看完这么多分析后,我可以推断我之前想到的一个问题了:一个线程能拥有多个 Handler 吗?这些 Handler 会互相干扰吗?

答案:一个线程可以拥有多个 Handler,并且这些 Handler 都是共用同一个线程的 Looper 和 MessageQueue,而且不会互相干扰,因为 Looper 取消息后的回调是 dispatchMessage(),这里的 msg.target 是之前发送消息的那个 Handler。

五、参考资料

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

推荐阅读更多精彩内容