`
daojin
  • 浏览: 690487 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

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;
}

 

 

0
1
分享到:
评论

相关推荐

    混合式开发工程师+资深UI工程师+Android底层开发工程师+Android资深架构师-学习路线

    混合式开发工程师 资深UI工程师 Android底层开发工程师 Android资深架构师 学习路线,必备知识点清单,实战案例

    《Android应用程序开发教程(第2版)》教学课件02Android UI设计.pdf

    《Android应用程序开发教程(第2版)》教学课件02Android UI设计.pdf《Android应用程序开发教程(第2版)》教学课件02Android UI设计.pdf《Android应用程序开发教程(第2版)》教学课件02Android UI设计.pdf《Android应用...

    android UI界面开发图片

    在Android平台上进行UI界面开发时,图片资源是至关重要的元素,它们不仅决定了应用的视觉吸引力,也直接影响用户体验。本文将围绕“Android UI界面开发图片”这一主题,深入探讨相关知识点,帮助开发者创建出美观且...

    AndroidUI界面开发规范

    ### AndroidUI界面开发规范 #### 一、引言 随着移动设备的普及和发展,用户界面(UI)设计在软件开发中的重要性日益凸显。一个良好的UI不仅能够提升用户体验,还能够帮助开发者更好地传达产品理念。本篇文章将从...

    Android UI控件组件库集合【源码】

    在Android开发中,UI设计是至关重要的一环,它直接影响到应用程序的用户体验。这个"Android UI控件组件库集合【源码】"提供了多种常用的UI控件及其源代码,旨在帮助开发者构建更加美观、功能丰富的应用界面。下面...

    Android程序研发源码Android ui开发类库示例源码.zip

    在Android应用程序开发中,UI(用户界面)设计与实现是至关重要的部分,它直接影响到用户的体验和应用的吸引力。这份"Android程序研发源码Android ui开发类库示例源码.zip"压缩包提供了关于Android UI开发的一个实例...

    《Android UI设计》PDF

    许多开发人员需要一本Android UI设计入门级教程,其能够同时针对移动UI的设计模式和碎片化解决方案进行深入分析。 《Android UI设计》面向创建移动应用的产品经理、设计师和开发者,系统讲解了从事Android UI设计...

    androidUI设计器

    Android UI 设计器是Android开发中的一个重要工具,它允许开发者直观地设计应用程序的用户界面,无需手动编写XML布局代码。这个工具极大地提升了开发效率,使得非程序员也能参与到UI设计中来,实现快速预览和调整...

    Android UI界面开发源码2

    讲解了Windows和Linux操作系统下安装开发Android所需的工具集及...其次对Android中的基础UI和高级UI的开发技巧进行了全面、透彻的讲解, 接着对NDK的各个知识点进行了深入的剖析, 并结合源码对其原理进行深入的分析。

    基于Android UI开发设计的一款银行利率年限本金的源码

    【Android UI开发设计】在移动应用开发领域,Android UI(用户界面)是至关重要的部分,它决定了应用程序的外观和用户体验。Android Studio是Google官方推出的集成开发环境(IDE),专为Android应用开发而设计,提供...

    安卓Android源码——ui开发类库示例源码.zip

    在安卓(Android)平台上进行UI(用户界面)开发是一项至关重要的任务,因为它直接影响到应用程序的用户体验。本资源“安卓Android源码——ui开发类库示例源码.zip”提供了丰富的UI开发示例,帮助开发者更好地理解和...

    android ui开发程序

    在Android应用开发中,UI(用户界面)设计是至关重要的,因为它直接影响到用户的体验和应用程序的易用性。本教程针对“android UI开发程序”,专为初学者提供了一个宝贵的资源库,涵盖了Android UI设计的基本元素和...

    android UI大全

    在Android开发领域,UI设计是至关重要的一环,它关乎到应用程序的用户体验和视觉吸引力。"android UI大全"这个资源包,显然集成了多种Android用户界面元素的源码和示例,旨在帮助初学者快速入门,并为面试做准备。让...

    Android UI 生成器

    android UI 生成器 可以生成android的UI减轻工作,比eclipse内置的好多了,直接生成布局文件拉人项目直接使用

    《精通Android UI设计》

    本书详细阐述了与 Android UI开发相关的基本解决方案,主要包括 Android UI设计工具, Android UI布局, Android UI Widgets,Android UI选项菜单, Android UI本地菜单, Android UI操作栏, Android UI设计考虑, ...

    提高 Android UI 开发效率的 UI 库

    在Android开发中,UI设计和实现是至关重要的环节,它直接影响到应用的用户体验和整体质量。为了提高开发效率,许多开发者会使用UI库,这些库提供了丰富的组件、样式和工具,帮助开发者快速构建美观且功能齐全的用户...

    Android UI基础教程 高清带标签pdf

     《AndroidUI基础教程》是一本Android用户界面设计的基础入门图书。全书从最基础的技术概念开始介绍,深入讨论了UI设计的各项技术,涵盖了为应用程序创建UI的所有内容。本书不仅会告诉读者创建灵活布局的最佳方法,...

    Android ui开发类库示例源码.rar

    这份"Android UI开发类库示例源码"压缩包提供了一些实用的Android UI开发资源,帮助开发者更好地理解和实践Android UI设计。源码是学习的最佳途径,通过分析和研究这些示例,你可以深入掌握Android UI开发的技巧和...

    老罗Android开发视频教程 (android常用UI编程) 26-33集源码

    在本资源中,"老罗Android开发视频教程 (android常用UI编程) 26-33集源码.zip"是一个包含Android应用开发教学内容的压缩文件。老罗,可能指的是知名的技术讲师罗永浩,以其通俗易懂的讲解风格而闻名。这个教程聚焦于...

Global site tag (gtag.js) - Google Analytics