亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解Android啟動第一幀

 更新時(shí)間:2021年10月14日 14:19:00   作者:貓尾巴  
這篇文章我們就來介紹Android啟動第一幀,至于Android第一幀什么時(shí)候開始調(diào)度,具體內(nèi)容我們就來看下面文章內(nèi)容吧,感興趣得小伙伴可以和小編一起來學(xué)習(xí)奧

冷啟動結(jié)束的時(shí)間怎么確定?根據(jù) Play Console 文檔,當(dāng)應(yīng)用程序的第一幀完全加載時(shí),將跟蹤啟動時(shí)間。從 App 冷啟動時(shí)間文檔中了解到更多信息:一旦應(yīng)用進(jìn)程完成了第一次繪制,系統(tǒng)進(jìn)程就會換出當(dāng)前顯示的背景窗口,用主 Activity 替換它。 此時(shí),用戶可以開始使用該應(yīng)用程序。

1、第一幀什么時(shí)候開始調(diào)度

  • ActivityThread.handleResumeActivity() 調(diào)度第一幀。
  • 在第一幀 Choreographer.doFrame() 調(diào)用 ViewRootImpl.doTraversal() 執(zhí)行測量傳遞、布局傳遞,最后是視圖層次結(jié)構(gòu)上的第一個繪制傳遞。

2、第一幀

從 API 級別 16 開始,Android 提供了一個簡單的 API 來安排下一幀發(fā)生時(shí)的回調(diào):Choreographer.postFrameCallback()。

class MyApp : Application() {

  var firstFrameDoneMs: Long = 0

  override fun onCreate() {
    super.onCreate()
    Choreographer.getInstance().postFrameCallback {
      firstFrameDoneMs = SystemClock.uptimeMillis()
    }
  }
}

不幸的是,調(diào)用 Choreographer.postFrameCallback() 具有調(diào)度第一次遍歷之前運(yùn)行的幀的副作用。 所以這里報(bào)告的時(shí)間是在運(yùn)行第一次繪制的幀的時(shí)間之前。 我能夠在 API 25 上重現(xiàn)這個,但也注意到它不會在 API 30 中發(fā)生,所以這個錯誤可能已經(jīng)修復(fù)。

3、第一次繪制

ViewTreeObserver

Android 上,每個視圖層次結(jié)構(gòu)都有一個 ViewTreeObserver,它可以保存全局事件的回調(diào),例如布局或繪制。

ViewTreeObserver.addOnDrawListener()

我們可以調(diào)用 ViewTreeObserver.addOnDrawListener() 來注冊一個繪制監(jiān)聽器:

view.viewTreeObserver.addOnDrawListener { 
  // report first draw
}

ViewTreeObserver.removeOnDrawListener()

我們只關(guān)心第一次繪制,因此我們需要在收到回調(diào)后立即刪除 OnDrawListener。 不幸的是,無法從 onDraw() 回調(diào)中調(diào)用 ViewTreeObserver.removeOnDrawListener():

public final class ViewTreeObserver {
  public void removeOnDrawListener(OnDrawListener victim) {
    checkIsAlive();
    if (mInDispatchOnDraw) {
      throw new IllegalStateException(
          "Cannot call removeOnDrawListener inside of onDraw");
    }
    mOnDrawListeners.remove(victim);
  }
}

所以我們必須在一個 post 中進(jìn)行刪除:

class NextDrawListener(
  val view: View,
  val onDrawCallback: () -> Unit
) : OnDrawListener {

  val handler = Handler(Looper.getMainLooper())
  var invoked = false

  override fun onDraw() {
    if (invoked) return
    invoked = true
    onDrawCallback()
    handler.post {
      if (view.viewTreeObserver.isAlive) {
        viewTreeObserver.removeOnDrawListener(this)
      }
    }
  }

  companion object {
    fun View.onNextDraw(onDrawCallback: () -> Unit) {
      viewTreeObserver.addOnDrawListener(
        NextDrawListener(this, onDrawCallback)
      )
    }
  }
}

注意擴(kuò)展函數(shù):

view.onNextDraw { 
  // report first draw
}

FloatingTreeObserver

如果我們在附加視圖層次結(jié)構(gòu)之前調(diào)用 View.getViewTreeObserver() ,則沒有真正的 ViewTreeObserver 可用,因此視圖將創(chuàng)建一個假的來存儲回調(diào):

public class View {
  public ViewTreeObserver getViewTreeObserver() {
    if (mAttachInfo != null) {
      return mAttachInfo.mTreeObserver;
    }
    if (mFloatingTreeObserver == null) {
      mFloatingTreeObserver = new ViewTreeObserver(mContext);
    }
    return mFloatingTreeObserver;
  }
}

然后當(dāng)視圖被附加時(shí),回調(diào)被合并回真正的 ViewTreeObserver。

除了在 API 26 中修復(fù)了一個錯誤:繪制偵聽器沒有合并回真實(shí)的視圖樹觀察器。

我們通過在注冊我們的繪制偵聽器之前等待視圖被附加來解決這個問題:

class NextDrawListener(
  val view: View,
  val onDrawCallback: () -> Unit
) : OnDrawListener {

  val handler = Handler(Looper.getMainLooper())
  var invoked = false

  override fun onDraw() {
    if (invoked) return
    invoked = true
    onDrawCallback()
    handler.post {
      if (view.viewTreeObserver.isAlive) {
        viewTreeObserver.removeOnDrawListener(this)
      }
    }
  }

  companion object {
    fun View.onNextDraw(onDrawCallback: () -> Unit) {
      if (viewTreeObserver.isAlive && isAttachedToWindow) {
        addNextDrawListener(onDrawCallback)
      } else {
        // Wait until attached
        addOnAttachStateChangeListener(
            object : OnAttachStateChangeListener {
          override fun onViewAttachedToWindow(v: View) {
            addNextDrawListener(onDrawCallback)
            removeOnAttachStateChangeListener(this)
          }

          override fun onViewDetachedFromWindow(v: View) = Unit
        })
      }
    }

    private fun View.addNextDrawListener(callback: () -> Unit) {
      viewTreeObserver.addOnDrawListener(
        NextDrawListener(this, callback)
      )
    }
  }
}

DecorView

現(xiàn)在我們有一個很好的實(shí)用程序來監(jiān)聽下一次繪制,我們可以在創(chuàng)建 Activity 時(shí)使用它。 請注意,第一個創(chuàng)建的 Activity 可能不會繪制:應(yīng)用程序?qū)⒈拇?Activity 作為啟動器 Activity 是很常見的,它會立即啟動另一個 Activity 并自行完成。 我們在 Activity 窗口 DecorView 上注冊我們的繪制偵聽器。

class MyApp : Application() {

  override fun onCreate() {
    super.onCreate()

    var firstDraw = false

    registerActivityLifecycleCallbacks(
      object : ActivityLifecycleCallbacks {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        if (firstDraw) return
        activity.window.decorView.onNextDraw {
          if (firstDraw) return
          firstDraw = true
          // report first draw
        }
      }
    })
  }
}

四、鎖窗特性

根據(jù) Window.getDecorView() 的文檔:

請注意:setContentView() 中所述,首次調(diào)用此函數(shù)會“鎖定”各種窗口特征。

不幸的是,我們正在從 ActivityLifecycleCallbacks.onActivityCreated() 調(diào)用 Window.getDecorView(),它被 Activity.onCreate() 調(diào)用。 在一個典型的 Activity 中,setContentView() super.onCreate() 之后被調(diào)用,所以我們在 setContentView() 被調(diào)用之前調(diào)用 Window.getDecorView(),這會產(chǎn)生意想不到的副作用。

在我們檢索裝飾視圖之前,我們需要等待 setContentView() 被調(diào)用。

Window.Callback.onContentChanged()

我們可以使用 Window.peekDecorView() 來確定我們是否已經(jīng)有一個裝飾視圖。 如果沒有,我們可以在我們的窗口上注冊一個回調(diào),它提供了我們需要的鉤子,Window.Callback.onContentChanged():

只要屏幕的內(nèi)容視圖發(fā)生變化(由于調(diào)用 Window#setContentView() Window#addContentView() ),就會調(diào)用此鉤子。

但是,一個窗口只能有一個回調(diào),并且 Activity 已經(jīng)將自己設(shè)置為窗口回調(diào)。 所以我們需要替換那個回調(diào)并委托給它。

這是一個實(shí)用程序類,它執(zhí)行此操作并添加一個 Window.onDecorViewReady() 擴(kuò)展函數(shù):

= newCallback
        newCallback
      }

class WindowDelegateCallback constructor(
  private val delegate: Window.Callback
) : Window.Callback by delegate {

  val onContentChangedCallbacks = mutableListOf<() -> Boolean>()

  override fun onContentChanged() {
    onContentChangedCallbacks.removeAll { callback ->
      !callback()
    }
    delegate.onContentChanged()
  }

  companion object {
    fun Window.onDecorViewReady(callback: () -> Unit) {
      if (peekDecorView() == null) {
        onContentChanged {
          callback()
          return@onContentChanged false
        }
      } else {
        callback()
      }
    }

    fun Window.onContentChanged(block: () -> Boolean) {
      val callback = wrapCallback()
      callback.onContentChangedCallbacks += block
    }

    private fun Window.wrapCallback(): WindowDelegateCallback {
      val currentCallback = callback
      return if (currentCallback is WindowDelegateCallback) {
        currentCallback
      } else {
        val newCallback = WindowDelegateCallback(currentCallback)
        callback 
    }
  }
}

五、利用 Window.onDecorViewReady()

class MyApp : Application() {

  override fun onCreate() {
    super.onCreate()

    var firstDraw = false

    registerActivityLifecycleCallbacks(
      object : ActivityLifecycleCallbacks {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        if (firstDraw) return
        val window = activity.window
        window.onDecorViewReady {
          window.decorView.onNextDraw {
            if (firstDraw) return
            firstDraw = true
            // report first draw
          }
        }
      }
    })
  }
}

讓我們看看 OnDrawListener.onDraw() 文檔:

即將繪制視圖樹時(shí)調(diào)用的回調(diào)方法。

繪圖仍然需要一段時(shí)間。 我們想知道繪圖何時(shí)完成,而不是何時(shí)開始。 不幸的是,沒有 ViewTreeObserver.OnPostDrawListener API 。

第一幀和遍歷都發(fā)生在一個 MSG_DO_FRAME 消息中。 如果我們可以確定該消息何時(shí)結(jié)束,我們就會知道何時(shí)完成繪制。

Handler.postAtFrontOfQueue()

與其確定 MSG_DO_FRAME 消息何時(shí)結(jié)束,我們可以通過使用 Handler.postAtFrontOfQueue() 發(fā)布到消息隊(duì)列的前面來檢測下一條消息何時(shí)開始:

class MyApp : Application() {

  var firstDrawMs: Long = 0

  override fun onCreate() {
    super.onCreate()

    var firstDraw = false
    val handler = Handler()

    registerActivityLifecycleCallbacks(
      object : ActivityLifecycleCallbacks {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        if (firstDraw) return
        val window = activity.window
        window.onDecorViewReady {
          window.decorView.onNextDraw {
            if (firstDraw) return
            firstDraw = true
            handler.postAtFrontOfQueue {
              firstDrawMs = SystemClock.uptimeMillis()
            }
          }
        }
      }
    })
  }
}

編輯:我在大量設(shè)備上測量了生產(chǎn)中的第一個 onNextDraw() 和以下 postAtFrontOfQueue() 之間的時(shí)間差,以下是結(jié)果:

第 10 個百分位數(shù):25ms

第 25 個百分位數(shù):37 毫秒

第 50 個百分位數(shù):61 毫秒

第 75 個百分位數(shù):109 毫秒

第 90 個百分位數(shù):194 毫秒

到此這篇關(guān)于詳解Android啟動第一幀的文章就介紹到這了,更多相關(guān)Android啟動第一幀內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Android RecyclerView 數(shù)據(jù)綁定實(shí)例代碼

    Android RecyclerView 數(shù)據(jù)綁定實(shí)例代碼

    本文主要介紹Android RecyclerView 數(shù)據(jù)綁定的資料,這里詳細(xì)說明如何實(shí)現(xiàn) Android RecyclerView的數(shù)據(jù)綁定,并附示例代碼,有需要的小伙伴可以參考下
    2016-09-09
  • Android上下文菜單用法實(shí)例分析

    Android上下文菜單用法實(shí)例分析

    這篇文章主要介紹了Android上下文菜單用法,以完整實(shí)例形式分析了Android上下文菜單的定義、布局及功能實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-09-09
  • Android 更改 Toast 的默認(rèn)位置方法

    Android 更改 Toast 的默認(rèn)位置方法

    下面小編就為大家?guī)硪黄狝ndroid 更改 Toast 的默認(rèn)位置方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-04-04
  • Android中實(shí)現(xiàn)ping功能的多種方法詳解

    Android中實(shí)現(xiàn)ping功能的多種方法詳解

    這篇文章主要介紹了Android中實(shí)現(xiàn)ping功能的多種方法詳解,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-03-03
  • 解決Android studio3.6安裝后gradle Download失敗(構(gòu)建不成功)

    解決Android studio3.6安裝后gradle Download失敗(構(gòu)建不成功)

    這篇文章主要介紹了解決Android studio3.6安裝后gradle Download失敗(構(gòu)建不成功),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • 簡單實(shí)現(xiàn)Android放大鏡效果

    簡單實(shí)現(xiàn)Android放大鏡效果

    這篇文章主要教大家簡單實(shí)現(xiàn)Android放大鏡效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-12-12
  • 利用Kotlin的方式如何處理網(wǎng)絡(luò)異常詳解

    利用Kotlin的方式如何處理網(wǎng)絡(luò)異常詳解

    這篇文章主要 給大家介紹了關(guān)于利用Kotlin的方式如何處理網(wǎng)絡(luò)異常的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • Android?WebView軟鍵盤遮擋輸入框方案詳解

    Android?WebView軟鍵盤遮擋輸入框方案詳解

    這篇文章主要介紹了Android?WebView軟鍵盤遮擋輸入框方案詳解,本文提供了一種新的解決?WebView?輸入框被軟鍵盤遮擋的思路,不過這種思路也有它的局限性,目前來看僅適用于全屏的?WebView?中,需要的朋友可以參考下
    2022-06-06
  • Android對話框AlertDialog詳解

    Android對話框AlertDialog詳解

    本文詳細(xì)講解了Android對話框AlertDialog的實(shí)現(xiàn)方式,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-12-12
  • Android官方下拉刷新控件SwipeRefreshLayout使用詳解

    Android官方下拉刷新控件SwipeRefreshLayout使用詳解

    這篇文章主要為大家詳細(xì)介紹了Android官方下拉刷新控件SwipeRefreshLayout使用方法,實(shí)例展示如何使用下拉刷新控件,感興趣的小伙伴們可以參考一下
    2016-07-07

最新評論