消息机制 - Handler 使用

4 篇文章 0 订阅
3 篇文章 0 订阅

消息机制

提到消息机制,很快想到 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

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

练习代码地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值