nativePollOnce()的实现函数是android\_os\_MessageQueue\_nativePollOnce,代码如下:
**android_os_MessageQueue.cpp-->android_os_MessageQueue_nativePollOnce()**
```
static voidandroid_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jintptr, jint timeoutMillis)
NativeMessageQueue* nativeMessageQueue =
reinterpret_cast<NativeMessageQueue*>(ptr);
// 取出NativeMessageQueue对象,并调用它的pollOnce
nativeMessageQueue->pollOnce(timeoutMillis);
}
```
分析pollOnce函数:
**android_os_MessageQueue.cpp-->NativeMessageQueue::pollOnece()**
```
void NativeMessageQueue::pollOnce(inttimeoutMillis) {
mLooper->pollOnce(timeoutMillis); // 重任传递到Looper的pollOnce函数
}
```
Looper的pollOnce函数如下:
**Looper.cpp-->Looper::pollOnce()**
```
inline int pollOnce(int timeoutMillis) {
returnpollOnce(timeoutMillis, NULL, NULL, NULL);
}
```
上面的函数将调用另外一个有4个参数的pollOnce函数,这个函数的原型如下:
```
int pollOnce(int timeoutMillis, int* outFd, int*outEvents, void** outData)
```
其中:
- timeOutMillis参数为超时等待时间。如果为-1,则表示无限等待,直到有事件发生为止。如果值为0,则无需等待立即返回。
- outFd用来存储发生事件的那个文件描述符 。
- outEvents用来存储在该文件描述符1上发生了哪些事件,目前支持可读、可写、错误和中断4个事件。这4个事件其实是从epoll事件转化而来。后面我们会介绍大名鼎鼎的epoll。
- outData用于存储上下文数据,这个上下文数据是由用户在添加监听句柄时传递的,它的作用和pthread\_create函数最后一个参数param一样,用来传递用户自定义的数据。
另外,pollOnce函数的返回值也具有特殊的意义,具体如下:
- 当返回值为ALOOPER\_POLL\_WAKE时,表示这次返回是由wake函数触发的,也就是管道写端的那次写事件触发的。
- 返回值为ALOOPER\_POLL\_TIMEOUT表示等待超时。
- 返回值为ALOOPER\_POLL\_ERROR,表示等待过程中发生错误。
- 返回值为ALOOPER\_POLL\_CALLBACK,表示某个被监听的句柄因某种原因被触发。这时,outFd参数用于存储发生事件的文件句柄,outEvents用于存储所发生的事件。
上面这些知识是和epoll息息相关的。
* * * * *
**提示** 查看Looper的代码会发现,Looper采用了编译选项(即#if和#else)来控制是否使用epoll作为I/O复用的控制中枢。鉴于现在大多数系统都支持epoll,这里仅讨论使用epoll的情况。
* * * * *
#### 1. epoll基础知识介绍
epoll机制提供了Linux平台上最高效的I/O复用机制,因此有必要介绍一下它的基础知识。
从调用方法上看,epoll的用法和select/poll非常类似,其主要作用就是I/O复用,即在一个地方等待多个文件句柄的I/O事件。
下面通过一个简单例子来分析epoll的工作流程。
```
/* **① 使用epoll前,需要先通过epoll_create函数创建一个epoll句柄。**
下面一行代码中的10表示该epoll句柄初次创建时候分配能容纳10个fd相关信息的缓存。
对于2.6.8版本以后的内核,该值没有实际作用,这里可以忽略。其实这个值的主要目的是
确定分配一块多大的缓存。现在的内核都支持动态拓展这块缓存,所以该值就没有意义了 */
int epollHandle = epoll_create(10);
/* **② 得到epoll句柄后,下一步就是通过epoll_ctl把需要监听的文件句柄加入到epoll句柄中。**
除了指定文件句柄本身的fd值外,同时还需要指定在该fd上等待什么事件。epoll支持四类事件,
分别是EPOLLIN(句柄可读)、EPOLLOUT(句柄可写),EPOLLERR(句柄错误)、EPOLLHUP(句柄断)。
epoll定义了一个结构体struct epoll_event来表达监听句柄的诉求。
假设现在有一个监听端的socket句柄listener,要把它加入到epoll句柄中 */
struct epoll_event listenEvent; //先定义一个event
/* EPOLLIN表示可读事件,EPOLLOUT表示可写事件,另外还有EPOLLERR,EPOLLHUP表示
系统默认会将EPOLLERR加入到事件集合中 */
listenEvent.events = EPOLLIN;// 指定该句柄的可读事件
// epoll_event中有一个联合体叫data,用来存储上下文数据,本例的上下文数据就是句柄自己
listenEvent.data.fd = listenEvent;
/* **③** EPOLL_CTL_ADD将监听fd和监听事件加入到epoll句柄的等待队列中;
EPOLL_CTL_DEL将监听fd从epoll句柄中移除;
EPOLL_CTL_MOD修改监听fd的监听事件,例如本来只等待可读事件,现在需要同时等待
可写事件,那么修改listenEvent.events 为EPOLLIN|EPOLLOUT后,再传给epoll句柄*/
epoll_ctl(epollHandle,EPOLL_CTL_ADD,listener,&listenEvent);
/* 当把所有感兴趣的fd都加入到epoll句柄后,就可以开始坐等感兴趣的事情发生了。
为了接收所发生的事情,先定义一个epoll_event数组 */
struct epoll_event resultEvents[10];
int timeout = -1;
while(1) {
/* **④ 调用epoll_wait用于等待事件。**其中timeout可以指定一个超时时间,
resultEvents用于接收发生的事件,10为该数组的大小。
epoll_wait函数的返回值有如下含义:
nfds大于0表示所监听的句柄上有事件发生;
nfds等于0表示等待超时;
nfds小于0表示等待过程中发生了错误*/
int nfds = epoll_wait(epollHandle,resultEvents, 10, timeout);
if(nfds == -1) {
// epoll_wait发生了错误
} else if(nfds == 0) {
//发生超时,期间没有发生任何事件
} else{
// ⑤resultEvents用于返回那些发生了事件的信息
for(int i = 0; i < nfds; i++) {
struct epoll_event & event =resultEvents[i];
if(event & EPOLLIN) {
/* **⑥ 收到可读事件。**到底是哪个文件句柄发生该事件呢?可通过event.data这个联合
体取得 前传递给epoll的上下文数据,该上下文信息可用于判断到底是谁发生了事件 */
......
}
.......//其他处理
}
}
}
```
epoll整体使用流程如上面代码所示,基本和select/poll类似,不过作为Linux平台最高效的I/O复用机制,这里有些内容供读者参考,
epoll的效率为什么会比select高?其中一个原因是调用方法。每次调用select时,都需要把感兴趣的事件复制到内核中,而epoll只在epll\_ctl进行加入的时候复制一次。另外,epoll内部用于保存事件的数据结构使用的是红黑树,查找速度很快。而select采用数组保存信息,不但一次能等待的句柄个数有限,并且查找起来速度很慢。当然,在只等待少量文件句柄时,select和epoll效率相差不是很多,但还是推荐使用epoll。
epoll等待的事件有两种触发条件,一个是水平触发(EPOLLLEVEL),另外一个是边缘触发(EPOLLET,ET为Edge Trigger之意),这两种触发条件的区别非常重要。读者可通过man epoll查阅系统提供的更为详细的epoll机制。
最后,关于pipe,还想提出一个小问题供读者思考讨论:
为什么Android中使用pipe作为线程间通讯的方式?对于pipe的写端写入的数据,读端都不感兴趣,只是为了简单的唤醒。POSIX不是也有线程间同步函数吗?为什么要用pipe呢?
关于这个问题的答案,可参见邓凡平的一篇博文“随笔之如何实现一个线程池”。
- <http://www.cnblogs.com/innost/archive/2011/11/24/2261454.html>
#### 2. pollOnce()函数分析
下面分析带4个参数的pollOnce()函数,代码如下:
**Looper.cpp-->Looper::pollOnce()**
```
int Looper::pollOnce(int timeoutMillis, int*outFd, int* outEvents,
void** outData) {
intresult = 0;
for(;;) { // 一个无限循环
// mResponses是一个Vector,这里首先需要处理response
while (mResponseIndex < mResponses.size()) {
const Response& response = mResponses.itemAt(mResponseIndex++);
ALooper_callbackFunc callback = response.request.callback;
if (!callback) {// 首先处理那些没有callback的Response
int ident = response.request.ident; // ident是这个Response的id
int fd = response.request.fd;
int events = response.events;
void* data = response.request.data;
......
if (outFd != NULL) *outFd = fd;
if (outEvents != NULL) *outEvents = events;
if (outData != NULL) *outData = data;
/* 实际上,对于没有callback的Response,pollOnce只是返回它的
ident,并没有实际做什么处理。因为没有callback,所以系统也不知道如何处理 */
return ident;
}
}
if(result != 0) {
if(outFd != NULL) *outFd = 0;
if (outEvents != NULL) *outEvents = NULL;
if (outData != NULL) *outData = NULL;
return result;
}
// 调用pollInner函数。注意,它在for循环内部
result = pollInner(timeoutMillis);
}
}
```
初看上面的代码,可能会让人有些丈二和尚摸不着头脑。但是把pollInner()函数分析完毕,大家就会明白很多。pollInner()函数非常长,把用于调试和统计的代码去掉,结果如下:
**Looper.cpp-->Looper::pollInner()**
```
int Looper::pollInner(int timeoutMillis) {
if(timeoutMillis != 0 && mNextMessageUptime != LLONG_MAX) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
......//根据Native Message的信息计算此次需要等待的时间
timeoutMillis = messageTimeoutMillis;
}
intresult = ALOOPER_POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
#ifdef LOOPER_USES_EPOLL // 只讨论使用epoll进行I/O复用的方式
structepoll_event eventItems[EPOLL_MAX_EVENTS];
// 调用epoll_wait,等待感兴趣的事件或超时发生
inteventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS,
timeoutMillis);
#else
......//使用别的方式进行I/O复用
#endif
//从epoll_wait返回,这时候一定发生了什么事情
mLock.lock();
if(eventCount < 0) { //返回值小于零,表示发生错误
if(errno == EINTR) {
goto Done;
}
//设置result为ALLOPER_POLL_ERROR,并跳转到Done
result = ALOOPER_POLL_ERROR;
gotoDone;
}
//eventCount为零,表示发生超时,因此直接跳转到Done
if(eventCount == 0) {
result = ALOOPER_POLL_TIMEOUT;
gotoDone;
}
#ifdef LOOPER_USES_EPOLL
// 根据epoll的用法,此时的eventCount表示发生事件的个数
for (inti = 0; i < eventCount; i++) {
intfd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
/* 之前通过pipe函数创建过两个fd,这里根据fd知道是管道读端有可读事件。
读者还记得对nativeWake函数的分析吗?在那里我们向管道写端写了一个”W”字符,这样
就能触发管道读端从epoll_wait函数返回了 */
if(fd == mWakeReadPipeFd) {
if (epollEvents & EPOLLIN) {
// awoken函数直接读取并清空管道数据,读者可自行研究该函数
awoken();
}
......
}else {
/* mRequests和前面的mResponse相对应,它也是一个KeyedVector,其中存储了
fd和对应的Request结构体,该结构体封装了和监控文件句柄相关的一些上下文信息,
例如回调函数等。我们在后面的小节会再次介绍该结构体 */
ssize_t requestIndex = mRequests.indexOfKey(fd);
if (requestIndex >= 0) {
int events = 0;
// 将epoll返回的事件转换成上层LOOPER使用的事件
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;
// 每处理一个Request,就相应构造一个Response
pushResponse(events, mRequests.valueAt(requestIndex));
}
......
}
}
Done: ;
#else
......
#endif
// 除了处理Request外,还处理Native的Message
mNextMessageUptime = LLONG_MAX;
while(mMessageEnvelopes.size() != 0) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
const MessageEnvelope& messageEnvelope =mMessageEnvelopes.itemAt(0);
if(messageEnvelope.uptime <= now) {
{
sp<MessageHandler> handler = messageEnvelope.handler;
Message message = messageEnvelope.message;
mMessageEnvelopes.removeAt(0);
mSendingMessage = true;
mLock.unlock();
/* 调用Native的handler处理Native的Message
从这里也可看出Native Message和Java层的Message没有什么关系 */
handler->handleMessage(message);
}
mLock.lock();
mSendingMessage = false;
result = ALOOPER_POLL_CALLBACK;
}else {
mNextMessageUptime = messageEnvelope.uptime;
break;
}
}
mLock.unlock();
// 处理那些带回调函数的Response
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;
// 调用回调函数处理所发生的事件
int callbackResult = callback(fd, events, data);
if (callbackResult == 0) {
// callback函数的返回值很重要,如果为0,表明不需要再次监视该文件句柄
removeFd(fd);
}
result = ALOOPER_POLL_CALLBACK;
}
}
returnresult;
}
```
看完代码了,是否还有点模糊?那么,回顾一下pollInner函数的几个关键点:
- 首先需要计算一下真正需要等待的时间。
- 调用epoll\_wait函数等待。
- epoll\_wait函数返回,这时候可能有三种情况:
a) 发生错误,则跳转到Done处。
b) 超时,这时候也跳转到Done处。
c) epoll\_wait监测到某些文件句柄上有事件发生。
- 假设epoll\_wait因为文件句柄有事件而返回,此时需要根据文件句柄来分别处理:
a) 如果是管道读这一端有事情,则认为是控制命令,可以直接读取管道中的数据。
b) 如果是其他FD发生事件,则根据Request构造Response,并push到Response数组中。
- 真正开始处理事件是在有Done标志的位置。
a) 首先处理Native的Message。调用Native Handler的handleMessage处理该Message。
b) 处理Response数组中那些带有callback的事件。
上面的处理流程还是比较清晰的,但还是有个一个拦路虎,那就是mRequests,下面就来清剿这个拦路虎。
#### 3. 添加监控请求
添加监控请求其实就是调用epoll\_ctl增加文件句柄。下面通过从Native的Activity找到的一个例子来分析mRequests。
**android_app_NativeActivity.cpp-->loadNativeCode_native()**
```
static jint
loadNativeCode_native(JNIEnv* env, jobject clazz,jstring path,
jstringfuncName,jobject messageQueue,
jstringinternalDataDir, jstring obbDir,
jstringexternalDataDir, int sdkVersion,
jobject jAssetMgr,jbyteArray savedState)
{
......
/* 调用Looper的addFd函数。第一个参数表示监听的fd;第二个参数0表示ident;
第三个参数表示需要监听的事件,这里为只监听可读事件;第四个参数为回调函数,当该fd发生
指定事件时,looper将回调该函数;第五个参数code为回调函数的参数 */
code->looper->addFd(code->mainWorkRead,0,
ALOOPER_EVENT_INPUT,mainWorkCallback, code);
......
}
```
Looper的addFd()代码如下所示:
**Looper.cpp-->Looper::addFd()**
```
int Looper::addFd(int fd, int ident, int events,
ALooper_callbackFunccallback, void* data) {
if (!callback) {
/* 判断该Looper是否支持不带回调函数的文件句柄添加。一般不支持,因为没有回调函数
Looper也不知道如何处理该文件句柄上发生的事情 */
if(! mAllowNonCallbacks) {
return -1;
}
......
}
#ifdef LOOPER_USES_EPOLL
intepollEvents = 0;
// 将用户的事件转换成epoll使用的值
if(events & ALOOPER_EVENT_INPUT) epollEvents |= EPOLLIN;
if(events & ALOOPER_EVENT_OUTPUT) epollEvents |= EPOLLOUT;
{
AutoMutex _l(mLock);
Request request; // 创建一个Request对象
request.fd = fd; // 保存fd
request.ident = ident; // 保存id
request.callback = callback; //保存callback
request.data = data; // 保存用户自定义数据
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event));
eventItem.events = epollEvents;
eventItem.data.fd = fd;
// 判断该Request是否已经存在,mRequests以fd作为key值
ssize_t requestIndex = mRequests.indexOfKey(fd);
if(requestIndex < 0) {
// 如果是新的文件句柄,则需要为epoll增加该fd
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem);
......
// 保存Request到mRequests键值数组
mRequests.add(fd, request);
}else {
// 如果之前加过,那么就修改该监听句柄的一些信息
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, &eventItem);
......
mRequests.replaceValueAt(requestIndex, request);
}
}
#else
......
#endif
return1;
}
```
#### 4. 处理监控请求
我们发现在pollInner()函数中,当某个监控fd上发生事件后,就会把对应的Request取出来调用。
```
pushResponse(events, mRequests.itemAt(i));
```
此函数如下:
**Looper.cpp-->Looper::pushResponse()**
```
void Looper::pushResponse(int events, constRequest& request) {
Responseresponse;
response.events = events;
response.request = request; //其实很简单,就是保存所发生的事情和对应的Request
mResponses.push(response);//然后保存到mResponse数组
}
```
根据前面的知识可知,并不是单独处理Request,而是需要先收集Request,等到Native Message消息处理完之后再做处理。这表明,在处理逻辑上,Native Message的优先级高于监控FD的优先级。
下面来了解如何添加Native的Message。
#### 5. Native的sendMessage
Android 2.2中只有Java层才可以通过sendMessage()往MessageQueue中添加消息,从4.0开始,Native层也支持sendMessage()了。sendMessage()的代码如下:
**Looper.cpp-->Looper::sendMessage()**
```
void Looper::sendMessage(constsp<MessageHandler>& handler,
constMessage& message) {
//Native的sendMessage函数必须同时传递一个Handler
nsecs_tnow = systemTime(SYSTEM_TIME_MONOTONIC);
sendMessageAtTime(now, handler, message); //调用sendMessageAtTime
}
[Looper.java-->Looper::sendMessageAtTime()]
void Looper::sendMessageAtTime(nsecs_t uptime,
constsp<MessageHandler>& handler,
constMessage& message) {
size_t i= 0;
{
AutoMutex _l(mLock);
size_t messageCount = mMessageEnvelopes.size();
// 按时间排序,将消息插入到正确的位置上
while (i < messageCount &&
uptime >= mMessageEnvelopes.itemAt(i).uptime) {
i += 1;
}
MessageEnvelope messageEnvelope(uptime, handler, message);
mMessageEnvelopes.insertAt(messageEnvelope, i, 1);
// mSendingMessage和Java层中的那个mBlocked一样,是一个小小的优化措施
if(mSendingMessage) {
return;
}
}
// 唤醒epoll_wait,让它处理消息
if (i ==0) {
wake();
}
}
```
- 前言
- 推荐序
- 第1章 开发环境部署
- 1.1获取Android源代码
- 1.2Android的编译
- 1.3在IDE中导入Android源代码
- 1.3.1将Android源代码导入Eclipse
- 1.3.2将Android源代码导入SourceInsight
- 1.4调试Android源代码
- 1.4.1使用Eclipse调试Android Java源代码
- 1.4.2使用gdb调试Android C/C 源代码
- 1.5本章小结
- 第2章 深入理解Java Binder和MessageQueue
- 2.1概述
- 2.2Java层中的Binder分析
- 2.2.1Binder架构总览
- 2.2.2初始化Java层Binder框架
- 2.2.3窥一斑,可见全豹乎
- 2.2.4理解AIDL
- 2.2.5Java层Binder架构总结
- 2.3心系两界的MessageQueue
- 2.3.1MessageQueue的创建
- 2.3.2提取消息
- 2.3.3nativePollOnce函数分析
- 2.3.4MessageQueue总结
- 2.4本章小结
- 第3章 深入理解AudioService
- 3.1概述
- 3.2音量管理
- 3.2.1音量键的处理流程
- 3.2.2通用的音量设置函数setStreamVolume()
- 3.2.3静音控制
- 3.2.4音量控制小结
- 3.3音频外设的管理
- 3.3.1 WiredAccessoryObserver 设备状态的监控
- 3.3.2AudioService的外设状态管理
- 3.3.3音频外设管理小结
- 3.4AudioFocus机制的实现
- 3.4.1AudioFocus简单的例子
- 3.4.2AudioFocus实现原理简介
- 3.4.3申请AudioFocus
- 3.4.4释放AudioFocus
- 3.4.5AudioFocus小结
- 3.5AudioService的其他功能
- 3.6本章小结
- 第4章 深入理解WindowManager-Service
- 4.1初识WindowManagerService
- 4.1.1一个从命令行启动的动画窗口
- 4.1.2WMS的构成
- 4.1.3初识WMS的小结
- 4.2WMS的窗口管理结构
- 4.2.1理解WindowToken
- 4.2.2理解WindowState
- 4.2.3理解DisplayContent
- 4.3理解窗口的显示次序
- 4.3.1主序、子序和窗口类型
- 4.3.2通过主序与子序确定窗口的次序
- 4.3.3更新显示次序到Surface
- 4.3.4关于显示次序的小结
- 4.4窗口的布局
- 4.4.1从relayoutWindow()开始
- 4.4.2布局操作的外围代码分析
- 4.4.3初探performLayoutAndPlaceSurfacesLockedInner()
- 4.4.4布局的前期处理
- 4.4.5布局DisplayContent
- 4.4.6布局的阶段
- 4.5WMS的动画系统
- 4.5.1Android动画原理简介
- 4.5.2WMS的动画系统框架
- 4.5.3WindowAnimator分析
- 4.5.4深入理解窗口动画
- 4.5.5交替运行的布局系统与动画系统
- 4.5.6动画系统总结
- 4.6本章小结
- 第5章 深入理解Android输入系统
- 5.1初识Android输入系统
- 5.1.1getevent与sendevent工具
- 5.1.2Android输入系统简介
- 5.1.3IMS的构成
- 5.2原始事件的读取与加工
- 5.2.1基础知识:INotify与Epoll
- 5.2.2 InputReader的总体流程
- 5.2.3 深入理解EventHub
- 5.2.4 深入理解InputReader
- 5.2.5原始事件的读取与加工总结
- 5.3输入事件的派发
- 5.3.1通用事件派发流程
- 5.3.2按键事件的派发
- 5.3.3DispatcherPolicy与InputFilter
- 5.3.4输入事件的派发总结
- 5.4输入事件的发送、接收与反馈
- 5.4.1深入理解InputChannel
- 5.4.2连接InputDispatcher和窗口
- 5.4.3事件的发送
- 5.4.4事件的接收
- 5.4.5事件的反馈与发送循环
- 5.4.6输入事件的发送、接收与反馈总结
- 5.5关于输入系统的其他重要话题
- 5.5.1输入事件ANR的产生
- 5.5.2 焦点窗口的确定
- 5.5.3以软件方式模拟用户操作
- 5.6本章小结
- 第6章 深入理解控件系统
- 6.1 初识Android的控件系统
- 6.1.1 另一种创建窗口的方法
- 6.1.2 控件系统的组成
- 6.2 深入理解WindowManager
- 6.2.1 WindowManager的创建与体系结构
- 6.2.2 通过WindowManagerGlobal添加窗口
- 6.2.3 更新窗口的布局
- 6.2.4 删除窗口
- 6.2.5 WindowManager的总结
- 6.3 深入理解ViewRootImpl
- 6.3.1 ViewRootImpl的创建及其重要的成员
- 6.3.2 控件系统的心跳:performTraversals()
- 6.3.3 ViewRootImpl总结
- 6.4 深入理解控件树的绘制
- 6.4.1 理解Canvas
- 6.4.2 View.invalidate()与脏区域
- 6.4.3 开始绘制
- 6.4.4 软件绘制的原理
- 6.4.5 硬件加速绘制的原理
- 6.4.6 使用绘图缓存
- 6.4.7 控件动画
- 6.4.8 绘制控件树的总结
- 6.5 深入理解输入事件的派发
- 6.5.1 触摸模式
- 6.5.2 控件焦点
- 6.5.3 输入事件派发的综述
- 6.5.4 按键事件的派发
- 6.5.5 触摸事件的派发
- 6.5.6 输入事件派发的总结
- 6.6 Activity与控件系统
- 6.6.1 理解PhoneWindow
- 6.6.2 Activity窗口的创建与显示
- 6.7 本章小结
- 第7章 深入理解SystemUI
- 7.1 初识SystemUI
- 7.1.1 SystemUIService的启动
- 7.1.2 状态栏与导航栏的创建
- 7.1.3 理解IStatusBarService
- 7.1.4 SystemUI的体系结构
- 7.2 深入理解状态栏
- 7.2.1 状态栏窗口的创建与控件树结构
- 7.2.2 通知信息的管理与显示
- 7.2.3 系统状态图标区的管理与显示
- 7.2.4 状态栏总结
- 7.3 深入理解导航栏
- 7.3.1 导航栏的创建
- 7.3.2 虚拟按键的工作原理
- 7.3.3 SearchPanel
- 7.3.4 关于导航栏的其他话题
- 7.3.5 导航栏总结
- 7.4 禁用状态栏与导航栏的功能
- 7.4.1 如何禁用状态栏与导航栏的功能
- 7.4.2 StatusBarManagerService对禁用标记的维护
- 7.4.3 状态栏与导航栏对禁用标记的响应
- 7.5 理解SystemUIVisibility
- 7.5.1 SystemUIVisibility在系统中的漫游过程
- 7.5.2 SystemUIVisibility发挥作用
- 7.5.3 SystemUIVisibility总结
- 7.6 本章小结
- 第8章 深入理解Android壁纸
- 8.1 初识Android壁纸
- 8.2深入理解动态壁纸
- 8.2.1启动动态壁纸的方法
- 8.2.2壁纸服务的启动原理
- 8.2.3 理解UpdateSurface()方法
- 8.2.4 壁纸的销毁
- 8.2.5 理解Engine的回调
- 8.3 深入理解静态壁纸-ImageWallpaper
- 8.3.1 获取用作静态壁纸的位图
- 8.3.2 静态壁纸位图的设置
- 8.3.3 连接静态壁纸的设置与获取-WallpaperObserver
- 8.4 WMS对壁纸窗口的特殊处理
- 8.4.1 壁纸窗口Z序的确定
- 8.4.2 壁纸窗口的可见性
- 8.4.3 壁纸窗口的动画
- 8.4.4 壁纸窗口总结
- 8.5 本章小结