Android 中的消息机制

前言

涉及知识点:

  • 消息机制:Handler、Looper 和 MessageQueue
  • AsyncTask 基本使用
  • 实现一个简单的 SimpleAsyncTask

消息机制

Android 中的消息机制由三大部分组成:Handler、Looper 和 MessageQueue.

Looper 就是创建一个 MessageQueue,然后进入一个死循环里面不断地读取 MessageQueue 里面的消息,Handler 就是消息的创建者,处理者。

Android 中的消息机制_第1张图片
消息机制

由图我们可以看出,消息队列被封装在了 MessageQueue 中,通过 Looper 和线程 Thread 关联起来。而 Handler 又通过 Looper 关联,因而 Handler 最终和线程、线程的消息队列关联上来了。这也就是为什么我们常说更新 UI 的 Handler 必须要在主线程中创建,因为只有在主线程中创建,Handler 才能和主线程的消息队列关联上,这样 handleMessage 才会执行在 UI 线程,这时候更新 UI 才是线程安全的。

题外话:为什么常说只能在 UI 线程更新 UI ?

子线程可以有好多个,但如果每个子线程都直接对UI元素进行操作,界面会混乱不堪,线程会面临安全问题,虽然可以通过加锁机制来解决线程的安全问题,但是加锁会降低运行效率, 所以主线程(UI线程)并没进行加锁限制多线程访问, 可能这就是“出于性能优化考虑”。

既然没有对多线程访问进行限制,而且子线程依然有进行UI操作的需求,那么该如何解决呢?

所以Android规定只能在主线程中进行UI元素的更改,你们一帮菜鸡子线程如果还执意要来修改我管辖的用户界面 就必须先通知我(主线程),我来帮你们完成 :)

——知乎用户:大大大大头啊

创建自己的消息队列

我们知道了基本的消息机制。但是,要注意的是,Android 中除了 UI 线程,创建的工作线程默认是没有消息循环和消息队列的。

在非主线程直接 new Handler(); 会报错

Uncaught handler: thread Thread-8 exiting due to uncaught exception

java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()

所以,如果想让自己创建的工作线程 有消息循环和消息队列,并具有消息处理机制,就需要在线程中先调用 Looper.prepare() 来创建消息队列,然后调用 Loop.loop() 进入消息循环

public class MyloopThread extends Thread{
  Handler mHandler;
  
  public void run(){
    Loop.prepare();
    
    mHandler = new Handler(){
      public void handleMessage(Message msg){
        // process incoming message here
      }
    };
    
    Loop.loop();
  }
}

AsyncTask 基本使用

我们往往使用 Thread 创建子线程进行耗时操作,但是由于不能在子线程更新 UI,一般就会使用 Handler 发送消息给 UI 线程然后再更新。这个操作起来有点麻烦,在多个任务同时执行的时候,不易于对线程进行精细控制。于是 AsyncTask 应运而生。

public abstract class AsyncTask< Param, Progress, Result > ( ) 
//三个泛型类型: < 参数类型,后台执行任务的进度类型,返回的结果类型 > 如果不需要某个类型可以设置为 void

一个异步任务一般包含以下步骤:

Android 中的消息机制_第2张图片
AsyncTask

实现一个简单的 AsyncTask

下面我们来实现一个简单的 AsyncTask,类名为 SimpleAsyncTask。与 AsyncTask 类似,提供了三个函数:onPreExecute( )、 doInBackground( )、onPostExecute( )。泛型参数为了方便只有一个 doInBackgroud( ) 函数返回值类型的泛型参数。SimpleAsyncTask 执行起来和 AsyncTask 基本一样。首先是 onPreExecute 函数在任务运行之前执行,而且运行在 UI 线程之中。doInBackgroud 运行在后台执行耗时操作,并且将结果返回。onPostExecute 含有一个参数,这个参数就是 doInBaskgroud 的返回结果,onPostExecute 执行在 UI 线程。


public abstract  class SimpleAsyncTask {

    private static final HandlerThread HT = new HandlerThread("SimpleAsyncTask",
            Process.THREAD_PRIORITY_BACKGROUND);
    static {
        HT.start();
    }

    final Handler mUIHandler = new Handler(Looper.getMainLooper());
    final Handler mAsyncHandler = new Handler(HT.getLooper());

    /**
     * @功能描述:onPreExecute 任务执行之前的初始化操作等
     */
    protected void onPreExecute(){}

    /**
     * 后台执行任务
     * @return 返回执行结果
     */
    protected abstract Result doInBackground();

    /**
     * 返回结果传递给执行在 UI 线程的 onPostExecute
     * @param result 执行结果
     */
    protected void onPostExecuted(Result result){ }

    public final SimpleAsyncTask excute () {
        onPreExecute();

        mAsyncHandler.post(new Runnable() {
            @Override
            public void run() {
                postResult(doInBackground());
            }

        });
        return this;
    }

    private void postResult(final Result result){
        mUIHandler.post(new Runnable() {
            @Override
            public void run() {
                onPostExecuted(result);
            }
        });
    }

}

在 SimpleAsyncTask 里面首先创建了一个 HandlerThread(自带消息队列的 Thread),当线程启动之后就会构建它的消息队列,所以构建完成后,直接在静态代码块里面启动了该线程。然后创建了两个 Handler,分别关联 UI 线程和 HandlerThread 的子线程 mAsyncHandler。剩下三个函数已经解释过了,有需要的时候我们可以重写这三个方法。

execute 是执行的函数,里面先调用 onPreExecute,然后 doInBackground 函数被一个 Runnbale 包装通过 mAsyncTask 提交给了 HandlerThread 线程执行,当得到结果的时候又通过 mUIHandler 将结果提交到一个 Runnable 里面,这个 Runnbale 中执行了 onPostExecute。

下面是调用的示例代码:

new SimpleAsyncTask() {
    private void makeToast(String msg){
        Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onPreExecute() {
        makeToast("onPreExecute");
    }

    @Override
    protected String doInBackground() {
        try{
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "doInBackground finish!";
    }

    @Override
    protected void onPostExecuted(String s) {
        makeToast("onPostExecuted"+s);
    }
}.excute();

执行结果就是先 Toast:onPreExecute,延时 6 秒之后 Toast: onPostExecuted doInBackground finish!

Android 中的消息机制_第3张图片
执行结果

喜欢就点个赞,有问题就留个言,你不说话我怎么知道你来过


你可能感兴趣的:(Android 中的消息机制)