Handler消息传递机制学习笔记和demo实现

0,写在前面

博主在学习Handler消息传递机制时,学习的方法是通过问题来驱动理解,都是关注度比较高的问题,以下是我在学习后的简单整理,欢迎吐槽!

1,Handler是什么?能干嘛?

Google的Android开发文档是这么写的:
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 the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.
There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.
简言之,就是android.os.Handler允许我们发送和处理与线程的MessageQueue相关的Message和Runnable对象。 每个Handler实例都与单个线程和该线程的消息队列相关联。
主要用于:1)消息创建;2)将消息插入到队列中;3)处理消费者线程上的消息;4)管理队列中的消息。

2,为什么需要Handler?

image.png

这张图片清晰的说明了谷歌工程师为什么要开发Handler这个类。可以看出,Google工程师开发它主要是为了解决在非UI线程中更新UI组件比较麻烦的问题。

3,Handler都有哪些特点?

handler可以分发Message对象和Runnable对象到主线程中, 每个Handler实例,都会绑定到创建他的线程中(一般是位于主线程),它有两个作用:
1)安排消息或Runnable 在某个主线程中某个地方执行;
2)安排一个动作在不同的线程中执行。
Handler中分发消息的一些方法
post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message,long)
sendMessageDelayed(Message,long)
以上post类方法允许你排列一个Runnable对象到主线程队列中,
sendMessage类方法, 允许你安排一个带数据的Message对象到队列中,等待更新。

4,Handler执行的流程图

image.png

UI线程:就是主线程,系统在创建UI线程的时候会初始化一个Looper对象,同时也会 创建一个与其关联的MessageQueue;

Handler:作用就是发送与处理信息,如果希望Handler正常工作,在当前线程中要有一个Looper对象

Message:Handler接收与处理的消息对象

MessageQueue:消息队列,先进先出管理Message,在初始化Looper对象时会创建一个与之关联的MessageQueue;

Looper:每个线程只能够有一个Looper,管理MessageQueue,不断地从中取出Message分发给对应的Handler处理!

大白话来说,就是当子线程想修改Activity中的UI组件时,我们可以新建一个Handler对象,通过这个对象向主线程发送信息;而发送的信息会先到主线程的MessageQueue进行等待,由Looper按先入先出顺序取出,再根据message对象的what属性分发给对应的Handler进行处理。

5,如何理解Handler消息处理机制?

消息处理机制本质:一个线程开启循环模式持续监听并依次处理其他线程给它发的消息。
简单来说,就是一个线程开启一个无限循环模式,不断遍历自己的消息列表,如果有消息就挨个拿出来做处理,如果列表没消息,自己就堵塞(相当于wait,让出cpu资源给其他线程),其他线程如果想让该线程做什么事,就往该线程的消息队列插入消息,该线程会不断从队列里拿出消息做处理。

6,Looper、Handler、MessageQueue,Message作用和存在的意义?

  6.1,Looper 
  我们知道一个线程是一段可执行的代码,当可执行代码执行完成后,线程生命周期便会终止,线程就会退出,那么做为App的主线程,如果代码段执行完了会怎样?,那么就会出现App启动后执行一段代码后就自动退出了,这是很不合理的。所以为了防止代码段被执行完,只能在代码中插入一个死循环,那么代码就不会被执行完,然后自动退出,怎么在在代码中插入一个死循环呢?那么Looper出现了,在主线程中调用Looper.prepare()...Looper.loop()就会变当前线程变成Looper线程(可以先简单理解:无限循环不退出的线程),Looper.loop()方法里面有一段死循环的代码,所以主线程会进入while(true){...}的代码段跳不出来,但是主线程也不能什么都不做吧?其实所有做的事情都在while(true){...}里面做了,主线程会在死循环中不断等其他线程给它发消息(消息包括:Activity启动,生命周期,更新UI,控件事件等),一有消息就根据消息做相应的处理,Looper的另外一部分工作就是在循环代码中会不断从消息队列挨个拿出消息给主线程处理。

  6.2,MessageQueue 
  MessageQueue 存在的原因很简单,就是同一线程在同一时间只能处理一个消息,同一线程代码执行是不具有并发性,所以需要队列来保存消息和安排每个消息的处理顺序。多个其他线程往UI线程发送消息,UI线程必须把这些消息保持到一个列表(它同一时间不能处理那么多任务),然后挨个拿出来处理,这种设计很简单,我们平时写代码其实也经常这么做。每一个Looper线程都会维护这样一个队列,而且仅此一个,这个队列的消息只能由该线程处理。

  6.3,Message 
  想让主线程做什么事,总要告诉它吧,总要传递点数据给它吧,Message就是这个载体。

  6.4,Handler 
  简单说Handler用于同一个进程的线程间通信。Looper让主线程无限循环地从自己的MessageQueue拿出消息处理,既然这样我们就知道处理消息肯定是在主线程中处理的,那么怎样在其他的线程往主线程的队列里放入消息呢?其实很简单,我们知道在同一进程中线程和线程之间资源是共享的,也就是对于任何变量在任何线程都是可以访问和修改的,只要考虑并发性做好同步就行了,那么只要拿到MessageQueue 的实例,就可以往主线程的MessageQueue放入消息,主线程在轮询的时候就会在主线程处理这个消息。那么怎么拿到主线程 MessageQueue的实例,是可以拿到的(在主线程下mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),但是Google 为了统一添加消息和消息的回调处理,又专门构建了Handler类,你只要在主线程构建Handler类,那么这个Handler实例就获取主线程MessageQueue实例的引用(获取方式mLooper = Looper.myLooper();mQueue = mLooper.mQueue;),Handler 在sendMessage的时候就通过这个引用往消息队列里插入新消息。Handler 的另外一个作用,就是能统一处理消息的回调。这样一个Handler发出消息又确保消息处理也是自己来做,这样的设计非常的赞。具体做法就是在队列里面的Message持有Handler的引用(哪个handler 把它放到队列里,message就持有了这个handler的引用),然后等到主线程轮询到这个message的时候,就来回调我们经常重写的Handler的handleMessage(Message msg)方法。

7,Handler的用法

 7.1,消息的创建
  Message obtainMessage(int what, int arg1, int arg2);
  Message obtainMessage();
  Message obtainMessage(int what, int arg1, int arg2, Object obj);
  Message obtainMessage(int what);
  Message obtainMessage(int what, Object obj);

  7.2,将消息插入到队列中
      7.2.1,添加任务到消息队列中
      Message obtainMessage(int what, int arg1, int arg2)
      Message obtainMessage()
      Message obtainMessage(int what, int arg1, int arg2, Object obj)
      Message obtainMessage(int what)
      Message obtainMessage(int what, Object obj)

      7.2.2,添加数据对象到消息队列中
      boolean sendMessage(Message msg)
      boolean sendMessageAtFrontOfQueue(Message msg)
      boolean sendMessageAtTime(Message msg, long uptimeMillis)
      boolean sendMessageDelayed(Message msg, long delayMillis)

      7.2.3,添加数据对象到消息队列中
      boolean sendEmptyMessage(int what)
      boolean sendEmptyMessageAtTime(int what, long uptimeMillis)
      boolean sendEmptyMessageDelayed(int what, long delayMillis)

  7.3,消息的两种处理形式
      7.3.1,任务消息
      handler.post(new Runnable() {
         @Override
         public void run() {
           //TODO : Do some operation
         }
      });
      7.3.2,数据消息
        final Handler handler = new Handler() {
           @Override
           public void handleMessage(Message message) {
              //TODO : Get the data from Message and perform opertation accordingly.
           }
       };

      handler.sendMessage(message);

  7.4,如何跟踪消息队列处理?
    Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, TAG));
  Example of tracing a message:

      handler.post(new Runnable() {
        @Override
        public void run() {
            Log.d(TAG, "Executing Runnable");
        }
      });
      mHandler.sendEmptyMessage(111);

8,Handler使用的一个简单demo实现

Thread + Handler
布局代码:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="26dp"
android:orientation="vertical"
tools:context="com.example.luolu.handlerwiththreaddemo.ThreadHandlerAndroidExample">

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center_horizontal"
    android:autoLink="web"
    android:text="http://www.baidu.com/"
    android:textStyle="bold"
    android:textSize="20sp"/>

<Button
    android:id="@+id/start"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="50dp"
    android:text="Start"/>

<ProgressBar
    android:id="@+id/progress"
    style="?android:attr/progressBarStyleHorizontal"
    android:indeterminate="false"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:max="10"
    android:progress="0"/>
<TextView
    android:id="@+id/msg"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

</LinearLayout>

ThreadHandlerAndroidExample.java:
package com.example.luolu.handlerwiththreaddemo;

import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

/**

  • @author luolu
    */
    public class ThreadHandlerAndroidExample extends AppCompatActivity {

    private Handler handler = new Handler();
    private MyHandlerThread myHandlerThread;

    Button btnStart;
    ProgressBar progressBar;
    TextView textMsg;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

     progressBar = (ProgressBar)findViewById(R.id.progress);
     textMsg = (TextView)findViewById(R.id.msg);
     btnStart = (Button)findViewById(R.id.start);
    
     myHandlerThread = new MyHandlerThread("myHandlerThread");
     final Runnable myRunnable = new Runnable() {
    
         @Override
         public void run() {
             for (int i = 0; i <= 10; i++) {
                 try {
                     Thread.sleep(1000);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
    
                 //is accessed from within inner class, needs to be declared final
                 final int finalI = i;
                 handler.post(new Runnable() {
                     @Override
                     public void run() {
                         progressBar.setProgress(finalI);
                     }
                 });
             }
    
             handler.post(new Runnable() {
                 @Override
                 public void run() {
                     textMsg.setText("finished");
                 }
             });
         }
     };
    
     myHandlerThread.start();
     myHandlerThread.prepareHandler();
    
     btnStart.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) {
             myHandlerThread.postTask(myRunnable);
         }
     });
    

    }

    @Override
    protected void onDestroy() {
    myHandlerThread.quit();
    super.onDestroy();
    }

    public class MyHandlerThread extends HandlerThread {

     private Handler handler;
    
     public MyHandlerThread(String name) {
         super(name);
     }
    
     public void postTask(Runnable task){
         handler.post(task);
     }
    
     public void prepareHandler(){
         handler = new Handler(getLooper());
     }
    

    }
    }

此外,还需在清单文件中添加权限:
<uses-permission android:name="android.permission.INTERNET"></uses-permission>

实现效果图:


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

推荐阅读更多精彩内容