慕课网 剖析framework 笔记
6-1 说说android的UI刷新机制
这个和界面优化有关系,卡顿会影响用户体验,
理解UI刷新机制对解决问题有帮助的
问题:
1,丢帧是什么原因引起的?
2,Android的刷新频率是60帧/s,是每隔16ms就调用onDraw绘制一次?
3,onDraw之后屏幕会马上刷新吗?
4,如果界面没有重绘,还会每隔16ms刷新屏幕嘛》?
5,如果屏幕块刷新时才去onDraw绘制,会丢帧吗?
AP从系统拿到buffer,画完后返回给系统,
系统服务把buffer写到缓冲区,屏幕以一定频率刷新,每次刷新读缓冲区并显示,
如果缓冲区没有新数据,屏幕就一直使用老的数据。
屏幕的图像缓存不只有一个,如果只有一个,一边读一边写,会导致tearing,一般第一帧,一半第二帧,
所以一般有2个或者以上,一直swap就行了
下个问题,AP端什么时候开始绘制
屏幕每次收到vsync就从buffer拿数据显示,
绘制 是AP端发起的,随时可能发起,
所以1st脉冲显示0帧图像,2nd脉冲显示1st帧图像,
第三个时钟周期还是显示1st帧,因为2nd帧还没有画完,没画完不是因为它画的时间长,是因为它画的太晚了,vsync快来才画。
如果这种现象发生的非常频繁,用户就能感觉到,界面非常卡顿。
优化:
如果绘制也和显示一个节奏就行了,
每帧图像的绘制都控制在16ms以内,就行了,
但是有一个问题,AP层的View的重绘调RequestLayout,
这个函数什么时刻都可以调用。,怎么控制它真正绘制的时机?? : Choreographer
Android怎么处理的、?关键是一个类:Choreographer,舞蹈指导,
你往里发一个消息,它最快也要下一个vsync来的时候触发,
如,绘制随时发起,封装一个Runnable,丢个Choreographer,下个vsync来,处理这个消息,开始界面重绘。
相当于绘制节奏完全由Choreographer控制,
看看Choreographer的原理:
从requestLayout说起,它我们比较熟,是用来发起UI重绘的,
public void requestLayout(){
...
scheduleTraversals();
}
void scheduleTraversals(){
//它没直接绘制,做了2件事情,
//1,往线程消息队列插了一个syncBarrier
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//2,往Choreographer消息队列插了一个callback
mChoreographer.postCallback(Choregrapher.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
.....
}
1,syncBarrier就是一个屏障,把屏障插入消息队列,屏障后面的普通消息就要等着,屏障撤出才可以处理,
但是屏障对异步消息没有作用,这么做的原因:
因为有些类型的消息非常紧急,要马上处理。如果消息队列里面普通消息过多,耽误事,所以插了屏障,优先处理异步消息。
这里往Choreographer丢的Runnnable就是一个异步消息,
下个 Vsync来的时候,异步消息是要紧急处理的。
2,Choreographer,是和ViewRootImpl一起创建的,
创建:
//有getInstance,是单例吗?不是
public static Choreographer getInstance(){
//他是一个ThreadLocal,ThreadLocal
//也就是说在不同的线程调用getInstance返回的是不同的Choreographer对象
return SThreadInstance.get();
}
假如有人一口气调用了10次requestLayout,那么下次vsync到来前,会引发10次UI重绘吗?
不会
void scheduleTraversals(){
//每次判bool变量,是false才进,什么时候置的false?在mTraversalRunnable => doTraversal
if(!mTraversalScheduled){
mTraversalScheduled = true;
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
....
}
}
//下次vsync来的时候执行doTraversal,里面会给bool置flase
void doTraservsal(){
if(mTraversalScheduled){
mTraversalScheduled = false;
performTraversals();
}
}
看看这个callback是怎么加入到Choreographer的
private void postCallbackDelayedInternal(int callback Type, ...){
....
//Choreographer里面有一个数组叫mCallBackQueues,数组里每个元素都是一个callback单链表
//一方面要根据callback类型插入对应的单链表
//另一方面要根据callback要执行的时间顺序排序,dueTime,越是马上发生的callback越放到链表的前面
mCallbackQueue[callbackType].addCallbackLocked(dueTime, ...);
....
scheduleFrameLocked(now);
}
private void scheduleFramelocked(long now){
//如果当前线程就是Choreographer的工作线程,直接调shceduleVsyncLocked
if(isRunningOnLooperThreadLocked()){
scheduleVsyncLocked();
}else{
//否则要发消息到Choreographer的工作线程里面去,
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
//消息是异步消息,不受到屏障的影响,
msg.setAsynchronous(true);
//消息要插入到消息队列的头,非常紧急,
//因为要告诉SF,下一个vsync来时,第一时间通知我们,因为如果错过这个vysnc,就又要等一个周期
mHandler.sendMessageAtFrontOfQueue(msg);
}
}
scheduleVsyncLocked后会发生什么?
是告诉SF,我们要关注下一个vsync信号了,
当下个vsync信号发生,SF通知我们,然后回调到FrameDisplayEventReciver里面的onVsync()
class FrameDisplayEventReceiver extends DisplayEventReciver implements Runnable{
@Override
//timestamNanos是vsync信号的时间戳
public void onVsync(long timestampNanos, int builtInDisplayId, int frame){
....
mTimestampNanos = timestampNanos;
mFrame = frame;
//这个消息this其实是一个runnable,就是后面的run()
Message msg = Message.obtain(mHandler.this);
mgs.setAsynchronous(true);
//发了一个消息到Choreographer的工作线程里面去了,
//封装一个消息丢出去干嘛?它不是切换工作线程,因为onVsync本身就是调到Choreographer里面的,
//这个mHandler也是,这里发消息丢出去,它带了一个时间戳,表示消息要触发的时间,这样就可以按照时间戳顺序处理消息。
mHandler.sendMessageAtTime(msg, timestampNanos/TimeUils.NANOS_PER_MS);
}
@Overide
public void run(){
//到时间了,消息被处理,就是执行doFrame
doFrame(mTimestampNanos, mFrame);
}
}
doFrame分两个阶段
//阶段1
//frameTimeNanos是这帧的时间戳
void doFrame(long frameTimeNanos, int frame){
long intendedFrameTimeNanos = frameTimeNanos;
long startNanos = System.nanoTime();
//当前时间和时间戳间隔越大,这帧处理的延迟越大
final long jitterNanos = startNanos - frameTimeNanos;
//延迟大到超过一个周期,
if(jitterNanos >= mFrameIntervalNanos){
//算算延迟了几个周期
final long skippedFrames = jitterNanos/mFrameIntervalNanos;
//如果跳过的帧数大于这个常量,就会打印这个很熟悉的日志
//主线程做的事情太多了,绘制都做不了,跳帧太多
if(skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT){
Log.i(TAG, "Skipped" + skippedFrames + "frames! " + "the appliction may be doing too much work on main thread");
}
.....
}
}
//阶段2,处理callback,
//calback有4中类型,每种对应一个callback链表
void doFrame(long frameTimeNanos, int frame){
....
doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
doCallbacks(CHoreographer.CALBACK_ANIMATION, frameTImeNanos);
doCAllbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
doCallBacks(Choreographer.CALLLBACK_COMMIT, frameTimeNanos);'
}
//执行对应的doCallbacks,
//callback有时间戳,到了时间才执行回调
void doCallbacks(int callbackType, long frameTimeNanos){
CallbackRecord callbacks;
//extractDueCallback作用就是从callbackQueue取出到了时间的callbacks,
callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(...);
//for循环执行链表中的callback的run函数
for(CallbackRecode c = callbacks; c != null, c = c.next){
c.run(frameTimeNanos)
}
}
看看scheduleTraversals的时候传的callback是什么样的
//准备绘制,
void scheduleTraversals(){
if(!mTraversalScheduled){
mTraversalScheduled = true;
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
....
}
}
//mTraversalRunnable,最后调的就是doTraversal() => performTraversals()
final class TraversalRunnbale implements Runnable{
@Override
public void run(){
doTraversal(); // => performTraversals,真正绘制的地方
}
}
总结:
1,AP层的View调用requestLayout要求重绘,
2,其中是new了一个Runnable丢到Choreographer的消息队列,
3,Choreographer没马上处理消息,它是通过requestNextVsync向SurfaceFlinger请求下一个vSync信号,
4,SF会在下个vsync来的时候,通过postSyncEvent向choreographer发送一个通知,
5,choreographer收到通知之后就会处理消息队列里面的消息
6,之前的requestLayhout对应的Runnable里面执行的就是performTraversal,真正的执行绘制
下面看看scheduleVsync的实现
private void scheduleVsyncLocked(){
//会调到native的DisplayEventReceiver的shceduleVsync函数
mDisplayEventReceiver.scheduleVsync();
}
status_t NativeDisplayEventReceiver::scheuleVsync(){
status_t status = mReceiver.requestNextVsync();
}
status_t DisplayEventReceiver::requestNextVsync(){
//requestNextVsync,请求下一个vsync,
//mEventConnection对象怎么创建的?在DisplayEventRecevier的构造函数创建的
mEventConnection->requestNextVsync();
}
DisplayEventReceiver::DisplayEventReceiver(){
//拿到SF的binder句柄:ISurfaceComposer
sp sf(ComposerService::getComposerService());
if(sf != NULL){
//得到mEventConnection,这是另外一个binder句柄,
//这种套路很常见,拿到系统服务binder句柄后,要么openSeesion,要么CreateConnection,总之要单独弄一个通道。
mEventConnection = sf->createDisplayEventConnection();
//Channel是connection创建时new的一个BitTube对象
mDataChannel = mEventConnection->getDataChannel();
}
}
sp EventThread::Connection::getDataChannel() const{
return mChannel;
}
//这种套路很常见,拿到系统服务binder句柄后,要么openSeesion,要么CreateConnection,总之要单独弄一个通道。
//Channel是connection创建时new的一个BitTube对象
BitTube其实就是两个描述符,通过socketpair创建
mSendFd
mReceiverFd
机制:
和管道有点像,如果有个人拿到了读Fd-ReceiverFd,阻塞在这里,另一个人拿到SendFd 写Fd时,ReceiverFd就会被唤醒。
socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)
看看connection如何创建的
//它的实现在SF进程里面
sp SurfaceFlinger::createDisplayEventConnection(){
//eventThread是一个线程:等待、处理event
return mEventThread->createEventConnection();
}
sp EventThread::createEventConnection() const{
//new Connection and return
return new Connection(const_cast(this));
}
EventThread::Connection::Connection(const sp&eventThread)
//new BitTube
:count(-1), mEventThread(eventThread),mChannel(new BitTube()){}
void EventThread::Connection::onFirstRef(){
//向EventThread注册自己,这样EventThread有event的时候,就可以分发connection,
//connection又调到我们应用进程里面
mEventThread->registerDisplayEventConnection(this);
}
//看看connection是怎么注册到eventThread的
status_t EventThread::registerDisplayEventConnection(const sp & connection){
//把conection add到DisplayEventConnection列表
mDisplayEventConnections.add(connection);
//发广播,类似java的notifyAll,对应的有wait才对,
//wait是在eventThread里面。
mCondition.broadcast(); //notifyAll
}
看看EventThread是在哪里创建的
void SurfaceFlinger::init(){
//vysnc信号源,传给了EventThread
sp vsyncSrc = new DispSyncSource(&mPrimaryDispSync,..);
//创建EventThread
mEventThread = new EventThread(vsyncSrc);
...
}
//thread启动后不停的执行threadLoop
bool EventThread::threadLoop(){
DisplayEventReceiver::Event event;
Vector< spsignalConnections;
//等待事件,返回Connection列表,里面都是event
signalConnections = waitForEvent(&event);
const size_t count = signalConnections.size();
//遍历connection
for(size_t i=0; i & conn(signalConnections[i]);
//通过postEvent分发事件,典型的如vsync事件
conn->postEvent(event);
}
}
看看waitForEvent函数,这个函数很长,细节很多
Vector> EventThread::waitForEvent(..){
Vector>signalConnections;
do{
//看是否已经有vysnc信号来了,(不用关心vysnc怎么来,只关心怎么分发到应用层))
//有,就准备connection列表返回
//没有,等待vysnc
}while(signalConnections.isEmpty());
return signalConnctions;
}
//注意
void EventThread::requestNextVsync(const sp::Connection>&connection){
//connection里面有一个字段count,当count>=0时,才表示connection关注vsyncEvent,
//所以应用端调用requestNextVsync就是把count从-1变成0,
if(connection->count < 0){
connection->count = 0;
mCondition.broadcast();
}
}
当我们把connection加会signalConnections列表时,它有把count设置为-1.
这样你想接受下个vsync,就又要调一次requestNextVsync,把它重新置0
再看看事件如何分发出去
status_t EventThread::Connection::postEvent(const DispplayEeventReceiver::Event& event){
ssize_t size = DIslayEventReceiver::sendEvents(mChannel, &event, 1);
return size < 0 ? statuc_t(size):status_t(NO_ERROR);
}
ssize_t DisplayEventReceiver::sendEvnets(const sp&dataChannel, Event const* events, size_t count){
return BitTube::sendObjects(dataChannel, events, count);
}
ssize_t BitTube::snedObjects(const sp& tube,...){
const char* vaddr = reinterpret_cast(events);
ssize_t size = tube->wiret(vaddr, count *objSize);
return size < 0 ? size:size/static_cast(objSize);
}
BitTube就是socket管道,有两个fb,w/r,write就是写,那读的一端就会收到通知。
BitTube的描述符是在SF端创建的,写的一端是在SF进程,
1,那读的一端是如何传给应用进程的?
2,应用进程如何监听读FD的?
解答他们要看DislayEventRecevier的构造函数
DisplayEventRecevier::DisplayEventRecevier(){
sp sf(ComposerService::getComposerService());
mEventConnection = sf->createDisplayEventConnection();
//得到mDataChannel,它就是BitTube
mDataChannel = mEventConnection->getDataCannel();
}
//看看BitTube如何跨进程传递
//应用端拿到Connection的proxy端,getDataCannel把请求transact出去,
virtual sp getDataChannel() const {
Parcel data, reply;
data.writeInterfaceToken(IDisplayEventConnection::getInterfaceDescriptor());
remote()->trascat(GET_DATA_CHANNEL, data, &reply);
//BitTube根据reply把描述符还原出来,
return new BitTube(reply);
}
//SF收到getDataChannel请求,会返回mChannel,Channel就是bitTube,
//Channel放到parcel,跨进程传递给应用进程,放到reply里面
sp EventThread::Connection::getDataChannel() const{
return mChannel;
}
问题2,AP进程如何监听BitTube读事件?
要从Choreographer的构造函数说起,
private Choreographer(Looper looper){
...
//如果用vsync信号,就会创建DisplayEventReceiver,FrameDisplayEventReceiver继承了DisplayEventReceiver,
mDisplayEventReceiver = new FrameDisplayEventReceiver(looper);
}
public DisplayEventReceiver(Looper looper){
....
//构造函数调到了nativeInit函数,它是native的函数,
mRecevierPtr = nativeInit(new WeakReference(this),...);
}
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject recevierWeak,...){
....
//native层,创建一个NativeDisplayEventReceiver对象
sp recevier = new NativeDisplayEventRecevier(...);
//看看initialize
status_t status = recevier->initialize();
return reinterpret_castgetLooper()->addFd(mRecevier.getFd(), 0, Looper::EVENT_INPUT, this, NULL);
return OK;
}
DisplayEventReceiver::DisplayEventReceiver(){
//拿到SF的binder句柄:ISurfaceComposer
sp sf(ComposerService::getComposerService());
if(sf != NULL){
//得到mEventConnection,这是另外一个binder句柄,
//这种套路很常见,拿到系统服务binder句柄后,要么openSeesion,要么CreateConnection,总之要单独弄一个通道。
mEventConnection = sf->createDisplayEventConnection();
//Channel是connection创建时new的一个BitTube对象
mDataChannel = mEventConnection->getDataChannel();
}
}
int DisplayEventRecevier::getFd() const{
//前面讨论过,DataChannel就是BitTube
return mDataChannel->getFd();
}
//BitTube有两个描述符,发送和接收,这里返回的是接收的,
//发送的Fd在SurfaceFlinger,
int BitTube::getFd() const{
return mReceiveFd;
}
看看getlooer()->addFd,Fd如何添加到looper的,
int Looper::addFd(int fd, int ident, int events, ...){
Request request;
request.fd = fd;
....
//new了epoll_event,加入epoll
struct epoll_event eventItem;
request.initEventItem(&eventItem);
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem);
//把request加到mRequest列表
mRequests.add(fd, request);
}
看看Looper如何检测FD的读事件
int Looper::pollnner(int timeoutMillis){
...
//epoll_wait返回后在for里处理事件
int eventCount = epoll_wait(mEpollFd, eventItems,...);
//有两种事件,
for(int i = 0; i< eventCount; i++){
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
//1,消息队列的事件
if(fb == mWakeEventFd){
...
}else{//2,其他fd事件,
if(epollEvents & EPOLLIN) events |= EVENT_INPUT;
//放到response列表,for结束后,统一处理responses
pushResponse(events, mRequestl.valueAt(requestIndex));
}
}
//统一处理Responses
for(size_t i = 0 ; i < mResponses.size(); i++){
Response& response = mResponsese.editItemAt(i);
int fd = response.request.fd;
intevents = response.evdnts;
void *data = responose.request.data;
//调用callback的handleEvent,返回值很重要,
int callbackResult = response.request.callback->handleEvent(fd, events, data);
if(callbackResult == 0){
//返回0则删除这个fd
removeFd(fd, response.request.seq);
}
response.request.callback.clear();
result = POLL_CALLBACK;
}
return result;
}
我们添加的BitTube的Fd的回调是什么?
int NativeDisplayEventRecevier::handleEvent(int receiveFd, int events,...){
//把SF发的event读进来,
if(processPendingEvents(&vsyncTimestamp, &vsyncDisplayId, ...)){
mWaitingForVsync = false;
//分发vysnc,
dispatchVsync(vsyncTimestamp, vsyncDisplayId, vysncCount);
}
//返回1,Fd会一直被Looper监听,不会删除掉。
return 1;
}
void NatvieDisplayEventRecevier::dispatchVsync(nsecs_t timestamp,...){
//jni调用,调到java层的dispatchVysnc
env->CallVoidMethod(recevierObj.get()),gDisplayEventReceiverClassInfo.dispatchVsync, timestamp,...);
}
void displatchVsync(long timestampNanos, int builtInDisplayId, int frame){
//前面讲过,是处理Choreographer的callback的
onVysnc(timestampNanos, builtInDisplayId, frame);
}
回到问题:
1,丢帧原因? mainthread有耗时操作,耽误了View的绘制
2,android的刷新频率是60帧/s,那每隔10ms调onDraw绘制一次?
60是vsync的频率,但不是每个vsync都发起绘制,需要AP端主动发起重绘,才会向SF请求接收Vsync信号,才会再下个vsync来的时候绘制。
3.onDraw后屏幕立刻刷新? 不是,下一个vsync来的时候花心
4,如果界面没有重绘,还会每隔17ms刷新屏幕? 会的,只是数据一直用的旧的
5,如果快刷新时才绘制会丢帧吗?
View重绘不会被马上执行,都是下个vsync来才开始的,所以何时发起重绘没什么关系
总结,述说Android的UI刷新机制,如何回答:
1,vsync的原理
2,Choreographer的原理
3,UI刷新的大致流程,应用和SurfaceFlinger的通信过程。