背景:
在经常使用各种app时候,大家都会有一个体验:在点击打开app展示第一帧画面时候,发现往往第一帧画面显示的内容是无法点击的,一般这个画面是个默认的内容画面。
这第一帧画面显示后,一般过一会画面可能会重新进行刷新最新网络请求的画面,这时候画面才变成可以点击触摸交互的。甚至有的app第一帧画面就是显示完全空白内容,加载中等画面。等网络加载数据刷新完成后你才可以看到正常画面。

那么平时我们统计的冷启动时间,通过Displayed关键字过滤的一般都是第一帧画面时间Time to initial display (TTID),但是真真用户可以交互可以使用时间都不是TTID时间,那么如何统计或者展示出真真用户可以交互画面完全显示画面的时间Time to full display (TTFD)呢?
初步显示所用时间TTID
Time to initial display (TTID) 是指显示应用界面的第一帧所用的时间。此指标用于测量应用生成第一帧所用的时间,包括冷启动期间的进程初始化、冷启动或温启动期间的 activity 创建,以及显示第一帧。让用户看到应用快速启动,有助于缩短应用的 TTID,从而提升用户体验。Android 框架会自动为每个应用报告 TTID。针对应用启动进行优化时,我们建议您实现 reportFullyDrawn,以获取 TTFD 之前的信息。
TTID 的测量方式为时间值,表示包括以下事件序列的总经过时间:
启动进程。
初始化对象。
创建并初始化 activity。
膨胀布局。
首次绘制应用。
检索 TTID
如需查找 TTID,请在 Logcat 命令行工具中搜索包含名为 Displayed 的值的输出行。此值即为 TTID,类似于以下示例(其中 TTID 为 3 秒 534 毫秒):
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
如需在 Android Studio 中查找 TTID,请从过滤器下拉菜单中停用 Logcat 视图中的过滤器,然后找到 Displayed 时间,如图 5 所示。停用过滤器是必要的,因为提供此日志的是系统服务器,不是应用本身。

图 5. 停用的过滤器和 logcat 中的 Displayed 值。
在所有资源完全加载并显示之前,Logcat 输出中的 Displayed 指标不一定会捕获时间。它会省去布局文件中未引用的资源或被应用作为对象初始化一部分创建的资源。它之所以排除这些资源,是因为加载它们是一个内嵌进程,并且不会阻止应用的初步显示。
有时,Logcat 输出中的 Displayed 行中会包含用于显示总时间的附加字段。例如:
ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)
在这种情况下,第一个时间测量值仅针对第一个绘制的 activity。total 时间测量值是从应用进程启动时开始计算,并且可以包含首次启动但未在屏幕上显示任何内容的另一个 activity。total 时间测量值仅在单个 activity 的时间和总启动时间之间存在差异时才会显示。
我们建议您在 Android Studio 中使用 Logcat,但如果您不使用 Android Studio,也可以使用 adb shell activity manager 命令运行应用来测量 TTID。示例如下:
adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN
Displayed 指标和以前一样出现在 Logcat 输出中。您的终端窗口会显示以下内容:
Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete
-c 和 -a 为可选参数,可让您指定 和 。
完全显示所用时间 (TTFD)
Time to full display (TTFD) 是指应用达到可与用户互动的状态所用的时间。系统会报告显示应用界面的第一帧以及在显示第一帧后异步加载的内容所需的时间。一般情况下,这是从网络或磁盘加载的主要内容(由应用报告)。换句话说,TTFD 包括 TTID 以及应用可供使用所需的时间。通过让用户快速与您的应用互动,缩短应用的 TTFD 有助于提升用户体验。
当 Choreographer 调用 activity 的 onDraw() 方法时,系统会确定 TTID,并且在知道这是首次调用此方法时也会确定 TTID。不过,由于每个应用的行为不同,系统不知道何时确定 TTFD。如需确定 TTFD,应用需要在达到完全绘制状态时向系统发出信号。
查找TTFD
如需查找 TTFD,请调用 ComponentActivity 的 reportFullyDrawn() 方法,以发出完全绘制状态信号。reportFullyDrawn 方法会报告应用何时完全绘制且处于可用状态。TTFD 是指从系统收到应用启动 intent 到调用 reportFullyDrawn() 所用的时间。如果您不调用 reportFullyDrawn(),则系统不会报告任何 TTFD 值。
如需衡量 TTFD,请在完全绘制界面和所有数据后调用 reportFullyDrawn()。请勿在系统首次绘制并显示第一个 activity 的窗口之前调用 reportFullyDrawn(),因为此时系统会报告系统测量的时间。也就是说,如果您在系统检测到 TTID 之前调用 reportFullyDrawn(),系统会将 TTID 和 TTFD 报告为相同的值,并且此值为 TTID 值。
使用 reportFullyDrawn() 时,Logcat 会显示类似以下示例的输出,其中 TTFD 为 1s54ms:
system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
Logcat 输出有时包含 total 时间,如初步显示所用时间中所述。
如果您的显示时间比希望的时间长,则可以尝试识别启动过程中的瓶颈。
在您知道已达到完全绘制状态的基本情况下,您可以使用 reportFullyDrawn() 来指示完全绘制状态。不过,如果后台线程必须在达到完全绘制状态之前完成后台工作,您需要延迟 reportFullyDrawn(),以便更准确地衡量 TTFD。如需了解如何延迟 reportFullyDrawn(),请参阅以下部分。
提高启动时间准确性
如果您的应用正在执行延迟加载,并且初始显示不包含所有资源(例如,当您的应用从网络提取图片时),您可能需要延迟调用 reportFullyDrawn,直到应用可供使用后再调用,以便将列表填充时间纳入基准时间的计算范围。
例如,如果界面包含一个动态列表(如 RecyclerView)或延迟列表,那么可能就要在首次绘制列表之后、因此也就是在界面被标记为完全绘制之后,才通过后台任务来填充该列表。在这种情况下,基准测试不涵盖列表填充。
实战展示如何使用显示TTFD时间
正常不考虑TTFD时间,默认会有TTID时间
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search_activity);
final Button search = findViewById(R.id.search_btn);
//用正常的postDelay 500ms模拟网络请求到数据然后进行ui更新
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
//只有数据请求到了,相关button才可以被点击,才是真正用户可交互可用状态
search.setEnabled(true);
}
},500);
}
然后进行抓取log查看,可以看到确实是有TTID时间,过滤Displayed关键字就可以
adb logcat -c;adb logcat | grep -E "Fully|Displayed"
01-15 11:31:15.059 851 873 I ActivityTaskManager: Displayed com.example.linjw.dagger2demo/.SearchActivity for user 10: +1s18ms
那么如果要把网络请求后ui刷新用户真正可以交互的时间也算进去,那就需要app自行添加相关的report接口,添加方式如下:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search_activity);
final Button search = findViewById(R.id.search_btn);
//用正常的postDelay 500ms模拟网络请求到数据然后进行ui更新
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
//只有数据请求到了,相关button才可以被点击,才是真正用户可交互可用状态
search.setEnabled(true);
reportFullyDrawn();
Log.i("Fully","------------reportFullyDrawn------------");
}
},500);
}
然后再进行日志查看,这次注意要关注Fully这个关键字对应日志就是代表TTFD完全显示的时间:
adb logcat -c;adb logcat | grep -E “Fully|Displayed”
01-15 11:34:39.439 851 873 I ActivityTaskManager: Displayed com.example.linjw.dagger2demo/.SearchActivity for user 10: +790ms
01-15 11:34:39.800 851 873 I ActivityTaskManager: Fully drawn com.example.linjw.dagger2demo/.SearchActivity: +1s160ms
补充个疑问解释:
大家会说为啥直接app调用reportFullyDrawn()方法时候打个日志不就行了,看日志的时间就好了。
其实这个疑问也也很正常,但是大家想一想调用了reportFullyDrawn()是可以从app冷启动的源头计算到完全显示的这一段时间耗时,而不是简单的只是在末尾处有个log,这样你也不知道冷启动源头时间点是多少哈。
原文地址:
https://mp.weixin.qq.com/s/NXX_oKBIMNslUD6Dl8-iEA
更多framework实战开发干货,请关注下面“千里马学框架”
2018

被折叠的 条评论
为什么被折叠?



