异步任务之 Handler 初解

在 Android 当中,线程分为 UI 线程和工作线程,UI 线程负责界面绘制,事件传递消耗,工作线程负责一些比较好耗时的操作。

当 UI 线程和工作线程需要产生交互时,就需要用到异步了,Android 提供了 Handler 机制来帮助我们完成线程之间的消息传递,在了解 Handler 之前,我们需要先了解几个名词:

  • Thread:线程,这个就不用多说了。在 Android 中分为 UI 线程(主线程)和工作线程。
  • MessageQueue:消息队列,主要功能是存放 Message,在队列当中,Message 采用 FIFO 机制(先进先出 First In First Out)。每个线程最多可以拥有一个 MessageQueue,UI 线程创建后,会自动创建一个 MessageQueue,其他线程默认不会创建 MessageQueue。
  • Message:MessageQueue 中存放的对象,一个 MessageQueue 可以存放多个 Message,Message 可以携带少量数据信息,可在线程之间传递。
  • Handler:用于封装和处理 Message。
  • Looper:MessageQueue 的管理者。

上面只是简单的阐述了下 Handler 机制各个组件的功能,下面我们展开来讲述一下:

Thread

Android 系统将线程分为 UI 线程和工作线程,看下面代码:

public class MainActivity extends Activity {

    private Button button;

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

        button = findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.d("TTT", "Thread Name = " + Thread.currentThread().getId() + ";Thread Name = " + Thread.currentThread().getName());
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Log.d("TTT", "Thread Name = " + Thread.currentThread().getId() + ";Thread Name = " + Thread.currentThread().getName());
                    }
                }).start();
            }
        });
    }
}

点击按钮,查看 Log:

08-01 14:41:39.968 9481-9481/com.example.lgb.asynctest D/TTT: Thread Name = 1;Thread Name = main
08-01 14:41:39.976 9481-9604/com.example.lgb.asynctest D/TTT: Thread Name = 161;Thread Name = Thread-161

可以看到,主线程是一个 ID 为 1,Name 为 main 的线程,也就是该线程,负责 UI 的绘制,事件的分发等等工作。

我们创建的线程,也就是工作线程,我们在该线程中不能去操作 UI,但是实际当中我们需要从工作线程中获取数据去更新 UI,这就产生了两个线程之间的交互需求,也就自然而然的引出了异步的概念。

Message

上面说过了,Message 是线程之间传递数据的载体,线程之间传递的信息就是负载在 Message 对象当中。

可以通过以下方法获取 Message 对象:

  • Message 的构造方法
  • 调用 Message 的静态方法 obtain()
  • 调用 Handler 对象的 obtainMessage() 方法

通过以下方法负载需要传递的信息:

  • 简单的数据,可以通过 Message 对象的 arg1arg2 两个字段设置
  • 对象,可以通过 Message 对象的 object 字段
  • 可以将需要传递的数据包装成 Bundle 对象,通过调用 Message 的 setData 方法传递出去

通过以下方法识别 Message 身份:

  • 通过设置 Message 对象的 what 字段,为该 Message 设置一个身份信息

MessageQueue

MessageQueue 是线程内部维护的一个消息队列,我们并不会干预其如何运行,我们只需要知道主线程会自动创建一个 MessageQueue(由 Looper 对象创建),非主线程并不会自动创建 MessageQueue,如果需要,需自己创建。

MessageQueue 是无限循环的,当队列中有了新 Message,则由其所在线程拿出来处理,如果没有新 Message,就一直休眠着、等待着。

Handler

Handler 负责封装发送和处理 Massage,通俗点儿讲,Handler 在线程 A 当中打包并发送 Message 至 MessageQueue,在线程 B 当中,Handler 将 Message 从 MessageQueue 当中拿出并读取其中�所负载的数据。

创建 Handler 对象:

  • Handler handler = new Handler();
  • Handler handler = new Handler(Looper);

二者不同之处在于一个 Looper 参数,Looper 会在下面讲到。

发送 Message 的方法:

  • sendMessage(Message msg):将 Message 推送至消息队列尾部
  • sendMessageAtTime(Message msg, long uptimeMillis):在特定时间将 Message 推送消息至消息队列尾部
  • sendMessageAtFrontOfQueue(Message msg):将 Message 推送到消息队列首部会导致排序问题,可能导致其他副作用
  • sendMessageDelayed(Message msg, long delayMillis):延时将 Message 推送至消息队列尾部
  • sendEmptyMessage(int what):发送一个空的 Message
  • sendEmptyMessageAtTime(int what, long uptimeMillis):在特定时间发送一个空的 Message
  • sendEmptyMessageDelayed(int what, long delayMillis):延时发送一个空的 Message
  • post 方法及变形,其本质还是 sendMessage:

    • post(Runnable r):
    • postAtFrontOfQueue(Runnable r)
    • postAtTime(Runnable r, long uptimeMillis)
    • postDelayed(Runnable r, long delayMillis)

接收消息:

  • handleMessage(Message msg):Handler 使用该方法接收和处理 Message,所以我们的子类必须重写该方法。

Looper

Looper 是一个比较重要的角色,它是将消息(Message)、消息队列(MessageQueue)、线程(Thread)和 Handler 联接到一起的重要组件。

每个线程都只能有一个 Looper 和 MessageQueue,UI 线程在创建之初就会自动创建一个 MessageQueue 和 Looper,但工作线程却不会自动创建,需要我们自动创建:

  • 调用 Looper 的静态方法 prepare() 创建 Looper 和 MessageQueue
  • 调用 Looper 的静态方法 loop() 方法让 MessageQueue “动起来”

Looper 在线程当中创建 MessageQueue 并让其“转”起来,另一个线程中将信息打包成 Message,调用 Handler 对象的 sendMessage 方法将 Message 发送到 MessageQueue 当中,当 MessageQueue 有了新消息,其所在线程就会将消息拿出来处理。

子线程给主线程发送消息

public class MainActivity extends Activity {

    private Button button;

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

        button = findViewById(R.id.button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Message message = new Message();
                        message.obj = "来自子线程的消息";
                        handler.sendMessage(message);
                    }
                }).start();
            }
        });
    }

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.d("TTT", "接收到了来自子线程的消息:" + (String) msg.obj);
        }
    };
}

代码很简单,原理也不难,UI 线程在创建时既有 Looper 和 MessageQueue,我们在子线程当中调用主线程当中的 Handler 对象的 sendMessage 方法将消息发送至主线程的 MessageQueue 当中,主线程从队列中取出消息使用。

主线程给子线程发送消息
如果要在子线程当中创建 Handler 对象,可以这样写:

    class ChildThread extends Thread {

        private Handler handler = null;

        @Override
        public void run() {
            super.run();
            Looper.prepare();
            handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    //......
                }
            };
            Looper.loop();
        }
    }

这基本可以算模板代码了,但是这样写还不够严谨,因为在其他线程当中这样写

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                childThread = new ChildThread();
                childThread.start();
                childThread.handler.sendEmptyMessage(200);
                Log.d("TTT", "1.ThreadName = " + Thread.currentThread().getName());
            }
        });

就很容易出现空指针,因为有可能新线程内的 Handler 对象还没有创建成功,这时候调用其 handler 对象的 sendMessage 方法,自然会出现空指针,所以只需要这样修改一下:

class ChildThread extends Thread {

        private Handler handler = null;
        Object syncObject = new Object();

        @Override
        public void run() {
            super.run();
            Looper.prepare();
            synchronized (syncObject) {
                handler = new Handler() {
                    @Override
                    public void handleMessage(Message msg) {
                        super.handleMessage(msg);
                        Log.d("TTT", "2.ThreadName = " + Thread.currentThread().getName());
                    }
                };
                syncObject.notifyAll();
            }
            Looper.loop();
        }

        public Handler getHandler() {
            synchronized (syncObject) {
                if (handler == null) {
                    try {
                        syncObject.wait();
                    } catch (InterruptedException e) {
                    }
                }
                return handler;
            }
        }
    }

代码很简单,给 Handler 创建的代码加一个锁,确保其只有创建成功才可以被使用即可。