其他应用组件相关
Service的启动原理
获取AMS的binder对象并发起startService的调用。
其中ServiceRecord跟启动的Service是在AMS里面是对应关系的。
pendingStarts是之后调用startServiceCommon的方法。
r.app表示Service对应的进程,r.app.thread表示进程已经就绪。如果r.app不为null,代表当前service在当前进程已经启动就绪了,因为在service启动就绪后才会给r.app赋值。
根据上图区分几种情况:
- service启动就绪并且进程启动就绪,调用
sendServiceArgsLocked
- 进程启动了,就绪,但是service并未启动, 调用
realStartServiceLocked
,这个就是真正启动service的 - 进程未启动,调用
startProcessLocked
启动进程,然后在启动service
回顾下应用的启动流程:
跟进AMS里面attachApplication的方法:
会处理所以的pending意图,并调用realStartServiceLocked
方法:
scheduleCreateService是一个IPC调用,然后应用端会创建Service。
再跟进应用端,在ActivityThread.handleCreateService
里面:
就是反射调用创建Service,并调用了onCreate()方法。
再跟进前面的sendServiceArgsLocked
方法,看如何触发onStartCommand
:
最后又调用到主线程ActivityThread里面了,调用handleServiceArgs
:
总结:
Service的绑定原理
使用
原理
先看下整体流程:
看下bindService
具体实现:
因为ServiceConnection
不是一个binder对象,所以需要一个IServceConnection
的binder对象来跨进程通信,同时IServceConnection也是持有ServiceConnection的引用的。最后发起AMS端的bindService
调用:
先看下getServiceDispatcher
的调用:
就是一种缓存机制,真正创建的是在ServiceDispatcjer
的构造方法里面:
跟进RunConnection
干了什么:
RunConnection
是LoadedApk
的内部类,实现了Runnable
接口,run方法里面执行了doConnected
方法:
其中mActiveConnections其实就是个binder缓存的map,然后service!=null
表示已经绑定了,然后加载到缓存中。如果old!=null
表示之前已经绑定过了。最后一种情况,old==null&&service!=null
表示第一次绑定上了,回调到onServiceConnected
方法。
总结下getServiceDispatcher
这部分:
需要注意的是IServiceConnection是跟Context、ServiceConnection有关的。
再看绑定流程bindServiceLocked
方法:
其中bringUpServiceLocked
是用来启动Service的,最后由ActivityThread来调用Service的onCreate方法启动Servcie了。
而后面的判断:s是ServiceRecord对象,如果s.app!=null
表示Service已经运行。b.intent.received
表示当前应用程序进程已经接受到绑定Service时返回的Binder,这样应用程序进程就可以通过Binder来获取要绑定的Service的访问接口。如果b.intent.received
返回的是false,表示应用程序进程的客户端没有发送过绑定Service请求,则会调用requestServiceBindingLocked
方法。
接着往下看之前,了解一下相关对象:
上面有2个关键的函数:bringUpServiceLocked()
和 requestServiceBindingLocked()
。
先看bringUpServiceLocked
函数:
这里同Service原理部分一样。
再看requestServiceBindingLocked
函数:
首先i表示IntentBindRecord
,i.requested
表示是否发送给绑定Service的请求,i.app.size()>0
表示所以用当前Intent绑定Service的应用进程个数大于0。所以这个if条件是成立的,执行scheduleBindService
方法:
这里调用s.onBind(data,intent)
方法后,Service处于绑定状态了。如果rebing为true则会调用onRebind方法了。这里接着走未绑定的分支,在调用了onBind
方法后,再调用AMS的publishService
方法:
其中c.conn
就是IServiceConnection
,它是ServiceConnection
的本地代理。然后又走到了doConnected
方法了。
这时候再看看看 onRebind什么时候调用:
onRebind调用:
这里的条件有:进程是启动的、已经接受了binger对象、intent的第一个绑定的进程仅为一个、intent.doRebind是开启的。才会执行onRebind调用。其中doRebind调用在:
其实意思就是要先执行onUnbind才会开启。
bindService和startService区别:
bindService不会触发onStartServiceCommand(),因为binderService没有把ServiceRecord加到pendingStarts队列里 (pendingStarts是需要启动的Service列表) --> startServiceLocked函数里才会加到pendingStarts队列里
动态广播的注册与收发原理
动态广播的注册原理
先看registerReceiver
方法:
最终会调向registerReceiverInternal
方法:
其中IIntentReceiver
是个Binder对象,这里有2个关键的方法:getReceiverDispatcher
和AMS.registerReceiver
。
先看getReceiverDispatcher
方法:
这里可以看出,这里是跟Service的设计很像的。由Context
和BroadcastReceiver
两变量对应不同的IIntentReciver
。
接着看ReciverDispatcher
的构造函数:
最后会形成以下引用链:
就可以达到IItentReceiver的IPC的通信了。
再回到AMS.registerReceiver
方法:
其中mRegisteredReceivers是一个map对象:Map<IBinder,ReceiverLIst>
,一个IIntentReceiver
可能对应多个IntentReceive
(一个Activity中,用多个IntentFilter注册同一个BroadcastReceiver)。addFilter(BroadcastFilter)
是将BroadcastFilter添加到IntentResolver类型的mReceiverResolver中,这样AMS接到广播时就能从mReceiverResolver中找到对应的广播接受者了,从而达到了注册广播的目的。
广播的发送原理
对于普通的动态广播:系统端并行分发,到了应用端变成串行分发:
然后还做了很多其他事情:广播的校验、静态广播与动态广播列表的合并、有序无序广播区分。最终走到广播的接受部分。
广播的接受原理
其中Args
是一个Runnable对象,ReceiveDispatcher的内部类。发送到主线程处理了。
其中执行了onReceive
的回调。
总体回顾:
静态广播的注册与收发原理
广播的注册
静态广播的注册是要在清单文件中声明的。
所以静态广播会在系统启动后,ServerService会拉起PackageManagerService
去扫描已安装app的清单文件。会调用PackageManagerService.scanPackageLI
方法,然后最后调用到commitPackageSettings
方法,部分代码如下:
for (i=0; i<N; i++) {
PackageParser.Activity a = pkg.receivers.get(i);
a.info.processName = fixProcessName(pkg.applicationInfo.processName,
a.info.processName);
mReceivers.addActivity(a, "receiver");
if (chatty) {
if (r == null) {
r = new StringBuilder(256);
} else {
r.append(' ');
}
r.append(a.info.name);
}
}
广播的发送
跟进boradcastIntentLocked
方法:
其中collectReceiverComponents用来寻找静态广播,mReceiverResolver.queryIntent用来寻找动态广播。
其中queue.scheduleBroadcastsLocked()
来处理串行分发,然后会使用BroadcastHandle发送BROADCAST_INTENT_MSG消息,并在handleMessage里处理,执行了processNextBroadcast
方法,嗯,代码非常长:
先看超时部分代码:
然后看broadcastTimeoutLocked
方法:
接着往下执行,如果并未超时,此时就idle状态,就开始执行下一个:
继续:
这里需要理解一点,startProcessLocked
这个现在可能没有那么好使了,流氓应用喜欢利用静态广播自启。
广播的接受
这里deliverToRegisteredReceiverLocked
方法是处理动态广播的分发, 记住这里的BroadcastRecord状态切换成CALL_DONE_RECEIVE
。然后调用到主线程的scheduleRegisteredReceiver
方法。会执行performReceive
方法:
主要干了两件事,调用广播的回调方法onReceive()
,然后通过IPC调用AMS的finishReceiver()
方法,通知AMS当前广播已经处理完了。因为在应用端,广播的分发是串行的。
其中finishReceiverLocked
是来决定是否继续串行分发广播。还记得前面动态BroadcastRecord状态切换成CALL_DONE_RECEIVE
了,这里会返回true了,然后执行下一个。
再看静态广播分发:
这里调用的是ActivitThread.scheduleReceiver
方法,创建了一个消息,并发送到主线程处理,会执行到handleReceiver
方法:
这里的getReceiverRestrictedContext
返回的ReceiverRestrictedContext
是一个以Application为mBase的ContextWrapper
。其中静态广播的BroadcastRecord状态为APP_RECEIVE,所以调用finish()方法后可以继续分发。
这里需要注意的是,静态广播存在优先级,优先级高的广播有权提前结束广播,调用abortBroadcast
方法来终止。
Provider的启动原理
这里没啥好讲的,一般用的少。最近的使用就是 app-start 框架 有用到,通过xml注册Provider来自动初始化第三方库。
UI体系相关
android的UI刷新机制
双缓存机制
在屏幕刷新中,Android系统引入了双缓冲机制。屏幕会以固定的频率从Frame Buffer读取数据,CPU每次提交数据给GPU后,GPU会向Back Buffer中缓存数据,Frame Buffer则会以每隔16.6ms与Back Buffer交换数据,然后交给屏幕绘制。
绘制流程
从常见入口 requestLayout() 开始
requestLayout() --> scheduleTraverals() --> Choreographer(创建是利用了ThreadLocal,线程独有)–>doTraversal()
注意点:一次vsync周期,只会调度一次Choreographer的绘制。
应用端是什么时候开始绘制的? 应用端绘制是独立的,如果vsync来临,当前绘制帧卡在来临之后,就会跳过当前的绘制帧,当前帧是不变化的,即使当前绘制操作优化的很好,频率过高也会给用户感觉丢帧。
优化思路:在vsync信号来的时候才进行绘制,并且绘制过程控制在16ms内,那么应用就会非常流畅
实现:Choreographer(舞蹈指导),把绘制操作封装成一个runnable丢给Choreographer,下一个vsync信号来的时候,就开始处理消息,真正的开始重绘(相当于绘制节奏,交由Choreographer处理了)
Choreographer工作原理:
- 应用层的View调用requestLayout方法,想要重绘。
- Choreographer.postCallback,其实是new了一个runnable,丢到Choreographer的消息队列里
- Choreographer没有马上处理消息,调用requestNextVsync函数,向SurfaceFlinger请求下一个vsync信号
- SurfaceFlinger在下一个vsync信号来的时候,通过postSyncEvent函数通知Choreographer
- Choreographer收到通知以后,就会处理消息队列里的消息,之前的requestLayout对应的就算执行里面的performTraversal函数,真正执行绘制。
- 利用 SyncBarrier:屏障,把这个屏障插到消息队列里,后面的普通消息是不能处理的。但对异步消息是没有影响的,为的就是让一些紧急处理的消息先执行。
- 处理绘制消息的对象:FrameDisplayEventReceiver(利用handler不是为了切线程,而是按时间戳发消息) --> doFrame()。
- doFrame()会计算当前时间与时间戳的间隔,间隔越大表示这一帧处理的时间越久,如果间隔超过一个周期,就会去计算跳过了多少帧,并打印出一个日志,这个日志我想很多人可能都见过:
Log.i(TAG, "Skipped " + skippedFrames + " frames! "
+ "The application may be doing too much work on its main thread.");
交互是通过管道机制,BitTube,一对Socket描述符(mSendFd和mReceiverFd),读信号和写信号能相互唤醒
- doFrame()最终mCallbackQueue中取出消息并按照时间顺序调用mTraversalRunnable的run()方法。
- 执行run方法:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
补充问题
- 丢帧一般是什么原因引起的?
主线程有耗时操作,耽误了view的绘制 - Android刷新频率60帧/秒,是每隔16ms调用onDraw绘制一次?
这里的刷新频率是指vsync的频率,并不是每调一次绘制一次。需要应用端主动发起重绘,才会向SurfaceFlinger请求接收vsync信号,在下次来vsync信号的时候才会真正的去绘制。 - onDraw完之后屏幕会马上刷新么?
onDraw完之后不回立即刷新的,而是等vsync信号来了才会刷新,才会交换缓存。 - 如果界面没有重绘,还会每隔16ms刷新屏幕么?
没有重绘,屏幕还是会刷新,只是画面数据一直用的是旧的。 - 如果在屏幕快要刷新的时候才去绘制会丢帧么?
如果在屏幕快要刷新的时候才去onDraw绘制会丢帧吗?
不会,View的重绘不会马上执行,需要等待下一次vsync信号。
surface跨进程原理
surface实现了Parcelable接口,就能表示它能跨进程通信。注意实现为writeToParcel
和readFormParcel
。
Activity的Surface是怎么跨进程传递的?
跟进查看AMS端处理逻辑:
这里outSurface会从SurfaceControl里面拷贝一些内容:
将SurfaceControl
的native对象与Surface的Native对象进行绑定。
这里就是把SurfaceControl的GraphicBufferProducer
(图像缓存生产者)传入创建的native层Surface对象,然后native层对象的Surface的指针会保存在传入进来outSurface
里面。
总结:
Activity在第一次绘制就会去申请Surface,在调用relayoutWindow的时候申请,mWindowSession提供IPC通信,向WMS传了一个空壳的Surface,WMS从native层获取SurfaceControl,里面有个GraphicBufferProducer,可从里面拷贝buffer到Surface,同时传进来的空壳的Surface会保存native的Surface指针。
注意点:
- surface本质是GraphicBufferProducer,而不是buffer
- surface跨进程传递,本质就是GraphicBufferProducer的传递,本身也是binder对象,传输非常效率
问题点:
- 怎么理解surface,它是一块buffer吗?
不是buffer,是个壳子,里面包含一个能生产buffer对象的GraphicBufferProducer(GBP) - 如果不是,suface是怎么跨进程传递的?
传递GraphicBufferProducer对象 - Activity的surface在系统中创建后,是怎么跨进程传回应用的?
系统创建的是SurfaceControl对象,里面有GBP,可以创建Surface,跨进程返回应用
surface的绘制原理
从performTraversals绘制开始:
默认采用软件绘制,执行drawSoftware
方法:
看lockCanvas
方法,调用了native方法,mNativeObjec就是Surface在native层对象对应的指针,mCanvas是Surface里面的对象,里面有一个sk类型bitmap作为mCanvas的缓冲区,再调用完nativeLockCanvas方法后就会被赋予值,并返回mCanvas。具体如下:
这里的关键就是通过native层的surface对象调用lock方法获取一块buffer,之后SKBitmap调用setPixels设置其内存地址为bufeer的,然后canvas设置bitmap。
重点查看surface怎么获取一块buffer,跟进lock
函数:
先调用dequeueBuffer
去申请了一块buffer,然后转化成backBuffer,lockAsync这个函数就是把buffer的handle中的地址传到vaddr中。mLockedBuffer是Surface中表示backBuffer的变量。
查看dequeueBuffer
如何获取buffer:
大意就是从远端的BufferQueue获取,返回一个result,并将下标赋值给buf。然后开始检测双端是否要同步,如果result带有BUFFER_NEEDS_REALLOCATION标签、或者本地gbuf为0,就需要调用requestBuffer刷新一下。
然后查看unlockCanvasAndPost
来提交:
调用native层的方法,然后释放资源。
给nativeCanvas设置了一个空的SKBitmap,然后调用了surface的unlockAndPost
方法:
首先mLockedBuffer解除锁定,然后调用queueBuffer
方法,重新提交BufferQueue,给定buffer一个index下标,通过GBP提交给远端。然后mLockedBuffer就赋值给前台buffer mPostedBuffer
,再自身重制。
总结:
- 应用绘制首先要申请Surface。
- Surface需要一块Buffer。
- 调用dequeueBuffer方法得到一块Buffer。
- 要获取buffer,得在SurfaceFlinger里创建一个BufferQueue --> 一个Surface对应一个BufferQueue。
- 这个BufferQueue有两端(producer和consumer),producer端需要跨进程传给应用,交由Surface保管。
- Surface需要绘制的时候,通过producer端向BufferQueue发起binder调用,申请一块buffer。
- 这个buffer作为Canvas的Bitmap缓冲区(和我们用的Bitmap不同,是底层Skia绘制用的),绘制操作完成把这个buffer返回给BufferQueue。
- BufferQueue通知consumer端,回调它的onFrameAvaliable,表示又有一帧数据绘制完毕了。 consumer可以在SurfaceFlinger进程里,也可以传给其他应用进程,就是用来消耗这一帧数据的。
相关问题:
- surface绘制的buffer是怎么来的?
通过GBP向BufferQueue申请 - buffer绘制完了是怎么提交的
通过GBP向BufferQueue提交
VSync信号机制
VSync信号的生成
硬件生成(HWComposer)/软件生成(VSyncThread),但是都是通过统一的接口回调到上层DispSyncThread。
分发机制:
硬件生成(HWComposer)/软件生成(VSyncThread)的信号通过接口回调给工作线程DispSyncThread,把它分发一分为二,分成两路进行分发(app-EventThread/sf—EventThread),分别通知app去绘制UI和通知sf去对应用生成对图像进行合成渲染。同时两者会有一定的错峰偏移,防止抢占CPU。
分发流程:
- VSync信号会唤醒EventThread线程
- 唤醒之后,把vsync信号通过注册的Connection分发出去
- 每个Connection会有两个描述符(mSenderFd,mReceiverFd),发送是往mSenderFd里写数据,对应的SF/App会监听EventThread的写事件,然后从mReceiverFd获取数据。
线程通信相关
线程的消息队列创建原理
先回忆下Handle、Looper、MessageQueue和线程的关系:
流程
消息队列创建其实就是MessageQueue
的创建,Handle机制就不需要在细说了。
- 在ActivityThread的main方法,调用
prepareMainLooper
方法 - 再调用
prepare(false)
方法,其参数quitAllowed表示当前Looper是否能退出,主线程是false,不能退出的 - 创建Looper,ThreadLocal与Looper进行绑定操作
- Looper构造里面创建java层的MessageQueue
- MessageQueue构造里调用
nativeInit()
方法
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
- 查看native层方法nativeInit:
///frameworks/base/core/jni/android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
//转换为jni类型,将内存地址返回
return reinterpret_cast<jlong>(nativeMessageQueue);
}
创建native层的NativeMessageQueue
:
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
调用Looper::getForThread()
从获取当前环境变量的looper,没有则创建一个native层的Looper
。
sp<Looper> Looper::getForThread() {
int result = pthread_once(& gTLSOnce, initTLSKey);
LOG_ALWAYS_FATAL_IF(result != 0, "pthread_once failed");
return (Looper*)pthread_getspecific(gTLSKey);
}
getForThread
获取当前线程额TLS,继续看Looper
的构造:
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
AutoMutex _l(mLock);
//重建epoll事件
rebuildEpollLocked();
}
先理解 eventfd,eventfd是Linux 2.6提供的一种系统调用,它可以用来实现事件通知。eventfd包含一个由内核维护的64位无符号整型计数器,创建eventfd时会返回一个文件描述符,进程可以通过对这个文件描述符进行read/write来读取/改变计数器的值,从而实现进程间通信。
跟进rebuildEpollLocked
方法:
void Looper::rebuildEpollLocked() {
//分配新的epoll实例并注册wake管道。
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
//监听可读事件(EPOLLIN)
eventItem.events = EPOLLIN;
//监听mWakeEventFd的读写事件
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
......
}
创建一个epoll实例,并添加一个mWakeEventFd
的可读监听事件。
native层
怎么监听的了?在MessageQueue
里插入一条新消息后,会调用enqueueMessage
方法,从而调用到native的wake
方法:
在wake()方法中会向mWakeEventFd写入数据。
没有消息时,会调用nativePollOnce
方法进入阻塞,再查看nativepollOnce方法,实现在native层Looper的pollOnce
方法:
,而pollOnce
会监听这个mWakeEventFd
是否可读。查看pollnner
方法:
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
if (result != 0) {
return result;
}
result = pollInner(timeoutMillis);
}
}
pollOnce
也是个死循环,核心方法pollInner
,会返回一个result值,当result不等于0时,就会跳出循环,返回到java层。查看pollnner
方法:
第一部分:
epoll_wait
是重点方法,这里会得到一段时间内(结合消息计算得来的)收到的事件个数,这里对于queue来说就是空闲(阻塞)状态。也不会下面的第二部分代码的执行了,会这里休眠。
遍历fd描述符,如果等于mWakeEventFd并且当前event是写事件,则会调用awoken()
方法。其作用不断的读取管道数据,直到清空管道。
接着第二部分:处理native层消息,并返回result给上层,判断上层是否继续堵塞。
//system/core/libutils/Looper.cpp
int Looper::pollInner(int timeoutMillis) {
//...
//事件集合(eventItems),EPOLL_MAX_EVENTS为最大事件数量,它的值为16
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
//1、等待事件发生或者超时(timeoutMillis),如果有事件发生,就从管道中读取事件放入事件集合(eventItems)返回,如果没有事件发生,进入休眠等待,如果timeoutMillis时间后还没有被唤醒,就会返回
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
//获取锁
mLock.lock();
//...省略的逻辑是:如果eventCount <= 0 都会直接跳转到Done:;标记的代码段
//遍历事件集合(eventItems),检测哪一个文件描述符发生了IO事件
for (int i = 0; i < eventCount; i++) {
//取出文件描述符
int fd = eventItems[i].data.fd;
//取出事件类型
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeEventFd) {//如果文件描述符为mWakeEventFd
if (epollEvents & EPOLLIN) {//并且事件类型为EPOLLIN(可读事件)
//这说明当前线程关联的管道的另外一端写入了新数据
//调用awoken方法不断的读取管道数据,直到清空管道
//第一部分
awoken();
} else{...}
} else {//如果是其他文件描述符,就进行它们自己的处理逻辑
//...
}
}
//第二部分:下面是处理Native的Message
Done:;
mNextMessageUptime = LLONG_MAX;
//mMessageEnvelopes是一个Vector集合,它代表着native中的消息队列
while (mMessageEnvelopes.size() != 0) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
//取出MessageEnvelope,MessageEnvelop有收件人Hanlder和消息内容Message
const MessageEnvelope& messageEnvelope = mMessageEnvelopes.itemAt(0);
//判断消息的执行时间
if (messageEnvelope.uptime <= now) {//消息到达执行时间
{
//获取native层的Handler
sp<MessageHandler> handler = messageEnvelope.handler;
//获取native层的消息
Message message = messageEnvelope.message;
mMessageEnvelopes.removeAt(0);
mSendingMessage = true;
//释放锁
mLock.unlock();
//通过MessageHandler的handleMessage方法处理native层的消息
handler->handleMessage(message);
}
mLock.lock();
mSendingMessage = false;
//result等于POLL_CALLBACK,表示某个监听事件被触发
result = POLL_CALLBACK;
} else {//消息还没到执行时间
mNextMessageUptime = messageEnvelope.uptime;
//跳出循环,进入下一次轮询
break;
}
}
//释放锁
mLock.unlock();
//...
}
如果当前没有消息处理,则还是会处于堵塞状态,让出CPU占用进入休眠等待。
总结:
补充问题
- 可以在子线程创建handler么?
可以,先调用Looper.prepare()即可,要注意使用 Looper.loop()和quit()方法。 - 主线程的Looper和子线程的Looper有什么区别?
子线程的Looper可以退出。 - Looper和MessageQueue有什么关系?
一个线程有一个Looper,对应一个MessageQueue,一个looper可以对应多个Handler,每个Handler都可以往MessageQueue插入消息,MessageQueue可以根据target分到不同的Handler。 - MessageQueue是怎么创建的?
Java层的MessageQueue在创建的时候就会调用一个native函数去创建Native层的MessageQueue。
android线程间消息传递机制
分发消息
先看handle如何分发消息:
这个callback就是调用post(Runnable)
方法时设置的:
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
如果没有全局Callback就会执行handleMessage(msg)
方法。
发送消息
其中nativeWake
唤醒处于堵塞状态的MessagQueue的next()
方法。
线程间传递
handler的消息延时实现
要分析两个位置:消息入队时候跟进延迟插入链表、取消息时是否有可用的消息(可能当前时间节点,延迟消息还没到)。
消息入队
通常我们通过sendMessageDelayed发送,或者postDelayed发送消息的。
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
会调用sendMessageAtTime
方法,第二个参数为 当前系统开机后时间+延迟时间 的值。
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
然后调用MessageQueue的enqueueMessage
方法:
boolean enqueueMessage(Message msg, long when) {
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
首先要了解Message
数据结构是一个时间纬度的单链表。
分析:
p == null || when == 0 || when < p.when
如果p == null
表示当前链表为空,直接赋值设置头节点;如果when == 0
表示这个消息是要插入头部的,这里是调用Handler.sendMessageAtFrontOfQueue
方法的传的0,然后将传入msg设置为头节点。when < p.when
表示传入msg的执行时间比当前msg更提前,所以设置为头节点。
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) {
MessageQueue queue = mQueue;
return enqueueMessage(queue, msg, 0);
}
- 处理else语句,启动一个死循环,插入节点。
传入的节点when 小于 next节点的when,则说明找到插入位置,或者next节点为空,跳出循环执行msg.next = p; prev.next = msg;
插入操作。否则next继续指向下一个节点msg。 - 判断是否有消息入列,有则执行
nativeWake
函数。
取消息
看MessageQueue.next()
方法:
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
//第一次进来 是未堵塞的,如果当前消息队列没消息就会堵塞
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
//获取当前开机后时间
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
//表示屏障挡住了,取下一跳消息了
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
}
}
分析:
nextPollTimeoutMillis
第一次for循环先是未堵塞的,如果当前无消息,则会设置为非0,下次for循环就会堵塞。- 有消息,如果当前时间 小于 当前消息的when,即表示当前消息队列所有消息还没到达执行时间(头节点执行是最先的),则计算超时时间。
- 有消息,并且头部消息延迟时间已经到了,返回当前msg,并移除,将mMessages赋予下个消息。
- 然后进入下次for循环。
上面还有个重点在native层,就是超时时间nextPollTimeoutMillis
,记得前面提到过的 int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
中,timeoutMillis
就是nextPollTimeoutMillis
,这个epoll_wait
方法,如果再没有新的消息唤醒情况下,会在超时时间之后自动唤醒。
idleHandler的原理
介绍与使用
介绍
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
回调接口,用于发现线程何时将阻塞等待更多消息。而queueIdle()
回调表示:当消息队列中的消息用完时调用,现在将等待更多。返回true可使您的空闲处理程序保持活动状态,返回false 使其被删除。如果队列中仍有消息待定,但是所有消息都计划在当前时间之后发送,则可以继续调用此方法。
官方解释已经很清楚了,通俗点讲就是 消息队列空闲时候回调。
空闲分成无消息和消息还没到触发条件两种情况。
使用
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
...
//处理事情
...
return false;
}
});
其中返回值,true表示下次空闲还会回调这里;false表示仅执行一次回调。
原理
public void addIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.add(handler);
}
}
这里就是IdleHandler
列表添加一个IdleHandler。在那用的了?
还是看Message.next()
方法:
Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
//从这里开始:就是处理Idle部分逻辑了
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
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(TAG, "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;
}
}
- 从
if (pendingIdleHandlerCount < 0... )
开始看起,上面是处理消息分发的,看这个判断逻辑就是暂且无消息或者消息触发条件未到,处于空闲状态了。 - 在执行
pendingIdleHandlerCount = mIdleHandlers.size();
赋值,这个mIdleHandlers
就是我们添加addIdleHandler的list列表。 - 如果pendingIdleHandlerCount还是默认值或者size为0,就跳出。
- 大于0,继续处理
IdleHandler
逻辑。初始化临时IdleHandler[]
的数组,并赋值为mIdleHandlers
的值。 - 开启
IdleHandler[]
数组的遍历,取出IdleHandler
实例,调用queueIdle()
回调,如果返回true就表示下次还会回调,返回false就从mIdleHandlers
的列表中移除,下次空闲不再回调。
应用
启动优化
启动优化可以使用IdleHandler
来利用特性,避免占用页面绘制。这样会在页面绘制完后,空闲下来再处理一些事情,当然这里queueIdle()
要返回false,只执行一次。
批量任务
存在一些任务密集、只关注最终结果的场景。
主线程进入loop循环为啥没ANR?
从几个方面回答:
- ANR是应用没有在规定的时间内完成AMS指定的任务,跟Looper的for循环没有必然的联系。
- AMS请求调到应用端binder线程,再丢消息去唤醒主线程处理,理解mWakeEventFd,nativePollOnce了(epoll_waite)。
- ANR不是因为主线程loop循环,而是因为主线程中有耗时任务。
消息屏障
特点
消息是可以分成三类:普通消息、消息屏障、异步消息
一般我们使用的都是普通消息,而异步消息跟普通消息相比多了异步标志位setAsynchoronous。
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}
消息屏障特点:
- 没有target(Handler) --> 通过有没有handler判断是否是消息屏障,普通消息需要handler发送的。
- 带有时间戳,只会影响它后面的消息
- 可以插多个消息屏障
- 插入消息屏障没有唤醒线程
- 插入消息屏障后会返回token(屏障序列号,累加),移除消息屏障的时候用,移除的时候可能要唤醒线程(唤醒条件:线程是因为消息屏障block住的,且该屏障后面没有跟着一个屏障。
- 私有的,非必要不要使用。
各种情况
在Message.next()
里,消息屏障只会block住普通消息,不会block住异步消息。
- 如果第一条消息就是屏障,就往后遍历,看看是否有异步消息。
- 如果没有异步消息,就无限休眠,等待被别人唤醒。
- 如果有异步消息,就看离这个消息触发还有多久,设置一个超时,继续休眠。
有消息屏障的时候插入消息:
- 插入普通消息:如果插入到队列头,当前线程数休眠的,则会唤醒线程。
- 插入异步消息,如果当前消息是最早的一条异步消息,且队列头是屏障,且线程是休眠的,唤醒线程(根据时间是否到了设置)
相关
这个跟UI的绘制有关,看下图:
Choreographer里的scheduleTraversals(),把屏障后面的普通消息block住,让界面绘制的异步消息优先执行,再执行完后移除消息屏障。
消息屏障会block住普通消息,给异步消息开通个绿色通道,让异步消息优先执行(比如界面绘制、输入输出事件分发等等比较紧急的消息)
进程通信相关
framework的跨进程方式
管道
-
半双工的、单向的
半双工是指数据传输指数据可以在一个信号载体的两个方向上传输,但是不能同时传输。
管道数据流只能一个方向流、要么读要么写。需要双方通信时,需要建立起两个管道;
Linux提供了pipe(fds)可以生成一堆描述符:读、写。 -
一般在父子进程之间或者兄弟进程之间使用
父进程向子进程写数据情景:
socket通信
- 全双工的,既可读又可写
通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合。全双工指可以同时(瞬时)进行信号的双向传输(A→B且B→A)。指A→B的同时B→A,是瞬时同步的。 - 两个进程之间无需存在亲缘关系
应用:
在Zygote进程的启动类ZygoteInit.main
方法中:通过socket接受AMS进程的请求,来启动应用进程。
public static void main(String argv[]) {
try {
...
registerZygoteSocket(socketName);
...
runSelectLoop(abiList);
....
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (RuntimeException ex) {
closeServerSocket();
throw ex;
}
}
通过registerZygoteSocket(socketName)
创建了本地socket,进入runSelectLoop
循环来检测socket是否有新过来的链接或者数据。
private static void runSelectLoop(String abiList) throws MethodAndArgsCaller {
ArrayList<FileDescriptor> fds = new ArrayList<FileDescriptor>();
ArrayList<ZygoteConnection> peers = new ArrayList<ZygoteConnection>();
// 将 zygote socket 的 fd加入数组
fds.add(sServerSocket.getFileDescriptor());
peers.add(null);
// 这里使用到了linux的poll机制
while (true) {
StructPollfd[] pollFds = new StructPollfd[fds.size()];
for (int i = 0; i < pollFds.length; ++i) {
pollFds[i] = new StructPollfd();
pollFds[i].fd = fds.get(i);
pollFds[i].events = (short) POLLIN;
}
try {
//poll是来检测是否有事件发生的
Os.poll(pollFds, -1);
} catch (ErrnoException ex) {
throw new RuntimeException("poll failed", ex);
}
for (int i = pollFds.length - 1; i >= 0; --i) {
//采用I/O多路复用机制,当客户端发出连接请求或者数据处理请求时,跳过continue,执行后面的代码
if ((pollFds[i].revents & POLLIN) == 0) {
continue;
}
if (i == 0) {
//表示zygote socket中有连接到来,那么就要创建客户端连接
// 实际上就是执行socket accept操作
// 然后把创建的这个连接也加入到监听数组中,索引肯定大于0了
// 那么一旦这个连接中有数据来了,i肯定大于0
ZygoteConnection newPeer = acceptCommandPeer(abiList);
peers.add(newPeer);
fds.add(newPeer.getFileDesciptor());
} else {
//调用ZygoteConnection.runOnce()处理客户端数据事务
boolean done = peers.get(i).runOnce();
// 处理完之后,断开连接,并且不在监听这个连接
if (done) {
peers.remove(i);
fds.remove(i);
}
}
}
}
}
共享内存
- 很快,不需要多次拷贝
- 进程之间无需存在亲缘关系
应用:
对于Android主要使用的方式是匿名共享内存Ashmem(Anonymous Shared Memory),跟原生的不太一样,比如它在自己的驱动中添加了互斥锁,另外通过fd的传递来实现共享内存的传递。MemoryFile是Android为匿名共享内存而封装的一个对象,也是进程间大数据传递的一个手段。
信号
- 单向的,发出去之后不管别人怎么处理
- 只能带个信号,不能带别的参数
- 知道进程pid就能发信号了,也可以一次给一群进程发信号
特点很像广播机制。
应用:
Binder的理解
Binder作用
binder 是 Android 中主要的跨进程通信方式,binder 驱动和 service manager 分别相当于网络协议中的路由器和 DNS,并基于 mmap 实现了 IPC 传输数据时只需一次拷贝。
特点
- 性能
基于 mmap 实现了 IPC 传输数据时只需一次拷贝。 - 方便
逻辑直接简单。 - 安全
binder声明信息只能在内核态添加的,而Socket方式的IP地址是开放的,管道方式拿到匿名管道的管道名称就可以往里面写数据。
binder通信架构
注意只有系统服务才能注册到ServiceManager,需要权限验证。
回顾启用binder机制:
打开binder驱动、内存映射、分配缓冲区、启动binder线程
完整的IPC通信流程
分层架构:
通信过程
binder对象跨进程原理
在publishBinder
里面,是通过writeStrongBinder
方法,将callbac写入
在onTransact
方法里面,可以看出是通过Parcel
来读写binder的
-
java层
通过android.os.Parcel进行跨进程传输,通过writeStrongBinder写到Parcel里,目标进程通过readStrongBinder从Parcel中读出来 -
Native层
binder以flat_binder_object形式存储在Parcel缓冲区里,Parcel有个数组专门保存其偏移,所以到目标进程,就可以根据这个偏移还原出flat_binder_object(所谓的一次拷贝) -
驱动层
Parcel传到binder驱动里之后,这个驱动根据flat_binder_object里的binder对象创建一些数据结构,包括binder_node(实体对象)和传给其他进程的binder_ref(引用对象),一个binder实体对象可能会对应多个binder引用对象
往上目标进程根据binder_ref的handle创建BpBinder(同一个进程直接返回)
由BpBinder再往上到BinderProxy到业务层的Proxy。
binder的oneway机制
带oneway和不带oneway的接口(普通aidl接口)区别:是否需要等待回复
注意点:oneway的接口,函数默认是没有返回值的,带上编译会出错。oneway重点放在了Client端。
特点:
- oneway是异步的,就算应用端处理非常耗时也不会阻塞系统服务
- oneway是串形化的,系统服务是一个一个的把binder调用分发给应用端