启动时间/页面加载(Activity
/Fragment
)时间统计的话,如果需要精确统计,一般都是在业务代码上插桩,或者从用户体检角度看的话,则是通过录制视频再做图像对比。这样灵活性都比较差,而且每个业务模块都需要自己去插桩,增加了复杂度。在这里,提供一种在Android
生命周期里提供的注入点的一种方案 - 即基于实现ActivityLifecycleCallbacks
跟FragmentLifecycleCallbacks
的回调接口,从而达到统计启动时间跟页面加载时间的方法。如果不需要测的太细,只需要监听Activity
的生命周期即可,因为Fragment
需要绑定在Activity
的生命周期内。
由于本人对Android
内部运行机制了解尚浅,关于统计的起始结束点,如果有争议,欢迎指出,如果合理,我会做出对应修正。
博客原文地址:http://www.coderlife.site/android/2018/08/12/android-starttime.html
Activity/Fragment
生命周期LifecycleCallbacks
接口说明Application
通过ActivityLifecycleCallbacks
使用接口提供了一套回调方法,用于让开发者对Activity
的生命周期事件进行集中处理。 ActivityLifecycleCallbacks
接口回调可以简化监测Activity
的生命周期事件,在一个类中作统一处理。 ActivityLifecycleCallbacks
使用要求API 14+
(Android 4.0+
)。
(1)Application.ActivityLifecycleCallbacks
接口定义如下
public interface ActivityLifecycleCallbacks {
void onActivityCreated(Activity activity, Bundle savedInstanceState);
void onActivityStarted(Activity activity);
void onActivityResumed(Activity activity);
void onActivityPaused(Activity activity);
void onActivityStopped(Activity activity);
void onActivitySaveInstanceState(Activity activity, Bundle outState);
void onActivityDestroyed(Activity activity);
}
(2)FragmentManager.FragmentLifecycleCallbacks
抽象类定义如下
public abstract static class FragmentLifecycleCallbacks {
public void onFragmentPreAttached(FragmentManager fm, Fragment f, Context context) {}
public void onFragmentAttached(FragmentManager fm, Fragment f, Context context) {}
public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {}
public void onFragmentActivityCreated(FragmentManager fm, Fragment f,
Bundle savedInstanceState) {}
public void onFragmentViewCreated(FragmentManager fm, Fragment f, View v,
Bundle savedInstanceState) {}
public void onFragmentStarted(FragmentManager fm, Fragment f) {}
public void onFragmentResumed(FragmentManager fm, Fragment f) {}
public void onFragmentPaused(FragmentManager fm, Fragment f) {}
public void onFragmentStopped(FragmentManager fm, Fragment f) {}
public void onFragmentSaveInstanceState(FragmentManager fm, Fragment f, Bundle outState) {}
public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {}
public void onFragmentDestroyed(FragmentManager fm, Fragment f) {}
public void onFragmentDetached(FragmentManager fm, Fragment f) {}
}
Activity
加载时间计算LauchActivity
启动时间统计(1)LauchActivity
首次启动的起始点
首次 lauchActivity 启动点放置在 SDK 初始化的流程中,在这个阶段,将ActivityLifecycleCallbacks
注册进去
private void init(){
long time = System.currentTimeMillis();
env = new Env.Builder().setAppStartTime(time)
.setBootActivity(AppUtils.getLauncherActivity(this.mContext))
.build();
...
// Activity生命周期监听注册
if (mContext instanceof Application) {
((Application) mContext).registerActivityLifecycleCallbacks(this);
}
fragmentLifeCallbacks = new FragmentLifeCallbacks(fragmentInfos);
Stats.IS_ROOT = RootUtil.isRooted();
}
(2)LauchActivity
首次启动的结束点
在onActivityStarted
的回调方法中实现当前view
的回调方法onWindowFocusChanged
,通过获取view
焦点的时间,作为结束点
@Override
public void onActivityStarted(Activity activity) {
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
view.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean hasFocus) {
if (hasFocus) {
if (activityName.equals(env.getLaunchActivity()) && pageInfo.isFirstStart()){
bootCost = System.currentTimeMillis()-env.getAppStartTime();
Log.d(MonitorType.LOG_TYPE_PAGE_LOAD_TIME,
activityName+"的lanchActivity启动时间: " + bootCost);
}
...
}
...
}
...
}
}
}
Activity
的首次启动时间统计(1)普通Activity
启动起始点
目前时间统计放在onActivityCreated
中,其实应该更靠前一点,但没有找到比较合适的hook
点
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
...
PageInfo pageInfo = pageMap.get(activityName);
if (pageInfo == null){
pageInfo = new PageInfo();
pageInfo.setFirstStart(true);
pageInfo.setFirstCreateTime(System.currentTimeMillis());
pageInfo.setActivityName(activityName);
pageMap.put(activityName,pageInfo);
} else {
...
}
if (pageInfo.getFragmentInfos().isEmpty()){
pageInfo.setFragmentInfos(fragmentInfos);
}
}
(2)普通Activity
启动结束点
@Override
public void onActivityStarted(Activity activity) {
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
view.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean hasFocus) {
if (hasFocus) {
...
if (pageInfo != null){
if (pageInfo.isFirstStart()){
if (!pageInfo.getActivityName().equals(env.getLaunchActivity())){
// 如果当前activityName不等于launchActivity时
pageInfo.setFirstLoadTime(System.currentTimeMillis()-pageInfo.getFirstCreateTime());
Log.d(MonitorType.LOG_TYPE_PAGE_LOAD_TIME,
activityName+"的首次加载时间: " + pageInfo.getFirstLoadTime());
}else{
...
}
pageInfo.setStartCount(1);
pageInfo.setFirstStart(false);
pageInfo.setBootIndex(++bootIndex);
} else {
...
}
}
}
}
});
}
}
Activity
的非首次启动时间统计(1)Activity
非首次启动的起始点
当Activity
非首次启动时,会先执行onActivityResumed
,我们只要将ActivityName
对应的PageInfo
对象做判断,如果不是首次启动,则可以将此处可以作为我们的起始时间点计算
@Override
public void onActivityResumed(Activity activity) {
...
PageInfo resumePageInfo = pageMap.get(activity.getClass().getName());
if (resumePageInfo != null && !resumePageInfo.isFirstStart()){
resumePageInfo.setCreateTime(System.currentTimeMillis());
}
}
(2)Activity
非首次启动的结束点
结束点位置差不多,同样是在onActivityStarted
中view
的焦点获取到的回调方法中统计
@Override
public void onActivityStarted(Activity activity) {
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
view.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean hasFocus) {
if (hasFocus) {
...
if (pageInfo != null){
if (pageInfo.isFirstStart()){
...
} else {
// 非启动的activity结束点
pageInfo.setLoadTime(System.currentTimeMillis()-pageInfo.getCreateTime());
Log.d(MonitorType.LOG_TYPE_PAGE_LOAD_TIME,
activityName+"的第"+ pageInfo.getStartCount() +"次加载时间: " + pageInfo.getLoadTime());
pageInfo.setStartCount(pageInfo.getStartCount()+1);
pageInfo.setBootIndex(++bootIndex);
}
}
}
}
}
Fragment
加载时间计算由于Fragment
生命周期是绑定在Activity
中的,因此Fragment
加载时间统计其实算是页面加载的细化处理。
Fragment
页面首次加载时间统计(1)起始点
起始点放在onFragmentPreAttached
中,所有Fragment
首次启动都会进行Activity
绑定
public void onFragmentPreAttached(FragmentManager fm, android.support.v4.app.Fragment f, Context context) {
// Fragment首次启动
curFragmentName = f.getClass().getName();
// Activity的Fragment首次启动时还没有来得及setFragment属性
if (!mFragmentInfos.containsKey(curFragmentName)){
FragmentInfo aFragmentInfo = new FragmentInfo();
aFragmentInfo.setFirstCreateTime(System.currentTimeMillis());
aFragmentInfo.setIsFirstBoot(true);
aFragmentInfo.setFragmentName(curFragmentName);
mFragmentInfos.put(curFragmentName, aFragmentInfo);
}
(2)结束点
在Fragment
中,也可以用获取焦点的方式判断Fragment
是否加载完成
public void onFragmentViewCreated(FragmentManager fm, android.support.v4.app.Fragment f, View v,
final Bundle savedInstanceState) {
...
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
view.getViewTreeObserver().addOnWindowFocusChangeListener(new ViewTreeObserver.OnWindowFocusChangeListener() {
@Override
public void onWindowFocusChanged(boolean hasFocus) {
if (hasFocus) {
if (Stats.topActivityName != null){
...
if (mFragmentInfos.get(curFragmentName).getIsFirstBoot()){
mFragmentInfos.get(curFragmentName).setFirstLoadTime(
System.currentTimeMillis() - mFragmentInfos.get(curFragmentName).getFirstCreateTime());
Log.d(MonitorType.LOG_TYPE_PAGE_LOAD_TIME,
"fragmnet=>" + curFragmentName + " 首次启动时间: "
+ mFragmentInfos.get(curFragmentName).getFirstLoadTime());
...
}
}
...
}
}
});
}
}
Fragment
页面非首次加载时间统计(1)起始点
首次启动会先执行onFragmentPreAttached
,而保存状态后的Fragment
不会,因此起始时间点为onFragmentAttached
public void onFragmentAttached(FragmentManager fm, android.support.v4.app.Fragment f, Context context) {
// 非首次启动起始时间点(onFragmentPreAttached[首次]->onFragmentAttached)
curFragmentName = f.getClass().getName();
if(!mFragmentInfos.get(curFragmentName).getIsFirstBoot()){
mFragmentInfos.get(curFragmentName).setCreateTime(System.currentTimeMillis());
}
}
(2)结束点
经过debug
,并没有重新绘制的流程。因此非首次启动的结束点在onFragmentStarted
,而不是onFragmentViewCreated
。
public void onFragmentStarted(FragmentManager fm, android.support.v4.app.Fragment f) {
// 已保存状态的首次启动结束点
curFragmentName = f.getClass().getName();
if (!mFragmentInfos.get(curFragmentName).getIsFirstBoot()){
mFragmentInfos.get(curFragmentName).setLoadTime(System.currentTimeMillis() - mFragmentInfos.get(curFragmentName).getCreateTime());
Log.d(MonitorType.LOG_TYPE_PAGE_LOAD_TIME,
"fragmnet=>" + curFragmentName + "非首次加载时间为: " + mFragmentInfos.get(curFragmentName).getFirstLoadTime());
}
}