Android Handler 消息传递机制

出于性能优化考虑,Android 的 UI 操作并不是线程安全的,这意味着如果多个线程并发操作 UI 组件,可能导致线程安全问题。为了解决这个问题,Android 制定了一条简单的规则:只允许 UI 线程修改 Activity 里的 UI 组件

当一个程序启动时,Android 会同时启动一个主线程(Main Thread),主线程主要负责处理与 UI 相关的事件,如用户的按键事件、触屏事件及屏幕绘制事件,并把相关的事件分发到对应的组件进行处理。所以主线程也称为 UI 线程。

Android 的消息传递机制是另一种形式的“事件处理”,这种机制主要是为了解决 Android 应用的多线程问题 —— Android 平台只允许 UI 线程修改 Activity 里的 UI 组件,这样就会导致新启动的线程无法动态改变界面组件的属性值。此时,就需要借助于 Handler 的消息传递机制来实现新启动的线程改变界面组件的属性值。

Handler 类简介###

Handler 类的主要作用有两个:
1.在子线程中发送消息;
2.在主线程中获取、处理消息。

例如,下面的使用示例,自动播放图片:
主布局文件的内容:




主程序文件的代码:

package com.toby.personal.testlistview;

import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.widget.ImageView;

import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {

    final private static String TAG = "Toby_Test";

    final private static int MsgWhat = 20170326;
    final private static int Duration = 1200;
    private int currentImageId = 0;

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

        final int[] imageIds = new int[]{
                R.drawable.dog_001, R.drawable.dog_002, R.drawable.dog_003, R.drawable.dog_004,
                R.drawable.dog_005,
        };


        final ImageView imageView = (ImageView) findViewById(R.id.imageView);

        final Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {

                if (msg.what == MsgWhat) {
                    imageView.setImageResource(imageIds[currentImageId++ % imageIds.length]);
                }

                super.handleMessage(msg);
            }
        };

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                handler.sendEmptyMessage(MsgWhat);
            }
        }, 0, Duration);

    }

}

程序运行之后,将开始自动播放图片,具体使用效果,各位可自行尝试运行。

Handler、Loop、MessageQueue 的工作原理###

与 Handler 一起工作的几个组件:
Message —— Handler 接收和处理的消息对象;
Looper —— 每个线程只能拥有一个 Looper。它的 loop 方法负责读取 MessageQueue 中的消息,读到消息之后就把消息交给发送该消息的 Handler 进行处理。
MessageQueue —— 消息队列,它采用先进先出的方式来管理 Message,程序创建 Looper 对象时会在它的构造器中创建 Looper 对象。Looper 提供的构造器源代码如下:

private Looper() {
    mQueue = new MessageQueue();
    mRun = true;
    mThread = Thread.currentThread();
}

Handler 的作用是发送和处理消息,程序使用 Handler 发送消息,消息需要使用 MessageQueue 对象进行保存,而 MessageQueue 是由 Looper 进行管理的,因此必须在当前线程中有一个 Looper 对象。可分为如下两种情况:
主线程中 —— 系统已经初始化了 Looper 对象,因此程序直接创建 Handler 即可,然后就可以通过 Handler 来发送和处理消息;
子线程中 —— 必须自己创建一个 Looper 对象,并启动它:创建 Looper 对象并调用它的 prepare() 方法即可。

prepare() 方法保证每个线程最多只有一个 Looper 对象。prepare() 方法的源代码如下:

    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

然后调用 Looper 的静态 loop() 方法来启动它。loop() 方法使用一个死循环不断取出 MessageQueue 中的消息,并将取出的消息分给该消息对应的 Handler 进行处理。下面是 Looper 类的 loop() 方法的源代码:

public static void loop() {
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

        // Make sure the identity of this thread is that of the local process,
        // and keep track of what that identity token actually is.
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();

        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

            // This must be in a local variable, in case a UI event sets the logger
            final Printer logging = me.mLogging;
            if (logging != null) {
                logging.println(">>>>> Dispatching to " + msg.target + " " +
                        msg.callback + ": " + msg.what);
            }

            final long traceTag = me.mTraceTag;
            if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
            }
            try {
                msg.target.dispatchMessage(msg);
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }

            if (logging != null) {
                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
            }

            // Make sure that during the course of dispatching the
            // identity of the thread wasn't corrupted.
            final long newIdent = Binder.clearCallingIdentity();
            if (ident != newIdent) {
                Log.wtf(TAG, "Thread identity changed from 0x"
                        + Long.toHexString(ident) + " to 0x"
                        + Long.toHexString(newIdent) + " while dispatching to "
                        + msg.target.getClass().getName() + " "
                        + msg.callback + " what=" + msg.what);
            }

            msg.recycleUnchecked();
        }
    }

总结一下,Looper、MessageQueue、Handler 各自的作用如下。
Looper:每个线程只有一个 Looper,它负责管理 MessageQueue,会不断地从 MessageQueue 中取出消息,并将消息分给对应的 Handler 处理。
MessageQueue:由 Looper 负责管理。它采用先进先出的方式管理 Message。
Handler:它能把消息发送给 Looper 管理的 MessageQueue,并负责处理 Looper 分给它的消息。

在线程中使用 Handler 的步骤如下。
1.调用 Looper 的 prepare() 方法为当前线程创建 Looper 对象,创建 Looper 对象时,它的构造器会创建与之配套的 MessageQueue。
2.有了 Looper 之后,创建 Handler 子类的实例,重写 HandleMessage() 方法,该方法负责处理来自于其他线程的消息。
3.调用 Looper 的 loop() 方法启动 Looper。

Looper 与 Handler 的用法实例,使用新线程计算质数
布局文件:




    

    

主程序文件的代码如下:

package com.toby.personal.testlistview;

import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    final private static String TAG = "Toby_Test";

    final private static String KEY_NUM = "KEY_NUM_TEST";
    final private static int MsgWhat = 20170326;

    private EditText editText = null;
    private CalculationThread calculationThread = null;

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

        editText = (EditText) findViewById(R.id.editText);
        calculationThread = new CalculationThread();
        calculationThread.start();
    }

    public void calculation(View view) {
        Message message = new Message();
        message.what = MsgWhat;
        Bundle bundle = new Bundle();
        String numb = editText.getText().toString();
        bundle.putInt(KEY_NUM, Integer.parseInt(numb.isEmpty() ? "100" : numb));
        message.setData(bundle);
        calculationThread.handler.sendMessage(message);
    }

    private class CalculationThread extends Thread {
        Handler handler;

        @Override
        public void run() {

            Looper.prepare();

            handler = new Handler() {
                @Override
                public void handleMessage(Message msg) {

                    if (msg.what == MsgWhat) {
                        final int upper = msg.getData().getInt(KEY_NUM);
                        List numbs = new ArrayList<>();
                        outer:
                        for (int i = 2; i <= upper; ++i) {

                            for (int j = 2; j <= Math.sqrt(i); ++j) {
                                if (i != 2 && i % j == 0) {
                                    continue outer;
                                }
                            }
                            numbs.add(i);
                        }

                        Toast.makeText(MainActivity.this, numbs.toString(),
                                Toast.LENGTH_LONG).show();

                    }

                    super.handleMessage(msg);
                }
            };

            Looper.loop();

            super.run();
        }
    }
}

该示例的运行效果如下图所示:


Android Handler 消息传递机制_第1张图片
显示效果

参考文献:《疯狂Android讲义(第2版)》

你可能感兴趣的:(Android Handler 消息传递机制)