不管是MFC,还是linux,还是android,UI开发都是如下两大核心机制:
第一个是消息循环,第二个是界面组织结构。
围绕着这些,衍生出来的OpenGL,SurfaceView,SurfaceFinger等都是为这两大机制服务的。
?
打一个比方。
?
?
消息循环是UI中的发动机。
?
界面组织结构就是UI的设计结构。
?
而其他的东西,则是建立在这些基础之上的。
?
理解这两大块儿,那么android的UI基础就学得差不多了。这个时候可以结合一些例子,来做一些真正有意义的开发,例如UI特效啊。自定义动画啊。。。。也可以顺便把动画机制给理解吃透。
?
?
接下来就学一下Canvas,SurfaceFlinger,Matrix,来做一些特效。
如果想更深入地学习。那么学习一下OpenGL。
再想深入的话,学习一下JNI编程。
再深入的话,把java虚拟机给了解一下,也许对于提高程序效率帮助很大。
?
?
本篇就介绍一下消息队列:
?
android_os_MessageQueue.cpp
MessageQueue.java
Looper.cpp (frameworks\base\native\android)?2369?2011/12/12
Looper.java (frameworks\base\core\java\android\os)?8874?2011/12/12
Handler.java (frameworks\base\core\java\android\os)?23620?2011/12/12
Activity中的事件默认都是在UI线程中发生的。
这意味着Activity中的任何一个函数执行完之后,都要回到消息队列,这个节点。handleMessage结束之后,就会再次去消息队列查看消息。这跟windows上开发的消息队列的概念是一致的。
?
?
1.入队:
入队的时候,按照Message.when的大小进行排序。如果时间相同,那么按照入队的先后进行排序。
如果入队的时候,时间戳为0,那么就激活消息管道。否则不激活等超时。
?
MessageQueue.java
final boolean enqueueMessage(Message msg, long when) { if (msg.isInUse()) { throw new AndroidRuntimeException(msg + " This message is already in use."); } if (msg.target == null && !mQuitAllowed) { throw new RuntimeException("Main thread not allowed to quit"); } final boolean needWake; synchronized (this) { if (mQuiting) { RuntimeException e = new RuntimeException( msg.target + " sending message to a Handler on a dead thread"); Log.w("MessageQueue", e.getMessage(), e); return false; } else if (msg.target == null) { mQuiting = true; } msg.when = when; //Log.d("MessageQueue", "Enqueing: " + msg); Message p = mMessages; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; // new head, might need to wake up } else { Message prev = null; while (p != null && p.when <= when) { prev = p; p = p.next; } msg.next = prev.next; prev.next = msg; needWake = false; // still waiting on head, no need to wake up } } if (needWake) { nativeWake(mPtr); } return true; }
?
2.?遍历
遍历的时候,按照队列头部的时间戳(为0,则立即调用,否则等待超时),进行poll函数调用。
?
? 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 (false) Log.v("MessageQueue", "Returning message: " + msg);
??????????????????????? msg.markInUse();
??????????????????????? return msg;
??????????????????? } else {
??????????????????????? nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
??????????????????? }
??????????????? } else {
??????????????????? nextPollTimeoutMillis = -1;
??????????????? }
??????????????? // If first time, then get the number of idlers to run.
??????????????? if (pendingIdleHandlerCount < 0) {
??????????????????? pendingIdleHandlerCount = mIdleHandlers.size();
??????????????? }
??????????????? if (pendingIdleHandlerCount == 0) {
??????????????????? // No idle handlers to run.? Loop and wait some more.
??????????????????? mBlocked = true;
??????????????????? continue;
??????????????? }
??????????????? if (mPendingIdleHandlers == null) {
??????????????????? mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
??????????????? }
??????????????? mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
??????????? }
??????????? // Run the idle handlers.
??????????? // We only ever reach this code block during the first iteration.
??????????? for (int i = 0; i < pendingIdleHandlerCount; i++) {
??????????????? final IdleHandler idler = mPendingIdleHandlers[i];
??????????????? mPendingIdleHandlers[i] = null; // release the reference to the handler
??????????????? boolean keep = false;
??????????????? try {
??????????????????? keep = idler.queueIdle();
??????????????? } catch (Throwable t) {
??????????????????? Log.wtf("MessageQueue", "IdleHandler threw exception", t);
??????????????? }
??????????????? if (!keep) {
??????????????????? synchronized (this) {
??????????????????????? mIdleHandlers.remove(idler);
??????????????????? }
??????????????? }
??????????? }
??????????? // Reset the idle handler count to 0 so we do not run them again.
??????????? pendingIdleHandlerCount = 0;
??????????? // While calling an idle handler, a new message could have been delivered
??????????? // so go back and look again for a pending message without waiting.
??????????? nextPollTimeoutMillis = 0;
??????? }
??? }
?
?
?
3.。?下面看一下looper的loop函数
?
Looper.java (frameworks\base\core\java\android\os)?8874?2011/12/12
public static void loop() { Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } 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(); while (true) { Message msg = queue.next(); // might block if (msg != null) { if (msg.target == null) { // No target is a magic identifier for the quit message. return; } long wallStart = 0; long threadStart = 0; // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); wallStart = SystemClock.currentTimeMicro(); threadStart = SystemClock.currentThreadTimeMicro(); } msg.target.dispatchMessage(msg); if (logging != null) { long wallTime = SystemClock.currentTimeMicro() - wallStart; long threadTime = SystemClock.currentThreadTimeMicro() - threadStart; logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); if (logging instanceof Profiler) { ((Profiler) logging).profile(msg, wallStart, wallTime, threadStart, threadTime); } } // 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.recycle(); } } }
sendMessage调用的queMessage,这与windows很不相同。
?
5. 一个小小的设计,则是效率上N倍以上的提升。
另外在UI开发中invalidate到最后的时候,就是一个sendMessage,而不是直接调用traversal方法。为的就是异步处理,防止一个循环周期中调用多次invalidate。这样可以给程序一个联合rect的机会。
代码如下:
?
注意mTraversalScheduled这个成员,是理解本机制的关键:
ViewRoot.java
void invalidate() { mDirty.set(0, 0, mWidth, mHeight); scheduleTraversals(); }
?
public void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; //noinspection ConstantConditions if (ViewDebug.DEBUG_LATENCY && mLastTraversalFinishedTimeNanos != 0) { final long now = System.nanoTime(); Log.d(TAG, "Latency: Scheduled traversal, it has been " + ((now - mLastTraversalFinishedTimeNanos) * 0.000001f) + "ms since the last traversal finished."); } sendEmptyMessage(DO_TRAVERSAL); } }
?
完毕。这是我对UI开发的一些基础性的理解,请扔砖。也希望能抛砖引玉吧。
?
6.next函数一开始就调用了poolInner。来扫描一下用户事件,并直接调用到onTouchEvent等:
?
int Looper::pollInner(int timeoutMillis) {#if DEBUG_POLL_AND_WAKE LOGD("%p ~ pollOnce - waiting: timeoutMillis=%d", this, timeoutMillis);#endif // Adjust the timeout based on when the next message is due. if (timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); int messageTimeoutMillis = toMillisecondTimeoutDelay(now, mNextMessageUptime); if (messageTimeoutMillis >= 0 && (timeoutMillis < 0 || messageTimeoutMillis < timeoutMillis)) { timeoutMillis = messageTimeoutMillis; }#if DEBUG_POLL_AND_WAKE LOGD("%p ~ pollOnce - next message in %lldns, adjusted timeout: timeoutMillis=%d", this, mNextMessageUptime - now, timeoutMillis);#endif } // Poll. int result = ALOOPER_POLL_WAKE; mResponses.clear(); mResponseIndex = 0;#ifdef LOOPER_STATISTICS nsecs_t pollStartTime = systemTime(SYSTEM_TIME_MONOTONIC);#endif#ifdef LOOPER_USES_EPOLL struct epoll_event eventItems[EPOLL_MAX_EVENTS]; int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);#else // Wait for wakeAndLock() waiters to run then set mPolling to true. mLock.lock(); while (mWaiters != 0) { mResume.wait(mLock); } mPolling = true; mLock.unlock(); size_t requestedCount = mRequestedFds.size(); int eventCount = poll(mRequestedFds.editArray(), requestedCount, timeoutMillis);#endif // Acquire lock. mLock.lock(); // Check for poll error. if (eventCount < 0) { if (errno == EINTR) { goto Done; } LOGW("Poll failed with an unexpected error, errno=%d", errno); result = ALOOPER_POLL_ERROR; goto Done; } // Check for poll timeout. if (eventCount == 0) {#if DEBUG_POLL_AND_WAKE LOGD("%p ~ pollOnce - timeout", this);#endif result = ALOOPER_POLL_TIMEOUT; goto Done; } // Handle all events.#if DEBUG_POLL_AND_WAKE LOGD("%p ~ pollOnce - handling events from %d fds", this, eventCount);#endif#ifdef LOOPER_USES_EPOLL 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 { ssize_t requestIndex = mRequests.indexOfKey(fd); if (requestIndex >= 0) { int events = 0; if (epollEvents & EPOLLIN) events |= ALOOPER_EVENT_INPUT; if (epollEvents & EPOLLOUT) events |= ALOOPER_EVENT_OUTPUT; if (epollEvents & EPOLLERR) events |= ALOOPER_EVENT_ERROR; if (epollEvents & EPOLLHUP) events |= ALOOPER_EVENT_HANGUP; pushResponse(events, mRequests.valueAt(requestIndex)); } else { LOGW("Ignoring unexpected epoll events 0x%x on fd %d that is " "no longer registered.", epollEvents, fd); } } }Done: ;#else for (size_t i = 0; i < requestedCount; i++) { const struct pollfd& requestedFd = mRequestedFds.itemAt(i); short pollEvents = requestedFd.revents; if (pollEvents) { if (requestedFd.fd == mWakeReadPipeFd) { if (pollEvents & POLLIN) { awoken(); } else { LOGW("Ignoring unexpected poll events 0x%x on wake read pipe.", pollEvents); } } else { int events = 0; if (pollEvents & POLLIN) events |= ALOOPER_EVENT_INPUT; if (pollEvents & POLLOUT) events |= ALOOPER_EVENT_OUTPUT; if (pollEvents & POLLERR) events |= ALOOPER_EVENT_ERROR; if (pollEvents & POLLHUP) events |= ALOOPER_EVENT_HANGUP; if (pollEvents & POLLNVAL) events |= ALOOPER_EVENT_INVALID; pushResponse(events, mRequests.itemAt(i)); } if (--eventCount == 0) { break; } } }Done: // Set mPolling to false and wake up the wakeAndLock() waiters. mPolling = false; if (mWaiters != 0) { mAwake.broadcast(); }#endif#ifdef LOOPER_STATISTICS nsecs_t pollEndTime = systemTime(SYSTEM_TIME_MONOTONIC); mSampledPolls += 1; if (timeoutMillis == 0) { mSampledZeroPollCount += 1; mSampledZeroPollLatencySum += pollEndTime - pollStartTime; } else if (timeoutMillis > 0 && result == ALOOPER_POLL_TIMEOUT) { mSampledTimeoutPollCount += 1; mSampledTimeoutPollLatencySum += pollEndTime - pollStartTime - milliseconds_to_nanoseconds(timeoutMillis); } if (mSampledPolls == SAMPLED_POLLS_TO_AGGREGATE) { LOGD("%p ~ poll latency statistics: %0.3fms zero timeout, %0.3fms non-zero timeout", this, 0.000001f * float(mSampledZeroPollLatencySum) / mSampledZeroPollCount, 0.000001f * float(mSampledTimeoutPollLatencySum) / mSampledTimeoutPollCount); mSampledPolls = 0; mSampledZeroPollCount = 0; mSampledZeroPollLatencySum = 0; mSampledTimeoutPollCount = 0; mSampledTimeoutPollLatencySum = 0; }#endif // Invoke pending message callbacks. mNextMessageUptime = LLONG_MAX; while (mMessageEnvelopes.size() != 0) { nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC); const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0); if (messageEnvelope.uptime <= now) { // Remove the envelope from the list. // We keep a strong reference to the handler until the call to handleMessage // finishes. Then we drop it so that the handler can be deleted *before* // we reacquire our lock. { // obtain handler sp<MessageHandler> handler = messageEnvelope.handler; Message message = messageEnvelope.message; mMessageEnvelopes.removeAt(0); mSendingMessage = true; mLock.unlock();#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS LOGD("%p ~ pollOnce - sending message: handler=%p, what=%d", this, handler.get(), message.what);#endif handler->handleMessage(message); } // release handler mLock.lock(); mSendingMessage = false; result = ALOOPER_POLL_CALLBACK; } else { // The last message left at the head of the queue determines the next wakeup time. mNextMessageUptime = messageEnvelope.uptime; break; } } // Release lock. mLock.unlock(); // Invoke all response callbacks. for (size_t i = 0; i < mResponses.size(); i++) { const Response& response = mResponses.itemAt(i); ALooper_callbackFunc callback = response.request.callback; if (callback) { int fd = response.request.fd; int events = response.events; void* data = response.request.data;#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS LOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p", this, callback, fd, events, data);#endif int callbackResult = callback(fd, events, data); if (callbackResult == 0) { removeFd(fd); } result = ALOOPER_POLL_CALLBACK; } } return result;}
?
?