脚本宝典收集整理的这篇文章主要介绍了《Android开发艺术探索》第10章-Android 的消息机制读书笔记,脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
Android 的消息机制是通过 Handler
的运行机制来实现将一个任务切换到 Handler
所在的线程中去执行。
但是,完成把一个任务切换到 Handler
所在的线程中去执行这个事情,单靠 Handler
类是不行的;实际上,Handler
的运行需要 MessageQueue
和 Looper
的支撑,Handler
是作为 Android 消息机制的上层接口而已。
换句话说,Android 定义了Handler
直接面向了开发者,屏蔽了 MessageQueue
和 Looper
(没有完全屏蔽 Looper
),开发者只需要和 Handler
打交道就可以运用 Android 的消息机制了。
不对。
在开发过程中,我们在子线程中执行一些耗时的操作,比如读取文件,读取数据库,访问网络等,拿到我们需要的数据,然后把这些数据显示在 UI 上。这时,直接在子线程中操作 UI 控件来显示数据,Android 是不允许的,会抛出异常给我们的;正确的做法是,在 UI 线程创建一个 Handler
对象,在子线程中使用这个 Handler
对象将要显示的数据切换到 Handler
所在的 UI 线程,再操作 UI 控件来显示数据。这就是 Handler
用来更新 UI 的场景了。
来看看实际的代码吧:
// 在主线程创建 Handler 对象
private Handler mainThreadHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 3) {
Log.d(TAG, "handleMessage: msg.what=" + msg.what + ",msg.obj=" +
msg.obj + ",threadName=" + Thread.currentThread().getName());
// 这里是主线程,可以放心更新 UI 了。
}
}
};
// 点击按钮从子线程发送消息到主线程
public void sendMessage2UIThread(View view) {
// 开启一个子线程
new Thread(new Runnable() {
@Override
public void run() {
int what = 3;
String obj = "hello, ui thread!";
Log.d(TAG, "sendMessage2UIThread: what="+ what +",obj=" +
obj + ",threadName=" + Thread.currentThread().getName());
mainThreadHandler.obtainMessage(what, obj).sendToTarget();
}
}, "work-thread").start();
}
打印日志如下:
D/MainActivity: sendMessage2UIThread: what=3,obj=hello, ui thread!,threadName=work-thread
D/MainActivity: handleMessage: msg.what=3,msg.obj=hello, ui thread!,threadName=main
但是,我们还可以把数据从主线程切换到子线程中去执行。这里使用实际的例子来进行说明:
private Handler workThreadHandler;
private void startWorkThread() {
// 开启一个子线程
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
// 在子线程中创建 Handler 对象
workThreadHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 2) {
Log.d(TAG, "handleMessage: msg.what=" + msg.what + ",msg.obj=" +
msg.obj + ",threadName=" + Thread.currentThread().getName());
}
}
};
Looper.loop();
}
}, "work-thread").start();
}
// 点击按钮从主线程发送消息到子线程
public void sendMessage2WorkThread(View view) {
int what = 2;
String obj = "hello, work thread!";
Log.d(TAG, "sendMessage2WorkThread: what="+ what +",obj=" +
obj + ",threadName=" + Thread.currentThread().getName());
workThreadHandler.sendMessage(workThreadHandler.obtainMessage(what, obj));
}
点击按钮,打印日志如下:
D/MainActivity: sendMessage2WorkThread: what=2,obj=hello, work thread!,threadName=main
D/MainActivity: handleMessage: msg.what=2,msg.obj=hello, work thread!,threadName=work-thread
可以看到,这里确实实现了把数据从主线程切换到子线程中了。
因此,我们说,Handler
并非是专门用来更新 UI 的,只是常被开发者用来更新 UI 而已。
我们知道,Android 规定访问 UI 要在主线程中进行,如果在子线程中更新 UI,程序就会抛出异常。这是因为在 ViewRootImpl
类中会对 UI 做验证,具体来说是由 checkThread
方法来完成的。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
现在我们在子线程里去给 TextView
控件设置文本:
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_check_thread_not_working);
tv = (TextView) findViewById(R.id.tv);
new Thread(() -> {
SystemClock.sleep(1000L);
tv.setText("I am text set in " + Thread.currentThread().getName());
},"work-thread").start();
}
运行程序,会报错:
2022-01-08 05:47:15.391 9225-9252/com.wzc.chapter_10 E/AndroidRuntime: FATAL EXCEPTION: work-thread
Process: com.wzc.chapter_10, PID: 9225
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6357)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:874)
at android.view.View.requestLayout(View.java:17476)
at android.view.View.requestLayout(View.java:17476)
at android.view.View.requestLayout(View.java:17476)
at android.view.View.requestLayout(View.java:17476)
at android.view.View.requestLayout(View.java:17476)
at android.widget.TextView.checkForRelayout(TextView.java:6871)
at android.widget.TextView.setText(TextView.java:4057)
at android.widget.TextView.setText(TextView.java:3915)
at android.widget.TextView.setText(TextView.java:3890)
at com.wzc.chapter_10.CheckThreadNotWorkingActivity.lambda$onCreate$0$CheckThreadNotWorkingActivity(CheckThreadNotWorkingActivity.java:19)
at com.wzc.chapter_10.-$$Lambda$CheckThreadNotWorkingActivity$Thy_KGiEr_duYPMycxt-0lYIEGo.run(lambda)
at java.lang.Thread.run(Thread.java:818)
可以看到正是在 ViewRootImpl
类的 checkThread
方法里面抛出了异常:
Only the original thread that created a view hierarchy can touch its views.
checkThread
方法里的 mThread
就是 UI 线程,现在我们在子线程里面调用了 checkThread
方法,则 Thread.currentThread()
就是子线程,这样 mThread != Thread.currentThread()
判断就为 true
,会进入 if
分支,抛出 CalledFromWrongThreadException
异常。
但是,如果我把 SystemClock.sleep(1000L);
这行代码注释掉会怎么样呢?
运行程序,效果如下:
是的,这不是幻觉,在子线程更新 UI 成功了。那么,问题又来了,为什么有休眠时在子线程更新 UI 报错,而不休眠时在子线程更新 UI 成功呢?
这是因为有休眠时,在执行更新 UI 操作时,ViewRootImpl
对象已经创建成功了,就会执行到 checkThread
方法了;没有休眠时,在执行更新 UI 操作时, ViewRootImpl
对象还未创建,就没有执行到 checkThread
方法了。
实际上,我们这里不加休眠的情况下,只是在子线程设置文本时没有走 checkThread
方法而已,等到真正把文本绘制到屏幕上,仍然是在 UI 线程进行的。
再看一下这个方法,
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
只要 mThread
与 Thread.currentThread()
相同就不会报异常,并且异常的中文含义:只有原来创建了视图体系的线程才可以操作它的 View。这根本没有说不让子线程更新 UI。这里真正想说明的意思是:哪个线程创建了视图体系,就要由那个线程来操作它的 View;换句话说,如果某个线程去操作另外一个线程创建的 View,那是不允许的。
那么,如果我们就在子线程中去完成视图的添加,这会有问题吗?
我们在子线程里面去添加一个 Window,代码如下:
public void createUIInWorkThread(View view) {
new Thread(() -> {
// 这里要由 Looper 对象,因为在 ViewRootImpl 里面会创建 ViewRootHandler 对象。
Looper.prepare();
TextView tv = new TextView(MainActivity.this);
tv.setBackgroundColor(Color.GRAY);
tv.setTextColor(Color.RED);
tv.setTextSize(40);
tv.setText("i am text created in " + Thread.currentThread().getName());
WindowManager windowManager = MainActivity.this.getWindowManager();
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT,
0, 0, PixelFormat.TRANSPARENT);
layoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;
layoutParams.gravity = Gravity.CENTER;
layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
windowManager.addView(tv, layoutParams);
Looper.loop();
}, "work-thread").start();
}
点击按钮,查看效果:
可以看到,在子线程里里面操作 UI 是可以的。这里我们总结一下:
在线程 A 里面一般不能操作线程 B 的 UI;但是如果线程 B 的 ViewRootImpl
还没有创建,这时就不会走 checkThread
方法,也不会抛出异常,最终仍是由线程 B 完成了 UI 的操作。
在一个线程里操作由这个线程自己创建的视图体系是可以的,也可以说,一个线程只可以操作它自己的 UI。
Android 的 UI 控件不是线程安全的,如果在多线程中并发访问可能会导致 UI 控件处于不可预期的状态;而如果对 UI 控件的访问加上锁机制,会让 UI 访问的逻辑变得复杂,也会降低 UI 访问的效率。
所以,Android 采用单线程模型才处理 UI 操作。
从构造方法来看
分为两大类:可以传递 Looper
对象的和不可以传递 Looper
对象的。
我们重点看不传递 Looper
对象的 Handler(Callback callback, boolean async)
方法,因为这个方法非常具有代表性。
public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
这个方法的主要作用:
Looper.myLooper()
方法来获取并通过 mLooper
持有 Looper
对象。Looper.myLooper()
方法会从线程本地变量 ThreadLocal
里面取出与当前线程对应的 Looper
对象。mLooper
对象仍为 null
,就会抛出异常:“Can't create handler inside thread that has not called Looper.prepare()”
,这是告诉当前线程没有调用 Looper.prepare()
,所以不能创建 Handler
。Looper
对象获取 MessageQueue
对象并赋值给 mQueue
成员变量。在构造 Handler
对象时,就一定要持有 Looper
对象和 MessageQueue
对象,也就是说,Handler
类组合了 Looper
对象和 MessageQueue
对象。
从发送消息方法来看
发送消息的方法分为两大类:postXXX 方法和 sendXXX 方法。
postXXX 方法用于发送一个 Runnable
对象,它会包装成一个 Message
对象,再发送到消息队列中;
sendXXX 方法用于发送一个 Message
对象到消息队列中。
从调用图可以看到,不管是 postXXX 方法和 sendXXX 方法,最终调用的都是 enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)
方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这个方法的主要作用:
Handler
对象赋值给 Message
对象的 target
字段;MessageQueue
对象的 enqueueMessage
方法把消息加入到消息队列。从处理消息方法来看
当消息从消息队列中取出时,会调用 Handler
对象的 dispatchMessage
方法来分发消息
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
public interface Callback {
public boolean handleMessage(Message msg);
}
private static void handleCallback(Message message) {
message.callback.run();
}
public void handleMessage(Message msg) {
}
该方法的主要作用
对于 Message
来说,就是回调接口的作用:Message
对象持有Handler
对象,通过调用这个 Handler
对象的 dispatchMessage
方法,把 Message
对象回调给了 Handler
。
对于 Handler
来说,就是分发消息的作用:
Message
对象的 callback
字段不为空,那么这个消息内部持有了一个 Runnable
对象,就调用 handleCallback
方法来运行那个 Runnable
对象封装的代码;Message
对象的 callback
字段为空,而 mCallback
对象不为 null
,就使用 Callback
类的 handleMessage
方法来处理消息;这个 mCallback
是在 Handler
的构造方法里面完成赋值的,使用这个回调的好处是不必要为了重写 Handler
类的 handleMessage
方法而去子类化 Handler
;Message
对象的 callback
字段为空,且mCallback
对象为 null
,就使用 Handler
类的 handleMessage
方法来处理消息了。从获取消息来看
Handler
封装了一系列的 obtainMessage
工具方法,方便我们拿到 Message
对象。
从移除消息来看
Handler
封装了 removeXXX 方法,内部委托给 MessageQueue
对象去做真正的工作。
public final void removeMessages(int what) {
mQueue.removeMessages(this, what, null);
}
总结一下:使用 Handler
可以组合 MessageQueue
对象和 Looper
对象,可以发送消息,可以处理消息,可以获取消息对象,可以移除消息,所以说Handler
是 Android 消息机制的上层接口。
图解:
Handler
对象 handler
,默认使用的是主线程的 Looper
对象以及对应的 MessageQueue
对象;Handler
对象 handler
的发送消息方法发送消息,最终通过 MessageQueue
对象的 enqueueMessage
方法把消息加入到消息队列中;Looper.loop()
方法运行在创建 Handler
里的线程,在这里就是运行在主线程, Loop.loop()
方法不断从消息队列中获取符合条件的 Message
对象;Message
对象后,通过 Message
对象持有的 target
字段(实际就是发送该消息的那个 Handler
对象)的 dispatchMessage
方法把消息回调给发送消息的那个 Handler
,这样消息就在主线程接收到了。场景一:当某些数据是以线程为作用域并且不同线程具有不同的线程副本的时候,考虑使用 ThreadLocal
。
对于 Handler
来说,它需要获取当前线程的 Looper
(Looper
的作用域就是线程并且不同线程具有不同的 Looper
),这时候使用 ThreadLocal
就可以轻松实现 Looper
在线程中的存取。
对于 SimpleDateFormat
来说,它不是线程安全的,也就是说在多线程并发操作时,会抛出异常。看演示代码如下:
public class SimpleDateFormatDemo1 {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
5, 50, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100));
List<String> data = Arrays.asList(
"2021-03-01 00:00:00",
"2020-01-01 12:11:40",
"2019-07-02 23:11:23",
"2010-12-03 08:22:33",
"2013-11-29 10:10:10",
"2017-09-01 14:14:14",
"2021-04-01 15:15:15"
);
for (String date : data) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(sdf.parse(date));
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
threadPool.shutdown();
}
}
运行这段程序,会出现这样的异常:
java.lang.NumberFormatException: For input string: ".103E2103E2"
at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043)
at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
at java.lang.Double.parseDouble(Double.java:538)
at java.text.DigitList.getDouble(DigitList.java:169)
at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
at java.text.DateFormat.parse(DateFormat.java:364)
at com.java.advanced.features.concurrent.threadlocal.SimpleDateFormatDemo1$1.run(SimpleDateFormatDemo1.java:45)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
为了解决 SimpleDateFormat
线程不安全的问题,我们可以使用一个 synchronized
修饰的方法封装其 parse
方法,但是这样多线程下会竞争锁,效率不高。使用 ThreadLocal
来为每个线程创建一个专属的 SimpleDateFormat
对象副本,当一个线程下需要获取 SimpleDateFormat
对象进行操作时,它获取的是它自己的那个副本,对其他线程的 SimpleDateFormat
对象副本没有影响,这样就不会发生线程不安全的问题了。
public class SimpleDateFormatDemo2 {
private static ThreadLocal<SimpleDateFormat> threadLocal = ThreadLocal.withInitial(new Supplier<SimpleDateFormat>() {
@Override
public SimpleDateFormat get() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
});
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
5, 50, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(100));
List<String> data = Arrays.asList(
"2021-03-01 00:00:00",
"2020-01-01 12:11:40",
"2019-07-02 23:11:23",
"2010-12-03 08:22:33",
"2013-11-29 10:10:10",
"2017-09-01 14:14:14",
"2021-04-01 15:15:15"
);
for (String date : data) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
System.out.println(threadLocal.get().parse(date));
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
threadPool.shutdown();
}
}
多次运行程序,都可以正常解析了。
实际上,JDK1.8 提供了线程安全的 DateTimeFormatter
来替代线程不安全的 SimpleDateFormat
。这里就不详细说明了。
场景二:使用 ThreadLocal
来进行复杂逻辑下的对象传递。
比如一个线程中的任务,它的函数调用栈比较深,或者说调用链有不能修改的第三方库,这时我们想要传递一个监听器参数进去,该怎么办呢?
如果都是自己的代码,可以修改给调用链上的每一个函数增加监听器参数,但是这样改动的地方很多,容易出错,也很麻烦;
如果调用链有不可以改动的第三方库,可以将监听器作为静态变量供线程访问,但是在多线程下每个线程都要有自己的监听器对象,我们就需要用一个集合(以线程名为键,以监听器为值)来管理这些监听器了。这样多线程下在获取指定的监听器的时候,还是会存在就集合的竞争。所以不好。
使用 ThreadLocal
就可以解决这个问题。代码如下:
private static ThreadLocal<Runnable> runnableThreadLocal = new ThreadLocal<>();
public void threadLocalargs(View view) {
new Thread("thread1") {
@Override
public void run() {
task();
}
}.start();
new Thread("thread2") {
@Override
public void run() {
task();
}
}.start();
}
private void task() {
Runnable runnable = () -> Log.d(TAG, "run: " + Thread.currentThread().getName());
runnableThreadLocal.set(runnable);
method1();
}
private void method1() {
method2();
}
private void method2() {
method3();
}
private void method3() {
runnableThreadLocal.get().run();
}
打印日志如下:
D/MainActivity: run: thread1
D/MainActivity: run: thread2
先来看下 ThreadLocal
的基本使用:
private static ThreadLocal<Boolean> sBooleanThreadLocal = new ThreadLocal<>();
private static ThreadLocal<String> sStringThreadLocal = new ThreadLocal<>();
public void threadLocal_basic(View view) {
sBooleanThreadLocal.set(true);
log();
new Thread("Thread#1") {
@Override
public void run() {
super.run();
sBooleanThreadLocal.set(false);
sStringThreadLocal.set(Thread.currentThread().getName());
log();
}
}.start();
new Thread("Thread#2"){
@Override
public void run() {
super.run();
sStringThreadLocal.set(Thread.currentThread().getName());
log();
}
}.start();
}
private void log() {
Log.d(TAG, "["+ Thread.currentThread().getName() +"]"+ "sBooleanThreadLocal.get()=" + sBooleanThreadLocal.get());
Log.d(TAG, "["+ Thread.currentThread().getName() +"]"+ "sStringThreadLocal.get()=" + sStringThreadLocal.get());
}
打印日志如下:
D/MainActivity: [main]sBooleanThreadLocal.get()=true
D/MainActivity: [main]sStringThreadLocal.get()=null
D/MainActivity: [Thread#1]sBooleanThreadLocal.get()=false
D/MainActivity: [Thread#1]sStringThreadLocal.get()=Thread#1
D/MainActivity: [Thread#2]sBooleanThreadLocal.get()=null
D/MainActivity: [Thread#2]sStringThreadLocal.get()=Thread#2
可以看到,虽然我们在不同的线程中访问的都是同样的 ThreadLocal
对象,但是它们从 ThreadLocal
对象中获取的值,正好就是设置的值或者是默认值。
这里我们看的是 JDK1.8 的源码。
我们先来看一下相关的类关系吧:
每个Thread
对象都持有一个ThreadLocal.ThreadLocalMap
对象threadLocals
;
ThreadLocalMap
是 ThreadLocal
的静态内部类,ThreadLocalMap里面维护了一个
Entry数组
table`;
Entry
是 ThreadLocal
的静态内部类,封装了一个键值对,key 是 ThreadLoal
对象,value 是 ThreadLocal
的泛型对应的值。
绘制类关系图如下:
核心代码如下:
public class ThreadLocal<T> {
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
}
}
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
接着我们去看 ThreadLocal
的 get()
和 set()
方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
该方法的主要作用:
ThreadLocalMap
变量;ThreadLocal
对象为 key,以泛型对应的值为 value,存入到 ThreadLocalMap
中。public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
该方法的主要作用:
ThreadLocalMap
变量;ThreadLocalMap
变量中,以 ThreadLocal
对象为 key,查找对应的 Entry
对象;Entry
对象的 value 部分,并返回。从 ThreadLocal
的 set
和 get
方法可以看出,ThreadLocal
对象操作的 ThreadLocalMap
对象都是当前线程的 ThreadLocalMap
对象;而每个 ThreadLocalMap
内部会持有一个 Entry
数组,所以 ThreadLocal
对象操作的 Entry
数组都是当前线程的 Entry
数组,也就是说,读/写操作都是针对当前线程的 Entry
数组,不涉及其他线程,不会影响其他线程,所以 ThreadLocal
可以在多个线程中互不干扰地存储和修改数据。
为了更好地理解 ThreadLocal
的线程隔离,把演示 ThreadLocal
的小例子绘制成内存图,如下:
消息队列就是 MessageQueue
,它使用 boolean enqueueMessage(Message msg, long when)
方法完成消息的插入,使用 Message next()
完成消息的读取(在获取到时会先把该消息从消息队列中移除)。
MessageQueue
是通过一个基于时间排序的单链表数据结构来维护消息列表。
下面看一下它的两个主要方法吧。
public final class MessageQueue {
// 链表的头节点
Message mMessages;
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 当队列中没有任何消息,或者新的 Message 的触发时间是最早的,则新的 Message 应该作为头节点
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 新的 Message 应该插入到队列中
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 遍历链表,找到应该插入的位置
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
// 插入新的 Message
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
}
public final class MessageQueue {
// 链表的头节点
Message mMessages;
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 当有同步屏障消息时,会进入此分支
if (msg != null && msg.target == null) {
// 通过 do while 循环来获取下一个异步消息。
// 如果没有获取到异步消息,不会结束 do while 循环。
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 下一条消息的触发时间还没有到,则设置一个超时时间以在触发时间到达时唤醒队列
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获取一条消息
mBlocked = false;
// 从链表中移除获取到的消息
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
// 返回获取的消息
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
...// 省略了 IdleHandler 部分
}
...// 省略了 IdleHandler 部分
}
}
}
Looper
在 Android 的消息机制中扮演着消息循环的角色,它通过 loop()
方法不停地从 MessageQueue
中查看是否有获取到消息:如果获取到消息,就立即交给 Handler
处理,如果获取不到消息,就一直阻塞,如果获取到 null
,就退出消息循环了。
prepare()
方法创建的 Looper
消息循环可以退出,prepareMainLooper()
方法创建的消息循环不可以退出。
prepare()
方法内部调用 prepare(boolean quitAllowed)
传参的参数是 true
,而和prepareMainLooper()
内部调用 prepare(boolean quitAllowed)
传参的参数是 false
,
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
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));
}
该方法的作用:
检查从线程本地变量 sThreadLocal
中获取的 Looper
对象是否为 null
,不为 null
,则抛出异常:每个线程只能创建一个 Looper
对象;
调用 Looper
的构造方法,创建 Looper
对象:构造方法里面创建了 MessageQueue
对象,并持有了创建 Looper
的当前线程
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
quitAllowed
传入到消息队列中,如果这个值为 true
,则表示允许退出消息循环;反之,不允许。主线程的消息循环不可以退出。
将 Looper
对象保存在线程本地变量 sThreadLocal
中。
prepare()
方法是用于子线程创建 Looper
对象的,prepareMainLooper()
是用于主线程创建 Looper
对象的。
quit()
会设置一个退出标记,并直接退出消息循环;
quitSafely()
只是设置了一个退出标记,Looper
仍会处理完消息队列中的非延时消息后才会退出。
public final class Looper {
final MessageQueue mQueue;
public void quit() {
mQueue.quit(false);
}
public void quitSafely() {
mQueue.quit(true);
}
}
public final class MessageQueue {
void quit(boolean safe) {
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}
synchronized (this) {
if (mQuitting) {
return;
}
// 设置退出标记为 true
mQuitting = true;
if (safe) {
// 移除所有需要延时处理的消息,保留需要触发的消息让消息队列继续处理
removeAllFutureMessagesLocked();
} else {
// 移除所有的消息
removeAllMessagesLocked();
}
// We can assume mPtr != 0 because mQuitting was previously false.
nativeWake(mPtr);
}
}
}
不管是调用了 quit()
还是 quitSafely()
方法,如果再向消息队列添加消息,都会返回 false
。
public final class MessageQueue {
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
if (mQuitting) { // 退出标记为 true,进入分支,会返回 false。
...
return false;
}
...
}
return true;
}
}
需要说明的是,在子线程中手动创建 Looper
,并调用Looper
的 loop()
方法,如果不主动调用 quit
方法来退出消息循环,那么这个子线程就一直处于等待的状态,具体来说就是消息循环等待下一个消息。所以,在不需要消息循环时,一定要主动终止 Looper
。
这种说法不对。我们用反证法来说明,假设Looper.loop() 方法在消息队列中没有消息时就会退出的说法正确,那么现在我们在一个子线程里,这样写代码:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
Looper.loop();
}
}
然后现在消息队列肯定没有消息,这样 Looper.loop()
方法就会退出了。
但是,我们在主线程使用 mHandler
发送消息,仍然可以发送成功,并且可以在子线程里面接收到。与假设矛盾,所以假设不成立,这种说法是错误的。
我们去看看 loop()
方法在那里 return
了:
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// 如果获取到的 Message 对象为 null,就退出消息循环。
return;
}
msg.target.dispatchMessage(msg);
final long newIdent = Binder.clearCallingIdentity();
msg.recycleUnchecked();
}
}
那么,什么时候 MessageQueue
的 next
方法会返回 null
呢?
当 Looper
的 quit
方法被调用时,消息队列被标记为退出状态,它的 next
方法就会返回 null
;
当 Looper
的 quitSafely
方法被调用时,消息队列被标记为退出状态,等处理完非延时消息后,它的 next
方法才会返回 null
。
实际上,当消息队列中没有消息时,调用 next
方法时不会返回的,它会一直阻塞,这样 loop
方法也阻塞在那里。
构建主线程消息循环,需要调用 Looper.prepareMainLooper()
方法,来创建 MessageQueue
对象和 Looper
对象。我们查看调用这个方法的地方有哪些:
可以看到在三个地方调用了这个方法:ActivityThread.java
、Bridge.java
和 SystemServer.java
,
其中 ActivityThread
的 main
函数里开启了 App 进程的主线程消息循环,SystemServer
的 run
方法开启了 system_server 进程的主线程消息循环。
这里以 App 进程的主线程消息循环来做说明:
ActivityThread
类是 App 进程的初始类,它的 main
函数是 App 进程的入口。Looper.prepareMainLooper()
方法就是在它的 main
函数中调用的。
我们以 scheduleLaunchActivity
为例,来说明主线程的消息循环,关键代码如下:
public final class ActivityThread {
final ApplicationThread mAppThread = new ApplicationThread();
final H mH = new H();
private class ApplicationThread extends ApplicationThreadNative {
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, CompatibilityInfo compatInfo,
IVoiceInteractor voiceInteractor, int procState, Bundle state,
PersistableBundle persistentState, List<ResultInfo> pendingResults,
List<Intent> pendingNewIntents, boolean notResumed, boolean isForward,
ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord();
... // 省略一系列赋值操作
sendMessage(H.LAUNCH_ACTIVITY, r);
}
...// 省略了其他的跨进程调用方法,只保留 scheduleLaunchActivity 方法
}
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
... // 省略了其他的常量定义,只保留 LAUNCH_ACTIVITY
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
... // 省略了其他的 case 分支,只保留 LAUNCH_ACTIVITY 分支
}
}
}
private void sendMessage(int what, Object obj) {
sendMessage(what, obj, 0, 0, false);
}
private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
Message msg = Message.obtain();
msg.what = what;
msg.obj = obj;
msg.arg1 = arg1;
msg.arg2 = arg2;
if (async) {
msg.setAsynchronous(true);
}
mH.sendMessage(msg);
}
private void attach(boolean system) {
if (!system) {
final IActivityManager mgr = ActivityManagerNative.getDefault();
mgr.attachApplication(mAppThread);
} else {
...
}
}
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
下面对关键代码进行说明:
main
方法是 App 进程的入口,在这里面:
通过 Looper.prepareMainLooper()
创建了主线程的 Looper
以及 MessageQueue
;
实例化了 ActivityThread
对象,这样也就在主线程初始化了 ActivityThread
对象的成员变量 mAppThread
和 mH
。
final ApplicationThread mAppThread = new ApplicationThread();
final H mH = new H();
ApplicationThread
和 H
都是 ActivityThread
的内部类,其中 ApplicationThread
是作为 IApplicationThread
binder 接口的服务端对象存在的,IApplicationThread
是 system_server 进程向 App 进程发起通信的桥梁;H
是一个 Handler
类的子类。
调用 ActivityThread
对象的 attach(false)
方法,内部把 mAppThread
设置给了 AMS,相当于 App 进程设置了一个回调接口对象给 AMS,这样 AMS 需要向 App 进程发起通信时就可以通过 mAppThread
了。
调用 Looper.loop()
方法,开启主线程的消息循环。
当 AMS 需要向客户端进程发起 scheduleLaunchActivity
请求时,就会通过它持有的 IApplicationThread
客户端对象来发起这个请求,经过 binder 驱动,会调用到 ApplicationThread
的 scheduleLaunchActivity
方法中,需要特别说明的是,因为是跨进程通信,此时ApplicationThread
的 scheduleLaunchActivity
方法是运行在客户端进程的 binder 线程池中。
接着调用 ActivityThread
的 sendMessage
方法,将需要发送的数据封装成一个 Message
对象,接着调用了 mH
的 sendMessage
方法。
在 H
类的 hanldeMessage
方法中接收到消息,这样就完成了将数据从binder线程池切换到了主线程。
checkThread
方法。以上是脚本宝典为你收集整理的《Android开发艺术探索》第10章-Android 的消息机制读书笔记全部内容,希望文章能够帮你解决《Android开发艺术探索》第10章-Android 的消息机制读书笔记所遇到的问题。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。