android的UI开发工程师指引
不管是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/12Handler.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() {
intpendingIdleHandlerCount=-1;//-1onlyduringfirstiteration
intnextPollTimeoutMillis=0;
for(;;){
if(nextPollTimeoutMillis!=0){
Binder.flushPendingCommands();
}
nativePollOnce(mPtr,nextPollTimeoutMillis);
synchronized(this){
//Trytoretrievethenextmessage.Returniffound.
finallongnow=SystemClock.uptimeMillis();
finalMessagemsg=mMessages;
if(msg!=null){
finallongwhen=msg.when;
if(now>=when){
mBlocked=false;
mMessages=msg.next;
msg.next=null;
if(false)Log.v("MessageQueue","Returningmessage:"+msg);
msg.markInUse();
returnmsg;
}else{
nextPollTimeoutMillis=(int)Math.min(when-now,Integer.MAX_VALUE);
}
}else{
nextPollTimeoutMillis=-1;
}
//Iffirsttime,thengetthenumberofidlerstorun.
if(pendingIdleHandlerCount<0){
pendingIdleHandlerCount=mIdleHandlers.size();
}
if(pendingIdleHandlerCount==0){
//Noidlehandlerstorun.Loopandwaitsomemore.
mBlocked=true;
continue;
}
if(mPendingIdleHandlers==null){
mPendingIdleHandlers=newIdleHandler[Math.max(pendingIdleHandlerCount,4)];
}
mPendingIdleHandlers=mIdleHandlers.toArray(mPendingIdleHandlers);
}
//Runtheidlehandlers.
//Weonlyeverreachthiscodeblockduringthefirstiteration.
for(inti=0;i<pendingIdleHandlerCount;i++){
finalIdleHandleridler=mPendingIdleHandlers[i];
mPendingIdleHandlers[i]=null;//releasethereferencetothehandler
booleankeep=false;
try{
keep=idler.queueIdle();
}catch(Throwablet){
Log.wtf("MessageQueue","IdleHandlerthrewexception",t);
}
if(!keep){
synchronized(this){
mIdleHandlers.remove(idler);
}
}
}
//Resettheidlehandlercountto0sowedonotrunthemagain.
pendingIdleHandlerCount=0;
//Whilecallinganidlehandler,anewmessagecouldhavebeendelivered
//sogobackandlookagainforapendingmessagewithoutwaiting.
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; }