消息机制 - Handler 使用

消息机制

提到消息机制,很快想到 Handler,没错,Android 的消息机制主要就是 Handler 的运行机制,另外也不能缺少 Looper 和 MessageQueue。很多人认为 Handler 的作用是用来更新 UI 操作的,多数情况下, Handler 的确用来更新 UI,由于 Android 机制的限制,UI 操作只能在主线程完成,所以当进行一些耗时操作,如网络请求,IO 操作,或者一些其他需要在子线程中进行的延迟操作,结束时,可能需要将数据更新到 UI 上,这时候需要使用 Handler 来完成,如果直接更新 UI 组件,会报错。

虽然 Handler 常用来更新 UI,但仅仅它的一个常见用法,也可以用来在不同的线程中进行数据交互。Handler,Looper 和 MessageQueue 都是线程绑定的,当在一个线程中创建 Looper 和 MessageQueue 后,通过 这个线程的 Handler 就可以向 MessageQueue 中发送消息(Message 或 Runnable),就可实现其他线程操作该线程的目的。

Android 系统不允许在子线程中访问 UI(有特例情况,后面有单独文章来说明),在《Android 开发艺术探索》一书中,有提到, Android 的 UI 组件不是线程安全的,如果多线程并发访问,可能到出现异常情况。如果通过加锁来控制的话,一方面会因为加锁后导致控制的逻辑比较复杂,另一方面也会因为加锁后导致执行的效率下降,所以系统设计时采用的是单线程模型来处理 UI 操作。所以才有了 Handler 来辅助进行切换到主线程完成 UI 更新操作。

Handler,Looper 和 MessageQueue 紧密相连,一个线程开启后,就可以创建 Looper 和 MessageQueue,对于每个线程来说,只能有一个 Looper 和 MessageQueue,但是 Handler 可以有多个, Handler 是和这个线程绑定的。这样,通过 Handler 就可以发送消息到 MessageQueue。MessageQueue 是一个单链表结构的消息队列, Looper 就是一个无限循环体,通过它的不断循环来处理消息队列中的消息,当然消息处理由取出来的 Message 回调 Handler 来处理,其中 Looper 的循环,当没有消息时会处于空闲状态(不占用 CPU),并不会退出循环,如果退出循环,程序也就退出了(这里指的是应用的主线程)。

消息机制大致的过程就是上述描述的,后面也会有单独的文章来分析源码。

Handler.jpg

Handler 构造


 public Handler();
 
 public Handler(Callback callback);

 public Handler(Looper looper);
 
 public Handler(Looper looper, Callback callback);
 
 public Handler(boolean async);
 
 public Handler(Callback callback, boolean async);
 
 public Handler(Looper looper, Callback callback, boolean async);

Handler 构造函数有很多个,但实际上参数类型就是三种,主不过组合起来有多个,Callback、Looper、async

(1)参数一:CallBack 是一个接口,作用是当我们发送消息时,消息如何处理,需要我们自己来决定,会回调这个接口。

public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        public boolean handleMessage(Message msg);
    }

(2)参数二:Looper,指定循环体,当构造函数中没有这个参数时,可以通过 ThreadLocal 来获取当前线程的 Looper,如果自己指定 Looper 的话,

一种是自己获取当前线程的 Looper:

Looper.myLooper();

另一种是获取主线程的 Looper:

Looper.getMainLooper();

使用时看自己的需要。

(3)参数三:boolean async 是否是异步

关于这个参数,源码中有这么一段描述,异步消息不是在全局的同步序列中中断或者事件。异步消息不受同步屏障的影响。默认情况下,是同步消息,也就是说,发送的消息到消息队列当中,消息默认会同步执行。


     * Asynchronous messages represent interrupts or events that do not require global ordering
     * with respect to synchronous messages.  Asynchronous messages are not subject to
     * the synchronization barriers introduced by {@link MessageQueue#enqueueSyncBarrier(long)}.

发送消息的方法

(1)post 系列

public final boolean post(Runnable r)

public final boolean postAtTime(Runnable r, long uptimeMillis)

public final boolean postAtTime(Runnable r, Object token, long uptimeMillis)

public final boolean postDelayed(Runnable r, long delayMillis)

public final boolean postDelayed(Runnable r, Object token, long delayMillis)

public final boolean postAtFrontOfQueue(Runnable r)

方法也不少,实际上很容易理解,实际的消息是一个 Runnable 接口,虽然和线程中的 Runnable 是一个接口,但是这里只是作为一个接口,不涉及线程,实际的操作需要我们来实现。剩下的参数关注 uptimeMillis 和 delayMillis 两个时间参数,uptimeMillis 是绝对时间,采用系统时间 android.os.SystemClock#uptimeMillis 来执行消息;delayMillis 是延迟时间,加上当前的时间,来得到绝对时间。

postAtFrontOfQueue 方法是将消息添加到消息队列的头部,意味着先执行该消息。

(2)send 系列

public final boolean sendMessage(Message msg)

public final boolean sendEmptyMessage(int what)

public final boolean sendEmptyMessageDelayed(int what, long delayMillis)

public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis)

public final boolean sendMessageDelayed(Message msg, long delayMillis)

public boolean sendMessageAtTime(Message msg, long uptimeMillis)

public final boolean sendMessageAtFrontOfQueue(Message msg)

send 系列的消息和 post 基本上市对应的,实际上 post 消息最终也是采用 send 消息的方法,只不过将 Runnable 封装到 Message 中了。send 消息也不多说,比较简单主要设置 Message 就可以了,注意:产生一个Message对象,可以new ,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。

小例子

完成构造 Handler,设置消息就可以进行发送消息,下面来演示一下,采用两种方式构造 Handler,两种方式发送消息。

先来一个错误的使用,即在子线程中直接更新 UI

public class ErrorUseActivity extends AppCompatActivity {

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_error_use);
        mTextView = findViewById(R.id.text_view);
        mTextView.setText("Hello World!");
    }

    @Override
    protected void onResume() {
        super.onResume();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                mTextView.setText("Hello Error!");
            }
        }).start();

        /**
         * 这种情况下会怎么样?这里有一个疑问,后面会进行分析
         */
//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//                try {
//                    mTextView.setText("Hello Error!");
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//        }).start();
    }

上面代码中先不看注释的部分,这部分可以自己试试,看看结果,后面有单独的文章来分析它。在子线程中睡眠 3 秒,然后更新 UI,会报错:

02-26 08:30:44.807 26642-26642/wang.ralf.handler_practice I/Choreographer: Skipped 43 frames!  The application may be doing too much work on its main thread.
02-26 08:30:44.993 26642-26671/wang.ralf.handler_practice D/OpenGLRenderer: endAllActiveAnimators on 0x7bda1fa400 (RippleDrawable) with handle 0x7bdcfffbe0
02-26 08:30:47.913 26642-27106/wang.ralf.handler_practice E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: wang.ralf.handler_practice, PID: 26642
    android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
        at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7313)
        at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1161)
        at android.view.View.requestLayout(View.java:21995)
        at android.view.View.requestLayout(View.java:21995)
        at android.view.View.requestLayout(View.java:21995)
        at android.view.View.requestLayout(View.java:21995)
        at android.view.View.requestLayout(View.java:21995)
        at android.view.View.requestLayout(View.java:21995)
        at android.view.View.requestLayout(View.java:21995)
        at android.widget.TextView.checkForRelayout(TextView.java:8531)
        at android.widget.TextView.setText(TextView.java:5394)
        at android.widget.TextView.setText(TextView.java:5250)
        at android.widget.TextView.setText(TextView.java:5207)
        at wang.ralf.handler_practice.ErrorUseActivity$1.run(ErrorUseActivity.java:32)
        at java.lang.Thread.run(Thread.java:764)

意思是在主线程做了跟多的操作,因为子线程是耗时操作,所以不允许在主线程中执行。下面就是 Handler 来登场了。

public class CorrectUseActivity extends AppCompatActivity {

    private Handler mHandler;
    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_correct_use);
        mTextView = findViewById(R.id.text_view);
        mHandler = new Handler(new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                switch (msg.what) {
                    case 1:
                        String str1 = (String) msg.obj;
                        mTextView.setText(str1);
                        break;
                    case 2:
                        String str2 = (String) msg.obj;
                        mTextView.setText(str2);
                        break;
                    default:
                        break;
                }
                return false;
            }
        });
        // 无参数构造情况下,不能使用 sendMessage 方法,否则不会执行,但是有参数构造情况下,可以使用 post 方法
//        mHandler = new Handler();
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 线程1
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                    Message message = Message.obtain();
                    message.what = 1;
                    message.obj = "come from Thread 1";
                    mHandler.sendMessage(message);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        // 线程2
        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            mTextView.setText("come from Thread 2");
                        }
                    });
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

使用很简单,没啥说的,自己试试就会了,另外,还是其他方法,带时间参数的,也可以尝试下。

需要注意的是:代码中注释部分

一般情况下:

1、使用无参构造 Handler,使用 post 方法发送消息,如果 使用 send 方法发送消息,不会得到执行。

2、使用有参(含有 Callback 接口)构造 Handler,使用 send 方法发送消息,如果使用 post 方法发送消息,也能够执行。

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

简单分析下源码:消息执行时,会调用 Handler 的 dispatchMessage 方法

(1)先执行 handleCallback(msg),也就是 post 方法发送的消息,所以 post 方法发送的消息都会被执行

(2)如果不是 post 方法发送的消息,也就是 send 方法发送的消息,如果 Handler 构造中不含有 Callback 接口,也就不会回调 mCallback.handleMessage(msg) 方法,而会调用 handleMessage(msg); 这方法是一个空方法,所以发送的消息不会执行。

post_send.jpeg

代码地址

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

推荐阅读更多精彩内容