当前位置: 代码迷 >> Android >> android的UI开发工程师向导
  详细解决方案

android的UI开发工程师向导

热度:29   发布时间:2016-05-01 19:36:19.0
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/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;}

?

?

  相关解决方案