前言
启动时间/页面加载(Activity/Fragment)时间统计的话,如果需要精确统计,一般都是在业务代码上插桩,或者从用户体检角度看的话,则是通过录制视频再做图像对比。这样灵活性都比较差,而且每个业务模块都需要自己去插桩,增加了复杂度。在这里,提供一种在Android生命周期里提供的注入点的一种方案 - 即基于实现ActivityLifecycleCallbacks跟FragmentLifecycleCallbacks的回调接口,从而达到统计启动时间跟页面加载时间的方法。如果不需要测的太细,只需要监听Activity的生命周期即可,因为Fragment需要绑定在Activity的生命周期内。
由于本人对Android内部运行机制了解尚浅,关于统计的起始结束点,如果有争议,欢迎指出,如果合理,我会做出对应修正。文章源自玩技e族-https://www.playezu.com/497946.html
博客原文地址:http://www.coderlife.site/android/2018/08/12/android-starttime.html文章源自玩技e族-https://www.playezu.com/497946.html
一.需要提前了解的知识点
1.Activity/Fragment生命周期
文章源自玩技e族-https://www.playezu.com/497946.html
文章源自玩技e族-https://www.playezu.com/497946.html
2.LifecycleCallbacks接口说明
Application通过ActivityLifecycleCallbacks使用接口提供了一套回调方法,用于让开发者对Activity的生命周期事件进行集中处理。 ActivityLifecycleCallbacks接口回调可以简化监测Activity的生命周期事件,在一个类中作统一处理。 ActivityLifecycleCallbacks使用要求API 14+(Android 4.0+)。文章源自玩技e族-https://www.playezu.com/497946.html
(1)Application.ActivityLifecycleCallbacks接口定义如下文章源自玩技e族-https://www.playezu.com/497946.html
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抽象类定义如下文章源自玩技e族-https://www.playezu.com/497946.html
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加载时间计算
1.LauchActivity启动时间统计
(1)LauchActivity首次启动的起始点
首次 lauchActivity 启动点放置在 SDK 初始化的流程中,在这个阶段,将ActivityLifecycleCallbacks注册进去文章源自玩技e族-https://www.playezu.com/497946.html
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焦点的时间,作为结束点文章源自玩技e族-https://www.playezu.com/497946.html
@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); } ... } ... } ... } } }
2.普通Activity的首次启动时间统计
(1)普通Activity启动起始点
目前时间统计放在onActivityCreated中,其实应该更靠前一点,但没有找到比较合适的hook点文章源自玩技e族-https://www.playezu.com/497946.html
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 { ... } } } } } }
3.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加载时间统计其实算是页面加载的细化处理。
1.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()); ... } } ... } } } }
2.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()); } }
未知地区 1F
欢迎大家参与讨论!