作业时最开端触摸的便是AMS和WMS,真实作业和学习还是有很大差异的,在作业中咱们一直作为一颗螺丝钉来support某一个局域的功能,但学习又是全体的,咱们没办法脱离上下文去学习或运用某一个局部的东西,这个道理和Android 中的Context也是很像的,脱离了Context咱们的学习就像无根之水,不知道为何学习,也不知道怎么运用。

在刚开端学习View的时分,还停留在一步一步加log看源码的阶段,在这个阶段整个人其实非常疑惑,总觉得View,直白点说,便是measure,layout和draw呗,丈量下尺度,找个相对方位,画出来就完事了。但处理问题往往没有这么简略,比方为什么黑屏、白屏,为什那些软件层的显现问题需求从View的视点去帮忙剖析处理?为什么不是WMS?View到底是怎样onDraw?它和GPU,CPU有啥联络?surface又是什么东西,运用层写进去的xml,到底是怎样显现到屏幕上的?

在了解这篇之后,后边我会再整理一份显现反常剖析思路,就非常简略了解了。 本篇是我从事这方面的作业和学习以来,依据自己的领会整理出来的一些根底结构,先对Android 烘托架构有个整理的认识,然后再去学习其间某一个子模块,就能做到知其然,知其所以然了。

一、根底概念扫盲

1. 屏幕硬件的显现原理

假如有一定硬件根底或许嵌入式根底,应该很好了解这儿的显现原理,屏幕由一个个的像素点组成,简化来看实践便是二极管嘛,操控它通断就好了。了解一下什么叫硬件,什么叫驱动。 打个简略点的比方,咱们做单片机开发应该也会触摸到二极管,数位管,比方经过数位管去显现一个1

Android GUI扫盲,渲染架构浅析

咱们只需求写一个Api,在需求显现1时,咱们就经过这个Api去输出true or false给IO口,比方这儿咱们只需求给b和c 置为高电平,给其他的二极管拉低,那么就会显现一个1出来。这个数位管就叫硬件,而咱们写的Api就叫驱动。而现在市面上的显现设备也是这姿态的,分为硬件和驱动,一般硬件的厂家会自己适配驱动,这些驱动会依据接纳的数据,转换成一系列的0和1操控屏幕显现成想要的姿态。那么它接纳的数据是什么?便是Bitmap–位图

所以咱们就知道了,只要能把想要显现的画面转换成Bitmap格局,就能够直接把这个玩意塞给屏幕驱动了,它会自己依据Bitmap去操控屏幕中的无数个晶体管,终究把这个画面显现到屏幕上。

2. Android的数据转化进程

实践上把图像转换成Bitmap,app自己就能够做完了,因为View本身便是运用层的东西,这也是为什么有时分咱们在debug进程中遇到一些黑屏问题,他人会告知你,你运用层的图送下来便是黑的,请你从运用层的视点剖析。因为Bitmap便是Android 做出来的呀,由此引出了烘托架构中的关键东西:SkiaOpenGL

这儿先不去管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个步骤:

  1. 制作背景(drawBackground)
  2. 假如需求,保存当时layer用于动画过渡
  3. 制作View的内容(onDraw)
  4. 制作子View(dispatchDraw)
  5. 假如需求,则制作View的褪色边际,类似于暗影效果
  6. 制作装修,比方滚动条(onDrawForeground)
  7. 假如需求,制作默认焦点高亮效果(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的格局?便是前面说到的,SkiaOpenGL,这两个东西从烘托架构微观上来了解,能够把它们俩看成黑盒,它们俩都属于图形引擎,效果也是一样的,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。

Android GUI扫盲,渲染架构浅析
这个屏幕在一分钟之内,最多能够切换多少次,便是这个屏幕的改写率。比方咱们看到许多屏幕是75HZ,144HZ,200HZ等等。

那么咱们App在制作的时分,每一秒钟能够制作多少次?也便是能够履行多少次将画面转换成Bitmap的操作?这个当然和体系核算才能有关,明显GPU核算比CPU核算快,更好的GPU核算也更快。那么一分钟能够制作多少张Bitmap,这个就叫体系的帧速率(fps),比方当时体系帧速率是60fps,120fps,便是说当时体系一分钟分别能够制作60或120张Bitmap。

假如说咱们让App直接和屏幕驱动对话,会是什么效果:

Android GUI扫盲,渲染架构浅析

运用层在制作完Bitmap之后,不经过体系层,直接放到屏幕的缓冲区里边。这样带来的第一个问题便是叠图次序紊乱。因为当时并不是只要一个运用啊,也不是只要一个进程。很明显,咱们除了当时FocusWindow的进程,还有system_server进程嘛,还有systemUI需求制作,还有Launcher的TaskBar需求制作,咱们都是各绘各的,各自搞完了就放到Bitmap里边去,毫无次序的摆放,也就没有办法有序叠图,显现成终究想要的效果。

此外还有一个问题,便是改写率和帧速率无法匹配。比方当时屏幕1秒钟切换75次,但App只送过来30张Bitmap,那么在没有Bitmap送到的周期里,缓冲区就没有更新数据,终究显现的效果便是黑屏或许白屏;假如当时的改写率是30HZ,但帧速率达到了60或更高,也便是说App送过来的Bitmap,有许多根本没有显现出来就被丢掉了,这便是掉帧,成果便是显现的画面有卡顿。明显,要体系的考虑整个烘托架构,必须要处理改写率和帧速率相匹配的问题。

从Android 体系的视点来讲,咱们不行能为每一个不同的屏幕专门适配一套烘托体系,那么就需求在软件层做一个约定,在不知道屏幕硬件功能的情况下,经过一个体系来均衡硬件目标和软件目标,比方:

  1. 每分钟固定60次调用屏幕改写
  2. 每分钟固定制作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交给驱动,并触发屏幕的改写,让这一帧图片显现出来。

Android GUI扫盲,渲染架构浅析

好了,现在咱们知道,在整个烘托架构中,有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 烘托流程

Android GUI扫盲,渲染架构浅析

整理一下根本流程和几个重要进程的效果,首先是在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处理。