日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Android繪制原理淺析「干貨」

背景

公司主營業(yè)務(wù):成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、移動(dòng)網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。創(chuàng)新互聯(lián)公司是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶帶來驚喜。創(chuàng)新互聯(lián)公司推出沽源免費(fèi)做網(wǎng)站回饋大家。

對于Android開發(fā),在面試的時(shí)候,經(jīng)常會(huì)被問到,說一說View的繪制流程?我也經(jīng)常問面試者,View的繪制流程.

對于3年以上的開發(fā)人員來說,就知道onMeasure/onLayout/onDraw基本,知道他們呢是干些什么的,這樣就夠了嗎?

如果你來我們公司,我是你的面試官,可能我會(huì)考察你這三年都干了什么,對于View你都知道些什么,會(huì)問一些更細(xì)節(jié)的問題,比如LinearLayout的onMeasure,onLayout過程?他們都是什么時(shí)候被發(fā)起的,執(zhí)行順序是什么?

如果以上問題你都知道,可能你進(jìn)來我們公司就差不多了(如果需要內(nèi)推,可以聯(lián)系我,Android/IOS 崗位都需要),可能我會(huì)考察你draw的 canvas是哪里來的,他是怎么被創(chuàng)建顯示到屏幕上呢?看看你的深度有多少?

對于現(xiàn)在的移動(dòng)開發(fā)市場逐漸趨向成熟,趨向飽和,很多不缺人的公司,都需要高級程序員.在說大家也都知道,面試要造飛機(jī)大炮,進(jìn)去后擰螺絲,對于一個(gè)3年或者5年以上Android開發(fā)不稍微了解一些Android深一點(diǎn)的東西,不是很好混.扯了這么多沒用的東西,還是回到今天正題,Android的繪圖原理淺析.

本文介紹思路

從面試題中幾個(gè)比較容易問的問題,逐層深入,直至屏幕的繪圖原理.

在講Android的繪圖原理前,先介紹一下Android中View的基本工作原理,本文暫不介紹事件的傳遞流程。

View 繪制工作原理

我們先理解幾個(gè)重要的類,也是在面試中經(jīng)常問到的

Activity,Window(PhoneWindow),DecorView之間的關(guān)系

理解他們?nèi)叩年P(guān)系,我們直接看代碼吧,先從Activity開始的setContentView開始(注:代碼刪除了一些不是本次分析流程的代碼,以免篇幅過長)

 
 
 
 
  1. //Activity
  2.  /**
  3.  * Set the activity content from a layout resource. The resource will be
  4.  * inflated, adding all top-level views to the activity.
  5.  *
  6.  * @param layoutResID Resource ID to be inflated.
  7.  *
  8.  * @see #setContentView(android.view.View)
  9.  * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
  10.  */
  11.  public void setContentView(@LayoutRes int layoutResID) {
  12.  getWindow().setContentView(layoutResID);
  13.  initWindowDecorActionBar();
  14.  }
  15.  
  16.  public Window getWindow() {
  17.  return mWindow;
  18.  }

里面調(diào)用的getWindow的setContentView,這個(gè)接下來講,那么這個(gè)mWindow是何時(shí)被創(chuàng)建的呢?

 
 
 
 
  1. //Activity
  2. private Window mWindow;
  3. final void attach(Context context, ActivityThread aThread,····) {
  4.  attachBaseContext(context);
  5.  mFragments.attachHost(null /*parent*/);
  6.  mWindow = new PhoneWindow(this, window, activityConfigCallback);
  7. }

在Activity的attach中創(chuàng)建了PhoneWindow,PhoneWindow是Window的實(shí)現(xiàn)類.

繼續(xù)剛才的setContentView

 
 
 
 
  1. //PhoneWindow
  2.  @Override
  3.  public void setContentView(int layoutResID) {
  4.  if (mContentParent == null) {
  5.  installDecor();
  6.  } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  7.  mContentParent.removeAllViews();
  8.  }
  9.  if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  10.  final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
  11.  getContext());
  12.  transitionTo(newScene);
  13.  } else {
  14.  mLayoutInflater.inflate(layoutResID, mContentParent);
  15.  }
  16.  }

在setContentView中,如果mContentParent為空,會(huì)去調(diào)用installDecor,最后將布局infalte到mContentParent.在來看一下installDecor

 
 
 
 
  1. //PhoneWindow
  2.  // This is the view in which the window contents are placed. It is either
  3.  // mDecor itself, or a child of mDecor where the contents go.
  4.  ViewGroup mContentParent;
  5.  
  6.  private DecorView mDecor;
  7.  
  8.  private void installDecor() {
  9.  mForceDecorInstall = false;
  10.  if (mDecor == null) {
  11.  mDecor = generateDecor(-1);
  12.  } else {
  13.  mDecor.setWindow(this);
  14.  }
  15.  if (mContentParent == null) {
  16.  mContentParent = generateLayout(mDecor);
  17.  }
  18.  }
  19.  protected DecorView generateDecor(int featureId) {
  20.  return new DecorView(context, featureId, this, getAttributes());
  21.  }

在installDecor,創(chuàng)建了一個(gè)DecorView.看mContentParent的注釋我們可以知道,他本身就是mDecor或者是mDecor的contents部分.

綜上,我們大概知道了三者的關(guān)系,

  • Activity包含了一個(gè)PhoneWindow,
  • PhoneWindow就是繼承于Window
  • Activity通過setContentView將View設(shè)置到了PhoneWindow上
  • PhoneWindow里面包含了DecorView,最終布局被添加到Decorview上.

理解ViewRootImpl,WindowManager,WindowManagerService(WMS)之間的關(guān)系

看了上述三者的關(guān)系后,我們知道布局最終被添加到了DecorView上.那么DecorView是怎么被添加到系統(tǒng)的Framework層.

當(dāng)Activity準(zhǔn)備好后,最終會(huì)調(diào)用到Activity中的makeVisible,并通過WindowManager添加View,代碼如下

 
 
 
 
  1. //Activity 
  2.  void makeVisible() {
  3.  if (!mWindowAdded) {
  4.  ViewManager wm = getWindowManager();
  5.  wm.addView(mDecor, getWindow().getAttributes());
  6.  mWindowAdded = true;
  7.  }
  8.  mDecor.setVisibility(View.VISIBLE);
  9.  }

那他們到底是什么關(guān)系呢? (下面提到到客戶端服務(wù)端是Binder通訊中的客戶端服務(wù)端概念. )

以下內(nèi)容是重點(diǎn)需要理解的部分

  • ViewRootImpl(客戶端):View中持有與WMS鏈接的mAttachInfo,mAttachInfo持有ViewRootImpl.ViewRootImpl是ViewRoot的的實(shí)現(xiàn),WMS管理窗口時(shí),需要通知客戶端進(jìn)行某種操作,比如事件響應(yīng)等.ViewRootImpl有個(gè)內(nèi)部類W,W繼承IWindow.Stub,實(shí)則就是一個(gè)Binder,他用于和WMS IPC交互。ViewRootHandler也是其內(nèi)部類繼承Handler,用于與遠(yuǎn)程IPC回來的數(shù)據(jù)進(jìn)行異步調(diào)用.
  • WindowManger(客戶端):客戶端需要?jiǎng)?chuàng)建一個(gè)窗口,而具體創(chuàng)建窗口的任務(wù)是由WMS完成,WindowManger就像一個(gè)部門經(jīng)理,誰有什么需求就告訴它,它和WMS交互,客戶端不能直接和WMS交互.
  • WindowManagerService(WMS)(服務(wù)端):負(fù)責(zé)窗口的創(chuàng)建,顯示等.

View的重繪

從上述關(guān)系中,ViewRootImpl是用于接收WMS傳遞來的消息.那么我們來看一下ViewRootImpl里面的幾個(gè)關(guān)于View繪制的代碼.

在這里在強(qiáng)調(diào)一下,ViewRootImpl 兩個(gè)重要的內(nèi)部類

  • W類 繼承Binder 用于接收WMS 傳遞來的消息
  • ViewRootHandler類繼承Handler 接收W類的異步消息

下面看一下ViewRootHandler類.(以View的setVisible為例.)

 
 
 
 
  1. // ViewRootHandler(ViewRootImpl的內(nèi)部類,用于異步消息處理,和Acitivity的啟動(dòng)很像)
  2. //第一步 Handler接收W(Binder)傳遞來的消息
  3. @Override
  4. public void handleMessage(Message msg) {
  5.  switch (msg.what) {
  6.  case MSG_INVALIDATE:
  7.  ((View) msg.obj).invalidate();
  8.  break;
  9.  case MSG_INVALIDATE_RECT:
  10.  final View.AttachInfo.InvalidateInfo info = (View.AttachInfo.InvalidateInfo) msg.obj;
  11.  info.target.invalidate(info.left, info.top, info.right, info.bottom);
  12.  info.recycle();
  13.  break;
  14.  case MSG_DISPATCH_APP_VISIBILITY://處理Visible
  15.  handleAppVisibility(msg.arg1 != 0);
  16.  break;
  17.  } 
  18. }
  19.  
  20. void handleAppVisibility(boolean visible) {
  21.  if (mAppVisible != visible) {
  22.  mAppVisible = visible;
  23.  scheduleTraversals();
  24.  if (!mAppVisible) {
  25.  WindowManagerGlobal.trimForeground();
  26.  }
  27.  }
  28. }
  29.  
  30.  void scheduleTraversals() {
  31.  if (!mTraversalScheduled) {
  32.  mTraversalScheduled = true;
  33.  mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
  34.  //開啟下次刷新,就遍歷View樹
  35.  mChoreographer.postCallback(
  36.  Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
  37.  if (!mUnbufferedInputDispatch) {
  38.  scheduleConsumeBatchedInput();
  39.  }
  40.  notifyRendererOfFramePending();
  41.  pokeDrawLockIfNeeded();
  42.  }
  43. }

看一下mTraversalRunnable

 
 
 
 
  1. final class TraversalRunnable implements Runnable {
  2.  @Override
  3.  public void run() {
  4.  doTraversal();
  5.  }
  6.  }
  7. final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  8.  
  9.  void doTraversal() {
  10.  if (mTraversalScheduled) {
  11.  mTraversalScheduled = false;
  12.  mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
  13.  performTraversals();
  14.  }
  15.  } 

在TraversalRunnable中,執(zhí)行doTraversal.并在doTraversal執(zhí)行performTraversals(),是不是看到了我們熟悉的performTraversals()了?是的,在這里才開始View的繪制工作.

在ViewRootImpl中的performTraversals(),這個(gè)方法代碼很長(大約800行代碼),大致流程是

  1. 判斷是否需要重新計(jì)算視圖大小,如果需要就執(zhí)行performMeasure()
  2. 是否需要重新安置所在的位置,performLayout()
  3. 是否需要重新繪制performDraw()

那么是什么導(dǎo)致View的重繪呢?這里總結(jié)了3個(gè)主要原因

  1. 視圖本身內(nèi)部狀態(tài)(enable,pressed等)變化,可能引起重繪
  2. View內(nèi)部添加或者刪除了View
  3. View本身的大小和可見性發(fā)生了變化

View的繪制流程

在上一小節(jié)了,講述了performTraversals()的是被WMS IPC調(diào)用執(zhí)行的.View的繪制流程一般是

從performTraversals -> performMeasure() -> performLayout() -> performDraw().

下面看一下performMeasure()

 
 
 
 
  1. //ViewRootImpl
  2. private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
  3.  if (mView == null) {
  4.  return;
  5.  }
  6.  Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
  7.  try {
  8.  mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  9.  } finally {
  10.  Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  11.  }
  12.  }
  13.  
  14.  public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
  15.  MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
  16.  && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
  17.  final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
  18.  && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
  19.  final boolean needsLayout = specChanged
  20.  && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
  21.  if (forceLayout || needsLayout) {
  22.  mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
  23.  resolveRtlPropertiesIfNeeded();
  24.  int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
  25.  if (cacheIndex < 0 || sIgnoreMeasureCache) {
  26.  //在這里調(diào)用了onMeasure 方法
  27.  onMeasure(widthMeasureSpec, heightMeasureSpec);
  28.  mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  29.  } 
  30.  }
  31.  }

最終調(diào)用了View的measure方法,而View中的measure()方法被定義成final類型,保證整個(gè)流程的執(zhí)行.performLayout()和performDraw()也是類似的過程.

而對于程序員,自定義View只需要關(guān)注他提供出來幾個(gè)對應(yīng)的方法,onMeasure/onLayout/onDraw. 關(guān)于這方面知識(shí)的網(wǎng)上介紹的資料很多,也可以很容易的看到View及ViewGroup里面的代碼,推薦看LinerLayout的源碼理解這部分知識(shí),在這里不詳細(xì)展開.

Android的繪圖原理淺析

Android屏幕繪制

關(guān)于繪制,就要從performDraw()說起,我們來看一下這個(gè)流程到底是怎么繪制的.

 
 
 
 
  1. //ViewRootImpl
  2. //1
  3.  private void performDraw() {
  4.  try {
  5.  draw(fullRedrawNeeded);
  6.  } finally {
  7.  mIsDrawing = false;
  8.  Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  9.  }
  10.  }
  11.  
  12.  //2
  13.  private void draw(boolean fullRedrawNeeded) {
  14.  Surface surface = mSurface;
  15.  if (!surface.isValid()) {
  16.  return;
  17.  }
  18.  
  19.  if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
  20.  return;
  21.  }
  22.  }
  23.  
  24.  //3
  25.  private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
  26.  boolean scalingRequired, Rect dirty) {
  27.  Canvas canvas = mSurface.lockCanvas(dirty);
  28.  } 

看代碼執(zhí)行流程,1—>2->3, 最終拿到了Java層的canvas,然后進(jìn)行一系列繪制操作.而canvas是通過Suface.lockCanvas()得到的.

那么Surface又是一個(gè)什么呢?在這里Surface只是一個(gè)抽象,在APP創(chuàng)建窗口時(shí),會(huì)調(diào)用WindowManager向WMS服務(wù)發(fā)起一個(gè)請求,攜帶上surface對象,只有他被分配完一段屏幕緩沖區(qū)才能真正對應(yīng)屏幕上的一個(gè)窗口.

來看一下Framework中的繪圖架構(gòu).更好的理解Surface

Surface本質(zhì)上僅僅代表了一個(gè)平面,繪制不同圖案顯然是一種操作,而不是一段數(shù)據(jù),Android使用了Skia繪圖驅(qū)動(dòng)庫來進(jìn)行平面上的繪制,在程序中使用canvas來表示這個(gè)功能.

雙緩沖技術(shù)的介紹

在ViewRootImpl中,我們看到接收到繪制消息后,不是立刻繪制而是調(diào)用scheduleTraversals,在scheduleTraversals調(diào)用Choreographer.postCallback(),這又是因?yàn)槭裁茨?這其實(shí)涉及到屏幕繪制原理(除了Android其他平臺(tái)也是類似的).

我們都知道顯示器以固定的頻率刷新,比如 iPhone的 60Hz、iPad Pro的 120Hz。當(dāng)一幀圖像繪制完畢后準(zhǔn)備繪制下一幀時(shí),顯示器會(huì)發(fā)出一個(gè)垂直同步信號(hào)(VSync),所以 60Hz的屏幕就會(huì)一秒內(nèi)發(fā)出 60次這樣的信號(hào)。

并且一般地來說,計(jì)算機(jī)系統(tǒng)中,CPU、GPU和顯示器以一種特定的方式協(xié)作:CPU將計(jì)算好的顯示內(nèi)容提交給 GPU,GPU渲染后放入幀緩沖區(qū),然后視頻控制器按照 VSync信號(hào)從幀緩沖區(qū)取幀數(shù)據(jù)傳遞給顯示器顯示.

但是如果屏幕的緩沖區(qū)只有一塊,那么這個(gè)VSync同步信號(hào)發(fā)出時(shí), 開始刷新屏幕,那么你看到的屏幕就是一條一條的數(shù)據(jù)在變化.為了讓屏幕看上去是一幀一幀的數(shù)據(jù),一般都有兩塊緩沖區(qū)(也被成為雙緩沖區(qū)).當(dāng)數(shù)據(jù)要刷新時(shí),直接替換另一個(gè)緩沖區(qū)的數(shù)據(jù).

雙緩沖技術(shù)里面,如果不能特定時(shí)間刷新完的話(如果60HZ的話,就是16ms內(nèi))把這個(gè)緩沖區(qū)數(shù)據(jù)刷新完成,屏幕發(fā)出VSync同步信號(hào),無法完成兩個(gè)緩沖區(qū)的切換,那么就會(huì)造成卡頓現(xiàn)象。

回到scheduleTraversals()上,這個(gè)地方就是使用了雙緩沖技術(shù)(或者三緩沖技術(shù)),Choreographer接收VSync的同步信號(hào),當(dāng)屏幕刷新來時(shí),開始屏幕的刷新操作。


網(wǎng)頁題目:Android繪制原理淺析「干貨」
本文路徑:http://www.dlmjj.cn/article/dpphpjj.html