iis服务器助手广告广告
返回顶部
首页 > 资讯 > 移动开发 >详解Android启动第一帧
  • 229
分享到

详解Android启动第一帧

2024-04-02 19:04:59 229人浏览 薄情痞子
摘要

目录1、第一帧什么时候开始调度2、第一帧3、第一次绘制ViewTreeObserverViewTreeObserver.addOnDrawListener()ViewTreeObse

冷启动结束的时间怎么确定?根据 Play Console 文档,当应用程序的第一帧完全加载时,将跟踪启动时间。从 App 冷启动时间文档中了解到更多信息:一旦应用进程完成了第一次绘制,系统进程就会换出当前显示的背景窗口,用主 Activity 替换它。 此时,用户可以开始使用该应用程序。

1、第一帧什么时候开始调度

  • ActivityThread.handleResumeActivity() 调度第一帧。
  • 在第一帧 Choreographer.doFrame() 调用 ViewRootImpl.doTraversal() 执行测量传递、布局传递,最后是视图层次结构上的第一个绘制传递。

2、第一帧

api 级别 16 开始,Android 提供了一个简单的 API 来安排下一帧发生时的回调:Choreographer.postFrameCallback()。


class MyApp : Application() {

  var firstFrameDoneMs: Long = 0

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

不幸的是,调用 Choreographer.postFrameCallback() 具有调度第一次遍历之前运行的帧的副作用。 所以这里报告的时间是在运行第一次绘制的帧的时间之前。 我能够在 API 25 上重现这个,但也注意到它不会在 API 30 中发生,所以这个错误可能已经修复。

3、第一次绘制

ViewTreeObserver

Android 上,每个视图层次结构都有一个 ViewTreeObserver,它可以保存全局事件的回调,例如布局或绘制。

ViewTreeObserver.addOnDrawListener()

我们可以调用 ViewTreeObserver.addOnDrawListener() 来注册一个绘制监听器:


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

ViewTreeObserver.removeOnDrawListener()

我们只关心第一次绘制,因此我们需要在收到回调后立即删除 OnDrawListener。 不幸的是,无法从 onDraw() 回调中调用 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 中进行删除:


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)
      )
    }
  }
}

注意扩展函数:


view.onNextDraw { 
  // report first draw
}

FloatingTreeObserver

如果我们在附加视图层次结构之前调用 View.getViewTreeObserver() ,则没有真正的 ViewTreeObserver 可用,因此视图将创建一个假的来存储回调:


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

然后当视图被附加时,回调被合并回真正的 ViewTreeObserver

除了在 API 26 中修复了一个错误:绘制侦听器没有合并回真实的视图树观察器。

我们通过在注册我们的绘制侦听器之前等待视图被附加来解决这个问题:


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

现在我们有一个很好的实用程序来监听下一次绘制,我们可以在创建 Activity 时使用它。 请注意,第一个创建的 Activity 可能不会绘制:应用程序将蹦床 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
        }
      }
    })
  }
}

四、锁窗特性

根据 Window.getDecorView() 的文档:

请注意:setContentView() 中所述,首次调用此函数会“锁定”各种窗口特征。

不幸的是,我们正在从 ActivityLifecycleCallbacks.onActivityCreated() 调用 Window.getDecorView(),它被 Activity.onCreate() 调用。 在一个典型的 Activity 中,setContentView() super.onCreate() 之后被调用,所以我们在 setContentView() 被调用之前调用 Window.getDecorView(),这会产生意想不到的副作用。

在我们检索装饰视图之前,我们需要等待 setContentView() 被调用。

Window.Callback.onContentChanged()

我们可以使用 Window.peekDecorView() 来确定我们是否已经有一个装饰视图。 如果没有,我们可以在我们的窗口上注册一个回调,它提供了我们需要的钩子,Window.Callback.onContentChanged():

只要屏幕的内容视图发生变化(由于调用 Window#setContentView() Window#addContentView() ),就会调用此钩子。

但是,一个窗口只能有一个回调,并且 Activity 已经将自己设置为窗口回调。 所以我们需要替换那个回调并委托给它。

这是一个实用程序类,它执行此操作并添加一个 Window.onDecorViewReady() 扩展函数:


= 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() 文档:

即将绘制视图树时调用的回调方法。

绘图仍然需要一段时间。 我们想知道绘图何时完成,而不是何时开始。 不幸的是,没有 ViewTreeObserver.OnPostDrawListener API

第一帧和遍历都发生在一个 MSG_DO_FRAME 消息中。 如果我们可以确定该消息何时结束,我们就会知道何时完成绘制。

Handler.postAtFrontOfQueue()

与其确定 MSG_DO_FRAME 消息何时结束,我们可以通过使用 Handler.postAtFrontOfQueue() 发布到消息队列的前面来检测下一条消息何时开始:


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()
            }
          }
        }
      }
    })
  }
}

编辑:我在大量设备上测量了生产中的第一个 onNextDraw() 和以下 postAtFrontOfQueue() 之间的时间差,以下是结果:

第 10 个百分位数:25ms

第 25 个百分位数:37 毫秒

第 50 个百分位数:61 毫秒

第 75 个百分位数:109 毫秒

第 90 个百分位数:194 毫秒

到此这篇关于详解Android启动第一帧的文章就介绍到这了,更多相关Android启动第一帧内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: 详解Android启动第一帧

本文链接: https://www.lsjlt.com/news/154463.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 详解Android启动第一帧
    目录1、第一帧什么时候开始调度2、第一帧3、第一次绘制ViewTreeObserverViewTreeObserver.addOnDrawListener()ViewTreeObse...
    99+
    2024-04-02
  • Android 帧动画使用详情
    目录Android 帧动画1、帧动画2、使用背景图片3、使用srcAndroid 帧动画 1、帧动画 使用一系列不同的图片,然后像一卷胶卷一样按顺序播放,这是一种传统的动画,也可称为...
    99+
    2024-04-02
  • Android 逐帧动画创建实例详解
    Android 逐帧动画创建实例详解前言:我们看早期电影的时候,电影通常是一张一张播放,用我们现在专有名词来说,就是一帧帧来,安卓同样有这样动画效果的编排形式。那么我们先定义逐帧动画xml文件<?xml version="1....
    99+
    2023-05-30
    android 逐帧动画 roi
  • Android AMS启动详解
    目录启动startServiceAMS的创建start()setSystemProcess后续goingCallBackstartHomeOnAllDisplays总结:启动 在A...
    99+
    2024-04-02
  • Android zygote启动流程详解
    目录对zygote的理解作用启动流程启动入口脚本讲解启动过程App_main::mainAndroidRuntime::start对zygote的理解 在Android系统中,zy...
    99+
    2024-04-02
  • Android Activity启动过程详解
     Android系统启动篇 1,《android系统启动流程简介》 2,《android init进程启动流程》 3,《android zygote进程启动流程》 4,《Android SystemServer进程启动流程》 5,《andr...
    99+
    2023-09-02
    android
  • 详解Android Activity的启动流程
    目录前言 简要 1.Launcher向AMS发送启动Activity 2.AMS启动Activity并通知Launcher进入Paused状态 3.新的进程启动,ActivityTh...
    99+
    2024-04-02
  • 详解Android系统启动过程
    目录计算机是如何启动的引导阶段加载内核阶段Android的启动过程init进程init.rc 文件service_manager 进程surface_flinger 进程media_...
    99+
    2024-04-02
  • Android 启动模式FLAG_ACTIVITY_CLEAR_TOP案例详解
    四种启动模式 standard: 只要被启动就会创建一个新的 singleTop: 栈顶复用(当被启动的Activity处于Task栈顶时,可以复用,直接调用o...
    99+
    2024-04-02
  • Android跨应用启动实例详解
    Android跨应用启动前言:相信大家,很多时候都是在自己的应用中,启动自己写的Activity,Service、BroadcastReceiver、contentProvider 。换句话说,这些都只是 * 单个应用中 组件间 * 的启动...
    99+
    2023-05-31
    android 跨应用 roi
  • Android app启动节点与上报启动实例详解
    目录app启动的关键节点启动时间怎么算方案1: 参考firebase:方案2 : ams总结app启动的关键节点 经常利用content provider 和Androidx里的 s...
    99+
    2024-04-02
  • Android Service启动绑定流程详解
    目录前言一、Service 的启动流程二、Service的绑定三、Service的Context总结前言 本文基于Android 11,参考《Android进阶解密》一书资料。了解...
    99+
    2023-03-08
    Android  Service启动绑定流程 Android Service
  • Android如何实现过渡动画、引导页 Android判断是否第一次启动App
    这篇文章主要介绍了Android如何实现过渡动画、引导页 Android判断是否第一次启动App,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。目前的App在安装后,第一次打开...
    99+
    2023-05-30
  • 详解Android广播Broadcast的启动流程
    Android中的广播是一种用于应用程序之间通信的机制。它允许应用程序发送和接收系统级或应用程序级的广播消息。当一个广播被发送时,系...
    99+
    2023-08-11
    Android
  • Android 两种启动模式的实例详解
    Android 两种启动模式的实例详解Intent的FLAG_ACTIVITY_CLEAR_TOP和FLAG_ACTIVITY_REORDER_TO_FRONTActivity的两种启动模式:FLAG_ACTIVITY_CLEAR_TOP和...
    99+
    2023-05-30
    android 启动模式 roi
  • 详解Android中的ActivityThread和APP启动过程
    目录ActiviryThreadActivityThread的初始化主线程Looper的初始化主线程Handler的初始化ApplicationThread及Activity的创建和...
    99+
    2024-04-02
  • Android 12.0 系统开机自启动第三方app
    1.前言   在12.0的系统rom定制化开发中,由于有些第三方app,需要在接收到开机广播后,启动app,但是在10.0以后第三方app就接收不到开机广播了 只有系统app才可以接收到开机广播了,所以在app内通过接收开机广播自启动就没...
    99+
    2023-08-31
    android java framework 开机自启动 开机启动app
  • 详解Android壁纸服务的启动过程
    壁纸基础 android中的壁纸分为动态壁纸和静态壁纸两种,两种类型的壁纸都以Service的类型运行在系统后台。 静态壁纸:仅以图片的形式进行展示对于静态壁纸,可以使用W...
    99+
    2024-04-02
  • 详解Android性能优化之启动优化
    目录1、为什么要进行启动优化2、启动的分类2.1 冷启动2.2 热启动2.3 温启动3、优化方向4、启动时间的测量方式4.1 使用adb 命令方式(线下使用方便)4.2 手动打点方式...
    99+
    2024-04-02
  • 关于Android冷启动耗时优化详解
    目录1,背景2,调研2.1,Android中启动的方式2.2,冷启动流程2.3,启动时间3,方案1,冷启动白屏现象2,启动时间优化总结1,背景 最近开发了一个新的App,前期工期紧,...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作