91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

說說Android的UI刷新機制的實現

發布時間:2020-10-13 11:57:08 來源:腳本之家 閱讀:286 作者:Raina 欄目:移動開發

本文主要解決以下幾個問題:

  1. 我們都知道Android的刷新頻率是60幀/秒,這是不是意味著每隔16ms就會調用一次onDraw方法?
  2. 如果界面不需要重繪,那么16ms到后還會刷新屏幕嗎?
  3. 我們調用invalidate()之后會馬上進行屏幕刷新嗎?
  4. 我們說丟幀是因為主線程做了耗時操作,為什么主線程做了耗時操作就會引起丟幀?
  5. 如果在屏幕快要刷新的時候才去OnDraw()繪制,會丟幀嗎?

好了,帶著以上問題,我們進入源碼來找尋答案。

一、屏幕繪制流程

屏幕繪制機制的基本原理可以概括如下:

說說Android的UI刷新機制的實現

整個屏幕繪制的基本流程是:

  • 應用向系統服務申請buffer
  • 系統服務返回buffer
  • 應用繪制后提交buffer給系統服務

如果放到Android中來,那么就是:

說說Android的UI刷新機制的實現

在Android中,一塊Surface對應一塊內存,當內存申請成功后,App端才有繪圖的地方。由于Android的view繪制不是今天的重點,所以這里點到為止~

二、屏幕刷新分析

屏幕刷新的時機是當Vsync信號到來的時候,具體如圖:

說說Android的UI刷新機制的實現

在Android端,是誰在控制 Vsync 的產生?又是誰來通知我們應用進行刷新的呢? 在Android中, Vysnc 信號的產生是由底層 HWComposer 負責的,而通知應用進行刷新,是Java層的 Choreographer ,Android整個屏幕刷新的核心就在于這個 Choreographer

下面我們結合代碼一起來看一下。

每次當我們要進行ui重繪的時候,都會調用 requestLayout() ,所以,我們從這個方法入手:

2.1 requestLayout()

----》類名:ViewRootImpl

  @Override
  public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
      checkThread();
      mLayoutRequested = true;
      //重點
      scheduleTraversals();
    }
  }

2.2 scheduleTraversals()

----》類名:ViewRootImpl

  void scheduleTraversals() {
    if (!mTraversalScheduled) {
      mTraversalScheduled = true;
      mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
      mChoreographer.postCallback(
          Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
      ......
    }
  }

可以看到,在這里并沒有立即進行重繪,而是做了兩件事情:

  • 往消息隊列里面插入一條SyncBarrier(同步屏障)
  • 通過Cherographer post了一個callback

接下來,我們簡單說一下這個 SyncBarrier (同步屏障)。

異步屏障的作用在于:

  • 阻止同步消息的執行
  • 優先執行異步消息

為什么要設計這個 SyncBarrier 呢?主要原因在于,在Android中,有些消息是十分緊急的,需要馬上執行,如果說消息隊列里面普通消息太多的話,那等到執行它的時候可能早就過了時機了。

到這里,可能有人會跟我一樣,覺得為什么不干脆在Message里搞個優先級,按照優先級來進行排序呢?弄個 PriorityQueue 不就完了嗎?

我自己的理解是,在Android中,消息隊列的設計是一個 單鏈表 ,整個鏈表的排序是根據時間進行排序的,如果此時再加入一個優先級的排序規則,一方面會復雜會排序規則,另一方面,也會使得消息不可控。因為優先級是可以用戶自己在外面填的,那樣不就亂套了嗎?如果用戶每次總填最高的優先級,這樣就會導致系統消息很久才會消費,整個系統運作就會出問題,最后影響用戶體驗,所以,我自己覺得Android的同步屏障這個設計還是挺巧妙的~

好了,總結一下,執行 scheduleTraversals() 后,會插入一個屏障,保證異步消息的優先執行。

插入一個小小的思考題: 如果說我們在一個方法里連續調用了 requestLayout() 多次,那么請問:系統會插入多條屏障或者 post 多個 Callback 嗎? 答案是不會,為什么呢?看到 mTraversalScheduled 這個變量了嗎?它就是答案~

2.3 Choreographer.postCallback()

先來簡單說一下 ChoreographerChoreographer 中文翻譯叫 編舞者 ,它的主要作用是進行系統協調的。(大家可以上網google下實際工作中的編舞者,這個類名真的起的很貼切了~)

Choreographer 這個類是應用怎么初始化的呢?是通過 getInstance() 方法:

 public static Choreographer getInstance() {
    return sThreadInstance.get();
  }
  
    // Thread local storage for the choreographer.
  private static final ThreadLocal<Choreographer> sThreadInstance =
      new ThreadLocal<Choreographer>() {
    @Override
    protected Choreographer initialValue() {
      Looper looper = Looper.myLooper();
      if (looper == null) {
        throw new IllegalStateException("The current thread must have a looper!");
      }
      Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
      if (looper == Looper.getMainLooper()) {
        mMainInstance = choreographer;
      }
      return choreographer;
    }
  };

這里貼出來是為了提醒大家, Choreographer 不是單例,而是每個線程都有單獨的一份。

好了,回到我們的代碼:

 ----》類名:Choreographer
 //1
  public void postCallback(int callbackType, Runnable action, Object token) {
    postCallbackDelayed(callbackType, action, token, 0);
  }
 //2 
   public void postCallbackDelayed(int callbackType,
      Runnable action, Object token, long delayMillis) {
    ....
    postCallbackDelayedInternal(callbackType, action, token, delayMillis);
  }
  //3
   private void postCallbackDelayedInternal(int callbackType,
      Object action, Object token, long delayMillis) {
        ...
        mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
        if (dueTime <= now) {
        scheduleFrameLocked(now);
      } else {
        ...
       }
      }

Choreographer post的callback會放入 CallbackQueue 里面,這個 CallbackQueue 是一個單鏈表。

首先會根據callbackType得到一條 CallbackQueue 單鏈表,之后會根據時間順序,將這個callback插入到單鏈表中;

2.4 scheduleFrameLocked()

 ----》類名:Choreographer
 private void scheduleFrameLocked(long now) {
    ...
    // If running on the Looper thread, then schedule the vsync immediately,
        // otherwise post a message to schedule the vsync from the UI thread
        // as soon as possible.
        if (isRunningOnLooperThreadLocked()) {
          scheduleVsyncLocked();
        } else {
          Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
          msg.setAsynchronous(true);
          mHandler.sendMessageAtFrontOfQueue(msg);
        }
      } else {
        ...
      }
    }
  }

scheduleFrameLocked 的作用是:

  • 如果當前線程就是 Cherographer 的工作線程的話,那么就直接執行 scheduleVysnLocked
  • 否則,就發送一個異步消息到消息隊列里面去 ,這個異步消息是不受同步屏障影響的,而且這個消息還要插入到消息隊列的頭部,可見這個消息是非常緊急的

跟蹤源代碼,我們發現,其實 MSG_DO_SCHEDULE_VSYNC 這條消息,最終執行的也是 scheduleFrameLocked 這個方法,所以我們直接跟蹤 scheduleVsyncLocked() 這個方法。

2.5 scheduleVsyncLocked()

 ----》類名:Choreographer
 
  private void scheduleVsyncLocked() {
    mDisplayEventReceiver.scheduleVsync();
  }
  
 ----》類名:DisplayEventReceiver
 
    public void scheduleVsync() {
    if (mReceiverPtr == 0) {
      Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
          + "receiver has already been disposed.");
    } else {
    //mReceiverPtr是Native層一個類的指針地址
    //這里這個類指的是底層NativeDisplayEventReceiver這個類
    //nativeScheduleVsync底層會調用到requestNextVsync()去請求下一個Vsync,
    //具體不跟蹤了,native層代碼更長,還涉及到各種描述符監聽以及跨進程數據傳輸
      nativeScheduleVsync(mReceiverPtr);
    }
  }

這里我們可以看到一個新的類: DisplayEventReceiver ,這個類的作用是注冊Vsync信號的監聽,當下個Vsync信號到來的時候就會通知到這個 DisplayEventReceiver 了。

在哪里通知呢?源碼里注釋寫的非常清楚了:

 ----》類名:DisplayEventReceiver
 
  // Called from native code. <---注釋還是很良心的
  private void dispatchVsync(long timestampNanos, int builtInDisplayId, int frame) {
    onVsync(timestampNanos, builtInDisplayId, frame);
  }

當下一個Vysnc信號到來的時候,會最終調用 onVsync 方法:

 public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
  }

點進去一看,是個空實現,回到類定義,原來是個抽象類,它的實現類是: FrameDisplayEventReceiver ,定義在 Cherographer 里面:

 ----》類名:Choreographer
 
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
      implements Runnable {
      ....
      }

2.6 FrameDisplayEventReceiver.onVysnc()

 ----》類名:Choreographer
 
 private final class FrameDisplayEventReceiver extends DisplayEventReceiver
      implements Runnable {

    @Override
    public void onVsync(long timestampNanos, int builtInDisplayId, int frame) {
       ....
      mTimestampNanos = timestampNanos;
      mFrame = frame;
      Message msg = Message.obtain(mHandler, this);
      msg.setAsynchronous(true);
      mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
    }

    @Override
    public void run() {
      ....
      doFrame(mTimestampNanos, mFrame);
    }
  }

onVsync 方法往 Cherographer 所在線程的消息隊列中發送的一個消息,這個消息是就是它自己(它實現了Runnable),所以最終會調用到 doFrame() 方法。

2.7 doFrame(mTimestampNanos, mFrame)

doFrame()的處理分為兩個階段:

  void doFrame(long frameTimeNanos, int frame) {
    final long startNanos;
    synchronized (mLock) {
      //1、階段一
      long intendedFrameTimeNanos = frameTimeNanos;
      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 application may be doing too much work on its main thread.");
        }
        ...
      }
      ...
    }

frameTimeNanos 是當前的時間戳,將當前的時間和開始時間相減,得到這一幀處理花費了多長,如果大于 mFrameIntervalNano ,說明處理耗時了,之后就打印出我們日常見到的 The application may be doing too much work on its main thread

階段二:

 void doFrame(long frameTimeNanos, int frame) {
 ...
try {
//階段2
      Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
      AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

      mFrameInfo.markInputHandlingStart();
      doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

      mFrameInfo.markAnimationsStart();
      doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);

      mFrameInfo.markPerformTraversalsStart();
      doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

      doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    } 
    ...
    }

doFrame() 的第二個階段做的是處理各種callback,從CallbackQueue里面取出到執行時間的callback進行處理,那這個callback是怎么樣呢?

這里要回憶一下之前的 postCallback() 操作:

說說Android的UI刷新機制的實現

這個 Callback 其實就一個 mTraversalRunnable ,它是一個 Runnable ,最終會調用到 run() 方法,實現界面的真正刷新:

 ----》類名:ViewRootImpl

  final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
      doTraversal();
    }
  }
  
  void doTraversal() {
    if (mTraversalScheduled) {
     ...
      performTraversals();
     ...
    }
  }
  
  private void performTraversals() {
   ...
   //開始真正的界面繪制
    performDraw();
   ...
  }

三、總結

經過漫長的代碼跟蹤,整個界面刷新流程算是跟蹤完了,下面我們來總結一下:

說說Android的UI刷新機制的實現

四、問題解答

我們都知道Android的刷新頻率是60幀/秒,這是不是意味著每隔16ms就會調用一次onDraw方法?

這里60幀/秒是屏幕刷新頻率,但是是否會調用onDraw()方法要看應用是否調用requestLayout()進行注冊監聽。

如果界面不需要重繪,那么還16ms到后還會刷新屏幕嗎?

如果不需要重繪,那么應用就不會受到Vsync信號,但是還是會進行刷新,只不過繪制的數據不變而已;

我們調用invalidate()之后會馬上進行屏幕刷新嗎?

不會,到等到下一個Vsync信號到來

我們說丟幀是因為主線程做了耗時操作,為什么主線程做了耗時操作就會引起丟幀

原因是,如果在主線程做了耗時操作,就會影響下一幀的繪制,導致界面無法在這個Vsync時間進行刷新,導致丟幀了。

如果在屏幕快要刷新的時候才去OnDraw()繪制,會丟幀嗎?

這個沒有太大關系,因為Vsync信號是周期的,我們什么時候發起onDraw()不會影響界面刷新;

五、參考文檔

gityuan大神的 Cherographer原理
慕課視頻

到此這篇關于說說Android的UI刷新機制的實現的文章就介紹到這了,更多相關Android UI刷新機制內容請搜索億速云以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持億速云!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

威海市| 东乡族自治县| 黄冈市| 鹤庆县| 肃宁县| 康平县| 高州市| 东乌珠穆沁旗| 黎川县| 邢台县| 琼中| 射洪县| 南汇区| 陇川县| 宁海县| 洛扎县| 五台县| 和龙市| 盐津县| 奉贤区| 马鞍山市| 尼玛县| 新兴县| 黄陵县| 澎湖县| 财经| 邯郸县| 湾仔区| 五原县| 莎车县| 思南县| 金川县| 晴隆县| 彭泽县| 白城市| 繁峙县| 措美县| 监利县| 双江| 桂东县| 阳高县|