Android事件处理机制

时间:2022-02-03 17:46:40

Android事件处理机制

如果你想在Android中处理消息,比如经典的应用场景:接收到网络应答后更新UI,只需要在响应事件的线程中添加一个Handler成员就可以了,如下

public class MainActivity extends Activity {
Handler messageHandler = new MyMessageHandler();
}

表面看Handler并没有与当前线程关联,那其他线程通过handler.sendMessage(msg);发送的消息,主线程如何接收处理的呢?进去看看代码。

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;
}
public final class Looper {
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
}

public class ThreadLocal<T> {
...
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
...
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
...
}
class Thread{
...
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}

上述代码很清楚的描述了如何获取当前线程的Looper的。而设置线程的Looper是在线程启动阶段做的,如下:

在ActivityThread类中

public final class ActivityThread {
public static void main(String[] args) {
...
Looper.prepareMainLooper();

ActivityThread thread = new ActivityThread();
...
Looper.loop();
...
}
}

public final class Looper {
public static void prepareMainLooper() {
prepare(false);
...
}
}
}

同样具有事件处理功能的HandlerThread类中

public class HandlerThread extends Thread {
...
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
...
}

由此可见,想要一个线程称为事件处理线程,必须先Looper.prepare();Looper.loop();,loop()使当前线程进入了消息处理的循环中,如下:

public final class Looper {
public static void loop() {
final Looper me = myLooper();
...
for (;;) {
Message msg = queue.next(); // might block
...
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
}
}
}
public final class Message implements Parcelable {
...
Handler target;
...
}
public class Handler {
...
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
...
}

无论你调用任何形式的Handler.sendXXXMessage(Message),都会走到enqueueMessage()里,在Message里记录当前Handler。事件循环线程通过msg.target.dispatchMessage(msg);分发给这个Handler处理。也就是哪个Handler发消息,哪个Handler处理。

而prepare()就构造一个Looper保存到线程的ThreadLocalMap成员中去。如下:

public final class Looper {
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));
}
}
public class ThreadLocal<T> {
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 getMap(Thread t) {
return t.threadLocals;
}
...
}
class Thread{
...
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}

也就是说,当在一个具有事件处理的线程(Android的UI线程或一个HandlerThread)运行时,内部会构造一个Looper并进行事件循环处理。在该线程构造一个Handler时,构造函数会记录这个线程的looper和它的队列,用这个handler发送消息时就发到这个队列里。消息所在的线程会调用这个handler处理这个事件。

至此,安卓的事件处理机制就很清楚了。

我为什么读Android的代码?从Java到C++。

我是一个C++程序员,工作中大部分用C++,但我为什么会去读Android的代码?并不是我要转行,而是我要去学习它的实现原理——要知道C++程序员经常需要自己实现一些基础功能。

一个Java程序员,尤其是目前国内的Java行业环境,几乎很少的人去写一个提供功能的基础库给别人使用,比如消息机制。国内大部分Java程序员可以说是当之无愧的"搬砖的",此处并无任何贬义,反而是一种赞扬:Java程序员有现成的、建立在丰富的标准库之上的许多优秀的库和框架,Java程序员只要根据自己的需求,选一个优秀的库拿来砌在一起实现功能就好,更多的关注与业务逻辑——这就是Enterprise的本质,业务是第一位。

而一个C++程序员,想要实现一个功能,是的,也有很多现成的、在思想上可谓优秀的库可供使用,但由于C++标准库功能的匮乏,导致许多功能没有统一的解决方案。于是各个库自己来实现本该由标准库提供的功能,这些功能的算法实现各式各样、接口各不相同,当程序员把他们结合在一起时,造成了学习和使用的复杂,这种复杂使很多C++程序员给出了另一个选择——自己造一个满足自己功能的*——这些本来是可以避免的,只要C++标准库及时提供C++程序员需要的基础功能,但C++标准委员会把这件事情搞砸了。假如有一天你想实现一个类似Apache Java HttpClient功能的C++库,你该如何选择网络模块从而完美实现跨平台?直到C++17,网络功能都没有加入到C++标准库中,于是Apache选择了Asio(假设);后来你又用到了Redis,你把hiredis(libuv做网络库)加入到你的代码中,asio和libuv相视一笑,各含无奈。——你可以想象C++标准委员会耽误了多少事情。

当然,我是从一个C++开发者的角度去看待的。C++标准委员会一直秉持着宁缺毋滥的谨慎态度去评审C++提议的,但这不能成为C++发展的绊脚石。拥有这样匮乏功能的标准库的C++是不适合企业级应用的开发的,你最好选择Java或C#,或其他更适合的语言。

C++如何实现一个消息处理线程基类

业务中有很多需要进行消息处理线程的情景:比如上述情景——UI线程就是一个消息处理线程;比如游戏服务器端,一般会有个线程通过Reactor/Proactor接收事件并立即放入消息队列,另有线程进行事件处理。C++想实现一个现成的消息处理线程基类该怎么做呢?

有如下几点需要解决:

  • 一个线程安全的消息队列。事实上这就是一个典型的生产者消费者模型,不多BB了。实现也好实现——用操作系统提供的PV semaphore去阻塞生产消费就可以了。

POSIX接口是有PV信号量API的。但C++标准并不支持。下面是一个PV信号量的简单实现。有些不熟悉条件变量的人或许产生下面的疑问:
1、wait里已经对mtx加锁并阻塞了,notify那里申请mtx的行为岂不是一直等待?
条件变量的实现,会自动解锁mutex并阻塞当前线程。参见 std::condition_variable
2、为什么要在一个while循环里wait条件变量?
简而言之,就是操作系统不能保证每次线程被唤醒时,条件变量的条件都是满足的;但可以保证,只要条件满足,就一定会唤醒。
参见Spurious wakeup

class semaphore
{
public:
semaphore ( int count_ = 0 ) : count ( count_ ) {}

inline void notify()
{
std::unique_lock<std::mutex> lock ( mtx );
count++;
cv.notify_one();
}

inline void wait()
{
std::unique_lock<std::mutex> lock ( mtx );

while ( count == 0 )
{
cv.wait ( lock );
}
//The while loop can be replaced as below.
//cv.wait ( lock, [&] () { return this->count > 0; } );
count--;
}

private:
std::mutex mtx;
std::condition_variable cv;
int count;
};
  • ThreadLocal。Android的ThreadLocal的运用纯粹是为了方便在一个线程内定义一个Handler时就能自动绑定到当前线程——这样看上去更简洁——虽然初看上去有些莫名其妙,但如果想实现就需要在线程里定义一个ThreadLocal。
  • 消息循环。需要在消息循环中调用Handler处理事件。