出于性能优化考虑,Android 的 UI 操作并不是线程安全的,这意味着如果多个线程并发操作 UI 组件,可能导致线程安全问题。为了解决这个问题,Android 制定了一条简单的规则:只允许 UI 线程修改 Activity 里的 UI 组件。
当一个程序启动时,Android 会同时启动一个主线程(Main Thread),主线程主要负责处理与 UI 相关的事件,如用户的按键事件、触屏事件及屏幕绘制事件,并把相关的事件分发到对应的组件进行处理。所以主线程也称为 UI 线程。
Android 的消息传递机制是另一种形式的“事件处理”,这种机制主要是为了解决 Android 应用的多线程问题 —— Android 平台只允许 UI 线程修改 Activity 里的 UI 组件,这样就会导致新启动的线程无法动态改变界面组件的属性值。此时,就需要借助于 Handler 的消息传递机制来实现新启动的线程改变界面组件的属性值。
Handler 类简介###
Handler 类的主要作用有两个:
1.在子线程中发送消息;
2.在主线程中获取、处理消息。
例如,下面的使用示例,自动播放图片:
主布局文件的内容:
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorGray"
android:id="@+id/imageView"
>
</ImageView>
主程序文件的代码:
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 的用法实例,使用新线程计算质数
布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorGray"
android:orientation="vertical"
>
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/show"
android:onClick="calculation"
/>
</LinearLayout>
主程序文件的代码如下:
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<Integer> 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讲义(第2版)》