android的UI开发工程师指引
2011-12-25 09:01
357 查看
不管是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
2. 遍历
遍历的时候,按照队列头部的时间戳(为0,则立即调用,否则等待超时),进行poll函数调用。
3.。 下面看一下looper的loop函数
Looper.java (frameworks\base\core\java\android\os) 8874 2011/12/12
sendMessage调用的queMessage,这与windows很不相同。
5. 一个小小的设计,则是效率上N倍以上的提升。
另外在UI开发中invalidate到最后的时候,就是一个sendMessage,而不是直接调用traversal方法。为的就是异步处理,防止一个循环周期中调用多次invalidate。这样可以给程序一个联合rect的机会。
代码如下:
注意mTraversalScheduled这个成员,是理解本机制的关键:
ViewRoot.java
完毕。这是我对UI开发的一些基础性的理解,请扔砖。也希望能抛砖引玉吧。
6.next函数一开始就调用了poolInner。来扫描一下用户事件,并直接调用到onTouchEvent等:
--------------------------------
娇妻如云 http://www.510h.com
剑道独尊 http://www.95wu.com
第一个是消息循环,第二个是界面组织结构。
围绕着这些,衍生出来的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; }
--------------------------------
娇妻如云 http://www.510h.com
剑道独尊 http://www.95wu.com
相关文章推荐
- android的UI开发工程师指引
- Android UI开发之RadioButton
- 史上最全Android开发资料:资源、UI、函数库、测试、构建全套教程
- Android开发学习之路--UI之基本布局
- Android开发之《Android应用开发揭秘》UI事件汇总
- Android开发优化之——对界面UI的优化(1)
- Android UI开发第二十八篇——Fragment中使用左右滑动菜单
- Android UI开发第一篇——android的九宫格式实现
- 工程师淘金:开发Android主攻四大方向
- Android UI开发神兵利器之Icon
- Android开发工程师
- Android UI开发详解之ActionBar
- Android UI开发第三十篇——使用Fragment构建灵活的桌面
- Android UI开发第十二篇——动画效果Animation
- 软件开发全套视频教程汇总(javaSE,javaEE,linux,android开发,C# ,web前端,大数据云计算,数据挖掘,web前端,php开发,UI设计,C++开发,3D视频)
- android 开发,百度地图 UI 控制器常用方法及作用
- Android开发之通过Handler的post方法更新UI
- 【转载】Android开发优化之——对界面UI的优化(1)
- Android UI开发第二十六篇——Fragment间的通信
- 《Android 开发工程师面试指南》