首页
社区
课程
招聘
[原创]安卓中Message,Handler,Looper的深入分析
2015-4-23 15:28 6335

[原创]安卓中Message,Handler,Looper的深入分析

2015-4-23 15:28
6335
我们的常用的系统中,程序的工作通常是有事件驱动和消息驱动两种方式,在Android系统中,Java应用程序是靠消息驱动来工作的。
    不知道如何更加优美的引出话题,这里以一个demo开始。
ublic void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Button btn = (Button) findViewById(R.id.button);
    btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            HandlerThread myThread = new HandlerThread("Test") {

                @Override
                protected void onLooperPrepared() {
                    super.onLooperPrepared();
                }
            };
            myThread.start();

            Log.d("HandlerThreadtest", "start thread");
            Handler myHandler = new Handler(myThread.getLooper()) {

                @Override
                public void handleMessage(Message msg) {
                    super.handleMessage(msg);
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.d("HandlerThreadtest", "handle message");
                }
            };

            Message msg = myHandler.obtainMessage(1);
            myHandler.sendMessage(msg);
        }
    });
}


Demo很简单,看似是为了利用消息循环而用消息循环,没有多大意义,从这里开始阐述安卓的消息循环以及消息在系统中是怎样生成以及被处理的。Demo中同时用到了三个类,分别是HandlerThread,Handler,Message,以下将进行一一说明。
HandlerThread(\frameworks\base\core\java\android\os\HandlerThread.java)

总得来说HandlerThread是一个带有looper的线程,那HandlerThread和Thread有什么区别呢?首先抛开looper不讲,后面会补充,先来研究一下HandlerThread和Thread的区别。
public class HandlerThread extends Thread {
    private int mPriority;
    private int mTid = -1;
    private Looper mLooper;

    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }

首先,Thread是HandlerThread的基类,那么可以理解HandlerThread拥有Thread的所有行为特征。其次,其中mLooper是和Thread的最大区别。
在Demo中myThread执行Start后会执行run函数:
public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}

在Run中调用了Looper.prepare()和Looper.loop()这样就进入了该线程的消息循环,什么事Looper以及消息循环是怎样驱动的后后面阐述。

Handler
public Handler(Looper looper) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = null;
}

将HandlerThread的Looper传给handler,Handler将其保存在mLooper,并且Handler还保存了上面传下来的MessageQueue。然后我们利用Handler来Sendmessage。最终会调用sendMessageAtTime
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
    boolean sent = false;
    MessageQueue queue = mQueue;
    if (queue != null) {
        msg.target = this;
        sent = queue.enqueueMessage(msg, uptimeMillis);
    }
    else {
        RuntimeException e = new RuntimeException(
            this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
    }
    return sent;
}

将消息的目标设置为自己,同时将消息插入到消息队列中(此处的消息队列就是HandlerThread中的)。做完这些后,我们发送的消息就会成功进入HandlerThread的处理流程。在插入消息队列时,会根据触发消息的时间将其插入到队列的合适位置。线程在进行loop时:
public static final void loop() {
    Looper me = myLooper();
    MessageQueue queue = me.mQueue;
    while (true) {
        Message msg = queue.next(); // might block
        //if (!me.mRun) {
        //    break;
        //}
        if (msg != null) {
            if (msg.target == null) {
                // No target is a magic identifier for the quit message.
                return;
            }
            if (me.mLogging!= null) me.mLogging.println(
                    ">>>>> Dispatching to " + msg.target + " "
                    + msg.callback + ": " + msg.what
                    );
            msg.target.dispatchMessage(msg);
            if (me.mLogging!= null) me.mLogging.println(
                    "<<<<< Finished to    " + msg.target + " "
                    + msg.callback);
            msg.recycle();
        }
    }
}

会依次从mQueue中取出消息,然后调用dispatchMessage分发,注意看调用方是msg.target,如果处理的消息是我们发送的,那么将会调用我们类中的dispatchMessage函数。在dispatchMessage中会回调到我们重写的HandleMessage。这样就完成了消息的发送,循环和处理。其中可以看到Looper和Handler紧密配合。如果用生产和消费者模型来比喻的话,Looper就是那个生产者,handler是消费者。Handler将消息丢给消息队列,looper永不停息的取出消息然后分发,最后由handler来继续处理。
以上只是讲了大概的流程,当然很多细节不能一一俱到了。继续吧,接下来深入一点看看消息循环到底是怎样的。

不得不说的Looper(\frameworks\base\core\java\android\os\Looper.java)
rivate Looper() {
    mQueue = new MessageQueue();
    mRun = true;
    mThread = Thread.currentThread();
}

创建了一个MessageQueue。
MessageQueue() {
    nativeInit();
}

在构造函数中直接调用了Native的初始化,继续跟进去看看在native中初始化做了些什么
static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (! nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return;
    }

    android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);
}

做了两件事:1.创建了NativeMessageQueue,2.调用setNativeMessageQueue将刚才创建的NativeMessageQueue保存在Java层MessageQueue中成员变量mPtr中。这是为了后续我们调用Java层的消息队列对象的其它成员函数进入到JNI层时,能够方便地找回它在JNI层所对应的消息队列对象。紧接着来看看NativeMessageQueue又是啥:
NativeMessageQueue::NativeMessageQueue() {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

创建了一个looper对象,不过这可不是Java层的Looper,记住现在是在Native中。
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks),
        mResponseIndex(0) {
    int wakeFds[2];
    int result = pipe(wakeFds);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not create wake pipe.  errno=%d", errno);

    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];

    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking.  errno=%d",
            errno);

    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking.  errno=%d",
            errno);

#ifdef LOOPER_USES_EPOLL
    // Allocate the epoll instance and register the wake pipe.
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance.  errno=%d", errno);

    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
    LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake read pipe to epoll instance.  errno=%d",
            errno);
#else
    // Add the wake pipe to the head of the request list with a null callback.
    struct pollfd requestedFd;
    requestedFd.fd = mWakeReadPipeFd;
    requestedFd.events = POLLIN;
    mRequestedFds.push(requestedFd);

    Request request;
    request.fd = mWakeReadPipeFd;
    request.callback = NULL;
    request.ident = 0;
    request.data = NULL;
    mRequests.push(request);

    mPolling = false;
    mWaiters = 0;
#endif

#ifdef LOOPER_STATISTICS
    mPendingWakeTime = -1;
    mPendingWakeCount = 0;
    mSampledWakeCycles = 0;
    mSampledWakeCountSum = 0;
    mSampledWakeLatencySum = 0;

    mSampledPolls = 0;
    mSampledZeroPollCount = 0;
    mSampledZeroPollLatencySum = 0;
    mSampledTimeoutPollCount = 0;
    mSampledTimeoutPollLatencySum = 0;
#endif
}

这坨代码有点多,做了这样几件事:
1.创建了一个管道,管道两端分别对应读和写,这样设计当然是考虑进程间通信的问题。
2.调用epoll_create创建epoll文件描述符。什么是epoll自行百度。
3.继续调用epoll_ctl设置管道读端。这里就是告诉mEpollFd,它要监控mWakeReadPipeFd文件描述符的EPOLLIN事件,即当管道中有内容可读时,就唤醒当前正在等待管道中的内容的线程。
以上所有的操作都是在初始化中,looper的初始化看起来还是蛮复杂的,可以分成以下几部分:
A. 在Java层,创建了一个Looper对象,这个Looper对象是用来进入消息循环的,它的内部有一个消息队列MessageQueue对象mQueue;
B. 在JNI层,创建了一个NativeMessageQueue对象,这个NativeMessageQueue对象保存在Java层的消息队列对象mQueue的成员变量mPtr中;
C. 在C++层,创建了一个Looper对象,保存在JNI层的NativeMessageQueue对象的成员变量mLooper中,这个对象的作用是,当Java层的消息队列中没有消息时,就使Android应用程序主线程进入等待状态,而当Java层的消息队列中来了新的消息后,就唤醒Android应用程序的主线程来处理这个消息。

Ok,接下来进入消息循环Looper.loop()该函数在前面贴过,消息循环就是一个无限取消息处理消息的过程,处理消息前面也说过,现在看看消息是如何取出来的。
Message msg = queue.next(); // might block


final Message next() {
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;

    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }
        nativePollOnce(mPtr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            final Message msg = mMessages;
            if (msg != null) {
                final long when = msg.when;
                if (now >= when) {
                    mBlocked = false;
                    mMessages = msg.next;
                    msg.next = null;
                    if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg);
                    return msg;
                } else {
                    nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
                }
            } else {
                nextPollTimeoutMillis = -1;
    //省略一部分
            }
        }
    }
}


调用nativePollOnce,还记得mPtr吗,不记得看前面。
void NativeMessageQueue::pollOnce(int timeoutMillis) {
    mLooper->pollOnce(timeoutMillis);
}

接着会继续调用Looper.pollInner()
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
bool acquiredLock = false;

等待管道事件,监听管道两端的读写事件,epoll_wait的最后一个参数表示超时时间。在管道没有IO事件发生时自动休眠,直到有事件来激活或者超时。
for (int i = 0; i < eventCount; i++) {
    int fd = eventItems[i].data.fd;
    uint32_t epollEvents = eventItems[i].events;
    if (fd == mWakeReadPipeFd) {
        if (epollEvents & EPOLLIN) {
            awoken();
        } else {
            LOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
        }
    } else {
    }
}


如果从epoll_wait返回时eventcount=0,则说明已经超时了什么也不处理。如果eventcount>0则说明有读或者写事件需要处理(当然我们只关心读),所以在后面的判断中如果是读事件就调用awoken,awoken很简单只是读取管道。这样函数从native中开始不断返回最后回到MessageQueue中的nativePollOnce中,然后就是消息的处理,怎么处理上文中已经说过。源代码可以参照(\frameworks\base\core\java\android\os\MessageQueue.java)。

比较零散,大家自行串起来吧

[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞0
打赏
分享
最新回复 (3)
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
禁止发春 2015-4-27 22:56
2
0
感谢分享,难得在这个版块看到长篇的分析,这里比较冷清
雪    币: 4
活跃值: (11)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Mr志志 2015-4-28 15:22
3
0
分析好深入。jni层一直不太懂,看完还是不懂 =-=
雪    币: 100
活跃值: (323)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Arcade 2015-4-28 15:49
4
0
建议楼主画个流程图神马的。

不知道楼主有没看  深入理解android 卷1,2 这本书。个人感觉上面就写的不错
游客
登录 | 注册 方可回帖
返回