作业时最开端触摸的便是AMS和WMS,真实作业和学习还是有很大差异的,在作业中咱们一直作为一颗螺丝钉来support某一个局域的功能,但学习又是全体的,咱们没办法脱离上下文去学习或运用某一个局部的东西,这个道理和Android 中的Context也是很像的,脱离了Context咱们的学习就像无根之水,不知道为何学习,也不知道怎么运用。
在刚开端学习View的时分,还停留在一步一步加log看源码的阶段,在这个阶段整个人其实非常疑惑,总觉得View,直白点说,便是measure,layout和draw呗,丈量下尺度,找个相对方位,画出来就完事了。但处理问题往往没有这么简略,比方为什么黑屏、白屏,为什那些软件层的显现问题需求从View的视点去帮忙剖析处理?为什么不是WMS?View到底是怎样onDraw?它和GPU,CPU有啥联络?surface又是什么东西,运用层写进去的xml,到底是怎样显现到屏幕上的?
在了解这篇之后,后边我会再整理一份显现反常剖析思路,就非常简略了解了。 本篇是我从事这方面的作业和学习以来,依据自己的领会整理出来的一些根底结构,先对Android 烘托架构有个整理的认识,然后再去学习其间某一个子模块,就能做到知其然,知其所以然了。
一、根底概念扫盲
1. 屏幕硬件的显现原理
假如有一定硬件根底或许嵌入式根底,应该很好了解这儿的显现原理,屏幕由一个个的像素点组成,简化来看实践便是二极管嘛,操控它通断就好了。了解一下什么叫硬件,什么叫驱动。 打个简略点的比方,咱们做单片机开发应该也会触摸到二极管,数位管,比方经过数位管去显现一个1
咱们只需求写一个Api,在需求显现1时,咱们就经过这个Api去输出true or false给IO口,比方这儿咱们只需求给b和c 置为高电平,给其他的二极管拉低,那么就会显现一个1出来。这个数位管就叫硬件,而咱们写的Api就叫驱动。而现在市面上的显现设备也是这姿态的,分为硬件和驱动,一般硬件的厂家会自己适配驱动,这些驱动会依据接纳的数据,转换成一系列的0和1操控屏幕显现成想要的姿态。那么它接纳的数据是什么?便是Bitmap–位图。
所以咱们就知道了,只要能把想要显现的画面转换成Bitmap格局,就能够直接把这个玩意塞给屏幕驱动了,它会自己依据Bitmap去操控屏幕中的无数个晶体管,终究把这个画面显现到屏幕上。
2. Android的数据转化进程
实践上把图像转换成Bitmap,app自己就能够做完了,因为View本身便是运用层的东西,这也是为什么有时分咱们在debug进程中遇到一些黑屏问题,他人会告知你,你运用层的图送下来便是黑的,请你从运用层的视点剖析。因为Bitmap便是Android 做出来的呀,由此引出了烘托架构中的关键东西:Skia和OpenGL
这儿先不去管View的measure、layout流程了,先了解下View的onDraw
/frameworks/base/core/java/android/view/View.java
23167 public void draw(Canvas canvas) {
23168 final int privateFlags = mPrivateFlags;
23169 mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
23170
23171 /*
23172 * Draw traversal performs several drawing steps which must be executed
23173 * in the appropriate order:
23174 *
23175 * 1. Draw the background
23176 * 2. If necessary, save the canvas' layers to prepare for fading
23177 * 3. Draw view's content
23178 * 4. Draw children
23179 * 5. If necessary, draw the fading edges and restore layers
23180 * 6. Draw decorations (scrollbars for instance)
23181 * 7. If necessary, draw the default focus highlight
23182 */
23183
23184 // Step 1, draw the background, if needed
23185 int saveCount;
23186
23187 drawBackground(canvas);
23188
23189 // skip step 2 & 5 if possible (common case)
23190 final int viewFlags = mViewFlags;
23191 boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
23192 boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
23193 if (!verticalEdges && !horizontalEdges) {
23194 // Step 3, draw the content
23195 onDraw(canvas);
后边的就不去看了,这儿注释也写的很清楚,draw里边的7个步骤:
- 制作背景(drawBackground)
- 假如需求,保存当时layer用于动画过渡
- 制作View的内容(onDraw)
- 制作子View(dispatchDraw)
- 假如需求,则制作View的褪色边际,类似于暗影效果
- 制作装修,比方滚动条(onDrawForeground)
- 假如需求,制作默认焦点高亮效果(drawDefaultFocusHighlight)
而咱们只用关注其间onDraw是怎样做的
20717 protected void onDraw(Canvas canvas) {
20718 }
会发现,这儿的onDraw()奇奇怪怪的,怎样是个空的?当然要是空的,因为每一个View并没有固定的显现方式,所以View想要制作成什么姿态,当然是自己决议,所以onDraw()由各种子View自己完成,而不会在父类中完成。在ViewRoot中,创建了画布Canvas并调用了View的draw()办法,实践上draw的进程和Canvas这个类是密不行分的,虽然各个子View会自己决议要怎样draw,但终究要制作出来,意图便是要把自己的姿态转换成Bitmap,这个流程依靠于Canvas。随便找几个view的比方
110 @Override
111 protected void onDraw(Canvas canvas) {
112 super.onDraw(canvas);
113 canvas.drawRect(0.0f, 0.0f, getWidth(), getHeight(), mPaint);
114 }
81 @Override
82 protected void onDraw(Canvas canvas) {
83 if (mBitmap != null) {
84 mRect.set(0, 0, getWidth(), getHeight());
85 canvas.drawBitmap(mBitmap, null, mRect, null);
86 }
58 @Override
59 protected void onDraw(Canvas canvas) {
60 Drawable drawable = getDrawable();
61 BitmapDrawable bitmapDrawable = null;
62 // support state list drawable by getting the current state
63 if (drawable instanceof StateListDrawable) {
64 if (((StateListDrawable) drawable).getCurrent() != null) {
65 bitmapDrawable = (BitmapDrawable) drawable.getCurrent();
66 }
67 } else {
68 bitmapDrawable = (BitmapDrawable) drawable;
69 }
70
71 if (bitmapDrawable == null) {
72 return;
73 }
74 Bitmap bitmap = bitmapDrawable.getBitmap();
75 if (bitmap == null) {
76 return;
77 }
78
79 source.set(0, 0, bitmap.getWidth(), bitmap.getHeight());
80 destination.set(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(),
81 getHeight() - getPaddingBottom());
82
83 drawBitmapWithCircleOnCanvas(bitmap, canvas, source, destination);
84 }
这儿能够看到,子View重写的onDraw办法,有的简略,有的杂乱,但是都依据ViewRoot给定的画布区域,调用canvas这个对象本身来制作,那么能够了解烘托架构中canvas的意义了吧,效果便是一块画布,咱们measure的意图便是为了恳求一块画布,layout的意图是确定这个画布摆放在屏幕上的相对方位,draw便是给这块画布上面填充内容。当然依靠的也是这个画布自己的一些api。
canvas怎样把Java层的draw,转变成Bitmap的格局?便是前面说到的,Skia和OpenGL,这两个东西从烘托架构微观上来了解,能够把它们俩看成黑盒,它们俩都属于图形引擎,效果也是一样的,draw画面时调用canvas经过JNI到Native办法,然后经过Skia或OpenGL图形引擎,就能够得到Bitmap数据。
3. 改写率和帧速率
OK有了前面的根底,咱们知道页面制作能够经过App层重写onDraw()来制作,onDraw()经过canvas东西,再运用Skia或OpenGL图形引擎就能转换成Bitmap数据,咱们也知道Bitmap数据能够直接丢给屏幕驱动,然后屏幕上就会显现出这一帧Bitmap对应的图像。那么问题就来了,是不是能够App制作一张,就给屏幕驱动丢一张Bitmap,再制作一张,再给驱动丢一张Bitmap?并没有这么简略。
咱们首先要知道两个概念,改写率和帧速率。显现屏幕并不是接纳一次Bitmap就制作一次,实践上屏幕供给给外面的有三个东西,一个是存放当时帧Bitmap的区域,还有一个是缓冲区存放下一帧Bitmap区域,然后还供给了一个接口,触发这个接口时,屏幕就会用缓冲区的Bitmap替换当时的Bitmap。
这个屏幕在一分钟之内,最多能够切换多少次,便是这个屏幕的改写率。比方咱们看到许多屏幕是75HZ,144HZ,200HZ等等。
那么咱们App在制作的时分,每一秒钟能够制作多少次?也便是能够履行多少次将画面转换成Bitmap的操作?这个当然和体系核算才能有关,明显GPU核算比CPU核算快,更好的GPU核算也更快。那么一分钟能够制作多少张Bitmap,这个就叫体系的帧速率(fps),比方当时体系帧速率是60fps,120fps,便是说当时体系一分钟分别能够制作60或120张Bitmap。
假如说咱们让App直接和屏幕驱动对话,会是什么效果:
运用层在制作完Bitmap之后,不经过体系层,直接放到屏幕的缓冲区里边。这样带来的第一个问题便是叠图次序紊乱。因为当时并不是只要一个运用啊,也不是只要一个进程。很明显,咱们除了当时FocusWindow的进程,还有system_server进程嘛,还有systemUI需求制作,还有Launcher的TaskBar需求制作,咱们都是各绘各的,各自搞完了就放到Bitmap里边去,毫无次序的摆放,也就没有办法有序叠图,显现成终究想要的效果。
此外还有一个问题,便是改写率和帧速率无法匹配。比方当时屏幕1秒钟切换75次,但App只送过来30张Bitmap,那么在没有Bitmap送到的周期里,缓冲区就没有更新数据,终究显现的效果便是黑屏或许白屏;假如当时的改写率是30HZ,但帧速率达到了60或更高,也便是说App送过来的Bitmap,有许多根本没有显现出来就被丢掉了,这便是掉帧,成果便是显现的画面有卡顿。明显,要体系的考虑整个烘托架构,必须要处理改写率和帧速率相匹配的问题。
从Android 体系的视点来讲,咱们不行能为每一个不同的屏幕专门适配一套烘托体系,那么就需求在软件层做一个约定,在不知道屏幕硬件功能的情况下,经过一个体系来均衡硬件目标和软件目标,比方:
- 每分钟固定60次调用屏幕改写
- 每分钟固定制作60张Bitmap
所以就需求操控硬件驱动的改写调用频率,比方每秒改写60次的话,那么每次时刻距离便是16.66毫秒,那么就依托屏幕上一次显现的时刻,加上16.66毫秒,作为触发下一次显现切换的机遇,这个触发的脉冲信号就叫垂直同步信号(Vsync信号),Android 把操控硬件调用频率策略相关的内容都写到一个进程——surfaceflinger
二、SurfaceFlinger是什么
简略解说下SurfaceFlinger是什么,它便是操控屏幕改写的一个进程。一个运用能够有许多个window,每一个window制作的Bitmap,实践上在内存中表现为一块buffer,它是经过OpenGL或Skia图形库制作出来的,这个Bitmap在Java层的数据存储在一块Surface当中,在底层经过JNI对应到一个NativeSurface。那么SurfaceFlinger的效果便是在每一个距离16.66毫秒的Vsync信号到来时,将一切的Surface从头制作到一块Framebuffer的内存,也便是终究的Bitmap,最后SurfaceFlinger会把终究的Framebuffer交给驱动,并触发屏幕的改写,让这一帧图片显现出来。
好了,现在咱们知道,在整个烘托架构中,有surfaceflinger进程,经过按照固定周期叠图、送图和改写屏幕的操作,完成了屏幕显现速率的操控,那么体系中又是怎么操控App制作图片的速率,以及怎么让App制作图片的速率和屏幕显现速率同步呢?答案便是——Choreographer
三、Choreographer是什么
前面有简略讲过View的制作流程,那么View的制作机遇由谁来操控,又是怎么操控的呢?其实完好的View制作流程,应该从咱们了解的setContentView()开端,在onCreate中会调用setContentView,在这儿会完成对Xml文件的加载,在AMS callback回ActivityThread,进行Resume的时分,会经过WindowManager的AIDL办法addView()将一切的子View添加到ViewRootImpl里边去,然后在ViewRootImpl中,会走到requestLayout()并履行scheduleTraversals()
/frameworks/base/core/java/android/view/ViewRootImpl.java
2257 void scheduleTraversals() {
2258 if (!mTraversalScheduled) {
2259 mTraversalScheduled = true;
2260 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
2261 mChoreographer.postCallback(
2262 Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
2263 notifyRendererOfFramePending();
2264 pokeDrawLockIfNeeded();
2265 }
2266 }
mChoreographer在这儿就出现了,它调用的是postCallback办法,传进去的参数是Runnable,在这个Runnable中做的作业便是doTraversal(),终究到performTraversal()真实开端制作,再往下便是了解的performMeasure、performLayout和performDraw了。所以Choreographer是经过postCallback的方式,给出了一个Runnable来做measure、layout和draw。
也便是说,只要调到performTraversal()才会真实进行图形的制作。所以整个图像的制作进程便是先去加载Xml文件,然后把要显现的View都经过addView的方式给ViewRootImpl,并交给Choreographer来主导真实的制作流程。看看Choreographer的postCallback,层层调用终究做事情的在postCallbackDelayedInternal
/frameworks/base/core/java/android/view/Choreographer.java
470 private void postCallbackDelayedInternal(int callbackType,
471 Object action, Object token, long delayMillis) {
472 if (DEBUG_FRAMES) {
473 Log.d(TAG, "PostCallback: type=" + callbackType
474 + ", action=" + action + ", token=" + token
475 + ", delayMillis=" + delayMillis);
476 }
477
478 synchronized (mLock) {
479 final long now = SystemClock.uptimeMillis();
480 final long dueTime = now + delayMillis;
//把scheduleTraversals()时要做的action放进了一个Callback行列
481 mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
482
//这儿是一个等候机制,第一次进来的时分是会走else去发送Msg的
483 if (dueTime <= now) {
484 scheduleFrameLocked(now);
485 } else {
//MSG_DO_SCHEDULE_CALLBACK 这个msg能够在当时线程handle中查看
486 Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
487 msg.arg1 = callbackType;
488 msg.setAsynchronous(true);
489 mHandler.sendMessageAtTime(msg, dueTime);
490 }
491 }
492 }
//这个msg在当时线程会去调用scheduleVsyncLocked()
934 private void scheduleVsyncLocked() {
935 try {
936 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#scheduleVsyncLocked");
937 mDisplayEventReceiver.scheduleVsync();
938 } finally {
939 Trace.traceEnd(Trace.TRACE_TAG_VIEW);
940 }
941 }
这儿scheduleVsync()终究调用到Native层,等候垂直同步信号。DisplayEventReceiver在JNI层也有对应的完成,它的效果便是办理垂直同步信号,当Vsync到来的时分,会发送dispatchVsync,callback回JAVA层履行onVsync()告诉运用,然后才会到运用的制作逻辑。
1172 public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
1173 VsyncEventData vsyncEventData) {
//这儿发送的msg没有写内容,那么便是默认值,会调用到0
1202 mLastVsyncEventData = vsyncEventData;
1203 Message msg = Message.obtain(mHandler, this);
1204 msg.setAsynchronous(true);
1205 mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
//调用到0也便是这儿的doFrame()
1141 @Override
1142 public void handleMessage(Message msg) {
1143 switch (msg.what) {
1144 case MSG_DO_FRAME:
1145 doFrame(System.nanoTime(), 0, new DisplayEventReceiver.VsyncEventData());
1146 break;
1147 case MSG_DO_SCHEDULE_VSYNC:
1148 doScheduleVsync();
1149 break;
1150 case MSG_DO_SCHEDULE_CALLBACK:
1151 doScheduleCallback(msg.arg1);
1152 break;
1153 }
1154 }
当Vsync到来时经过handle方式去调用doFrame(),这儿面的代码看看,首要便是和帧相关的一些核算,比方这个Vsync到来的时刻和上一个比较,是不是跳过了一些帧,核算当时离上一帧有多久,修正掉帧等等操作。假如当时的时刻不满足,会重复恳求下一次Vsync信号。doFrame()里边正常走下去到制作流程,调用run办法,就回到了上面说到的doTraversal()
所以Choreographer的首要效果便是协调Vsync信号和核算跳帧情况,然后断定时刻是否契合标准,假如不契合,不进行callback中的doTraversal(),持续恳求Vsync,假如契合,就会开端跑烘托,到doTraversal()持续走View的流程。
其间协调Vsync信号首要是经过DisplayEventReceiver这个重要东西来恳求,或等候它告诉Vsync,而对跳帧情况的核算和回调烘托流程,是在Java层做的。
四、Android 烘托流程
整理一下根本流程和几个重要进程的效果,首先是在onResume时addView到ViewRootImpl,然后经过Choreographer东西,向底层恳求或等候底层回调的Vsync信号,当Vsync合适的时分才会履行自己的callback正式开端制作,制作的流程在各子View重写的onDraw()中,重要东西是Canvas,经过Canvas与Skia或OpenGL图形库对话,生成Bitmap,整个制作流程以unlockCanvasAndPost()作为终点,告诉surfaceflinger当时页面现已制作好了。
Surface是别的一条路,Surface在JAVA层由WMS办理,能够将Surface了解成一块区域,这块区域也便是一块内存,对应的画面便是Bitmap的内容,因为Bitmap依靠于Skia or OpenGL图形库,而这两个库是在C环境下完成的,所以framework的Surface需求经过JNI层的Surface来与Bitmap建立联络。
五、画面卡顿发生的原因
基于这个烘托架构,咱们知道在画面显现进程中,运用层每秒钟会生产60帧图,屏幕也会每秒钟改写60张图,那么画面卡顿就很好了解了,肉眼可见的卡顿根本便是掉帧,掉帧越多卡顿也就越明显,总的来说便是当时体系的制作速率,跟不上屏幕的改写速率。那么当然和CPU、GPU的才能有关,比方CPU loading高,得不到满足的时刻片来完成制作相关的流程。
此外运用自身的问题,也可能会导致自己画面卡顿。假如是体系状态良好,但唯独这个运用自己卡顿的情况,咱们还是从原理来了解,那么原因一定是这个app自己制作流程调用的慢,会慢在哪里呢?当然会有许多种可能性,比方加载的View或动画太杂乱,增加了制作的时刻;比方这个进程自己的主线程或UI线程卡住。
比方咱们在Android Handler中,Google主张不要在Handler中处理杂乱函数,保证Handle线程的效率,假如Handler线程阻塞导致慢了,那么Handle处理msg当然也会慢,在Choreographer和制作流程,许多都是依靠于Handler处理。