APM框架Matrix源码分析(十一)StartupTracer之启动耗时监控

549 阅读5分钟

TracePlugin初始化时会调用StartupTracer构造函数

public StartupTracer(TraceConfig config) {
    //用户配置的splash页
    this.splashActivities = config.getSplashActivities();
    //监听Application创建完成回调
    ActivityThreadHacker.addListener(this);
}

ActivityThreadHacker是怎么监听到application的创建完成的呢?

AppMethodBeat第一次执行i方法时,会执行realExecute 方法,里面调用了ActivityThreadHacker.hackSysHandlerCallback(),这个方法只会执行一次。

hackSysHandlerCallback

hackSysHandlerCallback顾名思义就是hook Handler的Callback来拦截Handler消息。

public static void hackSysHandlerCallback() {
        try {
            //将第一个方法(Application的attachBaseContext)作为app启动的时间点并记录下来
            sApplicationCreateBeginTime = SystemClock.uptimeMillis();
            //通过maskIndex用来标记获取堆栈信息的起点,便于回溯
            sApplicationCreateBeginMethodIndex = AppMethodBeat.getInstance().maskIndex("ApplicationCreateBeginMethodIndex");
            Class<?> forName = Class.forName("android.app.ActivityThread");
            Field field = forName.getDeclaredField("sCurrentActivityThread");
            field.setAccessible(true);
            Object activityThreadValue = field.get(forName);
            //通过反射拿到ActivityThread对象的mH
            Field mH = forName.getDeclaredField("mH");
            mH.setAccessible(true);
            Object handler = mH.get(activityThreadValue);
            //拿到mH的父类Handler
            Class<?> handlerClass = handler.getClass().getSuperclass();
            if (null != handlerClass) {
                //获取Handler成员变量mCallback
                Field callbackField = handlerClass.getDeclaredField("mCallback");
                callbackField.setAccessible(true);
                Handler.Callback originalCallback = (Handler.Callback) callbackField.get(handler);
                //包装成HackCallback进行代理
                HackCallback callback = new HackCallback(originalCallback);
                callbackField.set(handler, callback);
            }

            MatrixLog.i(TAG, "hook system handler completed. start:%s SDK_INT:%s", sApplicationCreateBeginTime, Build.VERSION.SDK_INT);
        } catch (Exception e) {
            MatrixLog.e(TAG, "hook system handler err! %s", e.getCause().toString());
        }
    }

将app启动时间点记录为sApplicationCreateBeginTime

通过反射拿到ActivityThread对象的mH,mH继承自Handler,然后拿到Handler的mCallback,包装成HackCallback进行代理,消息的handleMessage就会被HackCallback拦截到。

	@Override
	public boolean handleMessage(Message msg) {
            //开关
            if (!AppMethodBeat.isRealTrace()) {
                //关闭则直接回调原来的handleMessage,不改变逻辑
                return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
            }
	         //是否是启动页
            boolean isLaunchActivity = isLaunchActivity(msg);
		 //isCreated避免重复调用
            if (!isCreated) {
                //当消息是启动页,Service,Receiver表示Application创建已执行完毕
                if (isLaunchActivity || msg.what == CREATE_SERVICE
                        || msg.what == RECEIVER) { // todo for provider
                    //记录Application创建完毕的时间点
                    ActivityThreadHacker.sApplicationCreateEndTime = SystemClock.uptimeMillis();
                    //记录消息类型
                    ActivityThreadHacker.sApplicationCreateScene = msg.what;
                    isCreated = true;
                    sIsCreatedByLaunchActivity = isLaunchActivity;
                    MatrixLog.i(TAG, "application create end, sApplicationCreateScene:%d, isLaunchActivity:%s", msg.what, isLaunchActivity);
                    synchronized (listeners) {
                        for (IApplicationCreateListener listener : listeners) {
                            //回调onApplicationCreateEnd
                            listener.onApplicationCreateEnd();
                        }
                    }
                }
            }
            //回调原来的handleMessage
            return null != mOriginalCallback && mOriginalCallback.handleMessage(msg);
	}

在app的启动流程中,Activity,Service,Receiver组件的启动是通过ActivityThread中mH去处理的,组件启动表示Application创建完毕,记录Application创建完毕的时间点为sApplicationCreateEndTime,并回调onApplicationCreateEnd。

接口看下isLaunchActivity如何判断是启动页消息呢?

可以先回顾下:Activity的启动流程

在ApplicationThread > Activity阶段,会通过sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction) 发送消息给H,该消息中ClientTransaction的mActivityCallbacks包含用于请求启动Activity的LaunchActivityItem。

	private boolean isLaunchActivity(Message msg) {
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O_MR1) {
                if (msg.what == EXECUTE_TRANSACTION && msg.obj != null) {
                    try {
                       //ClientTransaction用于辅助管理生命周期
                        if (null == method) {
                            Class clazz = Class.forName("android.app.servertransaction.ClientTransaction");
                           //拿到ClientTransaction的mActivityCallbacks
                            method = clazz.getDeclaredMethod("getCallbacks");
                            method.setAccessible(true);
                        }
                        List list = (List) method.invoke(msg.obj);
                        //判断 Request to launch an activity
                        if (!list.isEmpty()) {
                            return list.get(0).getClass().getName().endsWith(".LaunchActivityItem");
                        }
                    } catch (Exception e) {
                        MatrixLog.e(TAG, "[isLaunchActivity] %s", e);
                    }
                }
                //否则判断消息LAUNCH_ACTIVITY
                return msg.what == LAUNCH_ACTIVITY;
            } else {
                //低版本直接判断消息是LAUNCH_ACTIVITY或RELAUNCH_ACTIVITY
                return msg.what == LAUNCH_ACTIVITY || msg.what == RELAUNCH_ACTIVITY;
            }
		}

这样就得到了Application创建完成的回调onApplicationCreateEnd

onApplicationCreateEnd

	 @Override
    public void onApplicationCreateEnd() {
      	//如果app没有Activity,上报应用启动
      	if (!isHasActivity) {
            // sApplicationCreateEndTime - sApplicationCreateBeginTime
            long applicationCost = ActivityThreadHacker.getApplicationCost();
            MatrixLog.i(TAG, "onApplicationCreateEnd, applicationCost:%d", applicationCost);
            analyse(applicationCost, 0, applicationCost, false);
        }
    }

接着看Tracer生命周期的onAlive

@Override
protected void onAlive() {
    super.onAlive();
    MatrixLog.i(TAG, "[onAlive] isStartupEnable:%s", isStartupEnable);
    if (isStartupEnable) {
        //监听Activity的onWindowFocusChanged回调(插桩at)
        AppMethodBeat.getInstance().addListener(this);
        //通过Application注册了所有activity的生命回调
        Matrix.with().getApplication().registerActivityLifecycleCallbacks(this);
    }
}

通过registerActivityLifecycleCallbacks来监听Activity的生命周期,OnCreate时会回调onActivityCreated

onActivityCreated

//coldCost为0说明这个值没被改动过,表示冷启动
private long coldCost = 0;
//当前Activity计数,onActivityCreated时+1,onActivityDestroyed时-1
private int activeActivityCount;
//是否是温启动
private boolean isWarmStartUp;
//上一个Activity创建的时间点
private long lastCreateActivity = 0L;
//map存Activity创建时的时间点,因为可能存在多个Activity
private HashMap<String, Long> createdTimeMap = new HashMap<>();
//是否统计耗时开关
private boolean isShouldRecordCreateTime = true;

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
    MatrixLog.i(TAG, "activeActivityCount:%d, coldCost:%d", activeActivityCount, coldCost);
    //Activity个数为0 && coldCost改动过,说明是温启动
    if (activeActivityCount == 0 && coldCost > 0) {
        lastCreateActivity = uptimeMillis();
        MatrixLog.i(TAG, "lastCreateActivity:%d, activity:%s", lastCreateActivity, activity.getClass().getName());
        //温启动标记置为true
        isWarmStartUp = true;
    }
  	//Activity计数自增
    activeActivityCount++;
    if (isShouldRecordCreateTime) {
        //记录Activity创建时对应的时间点
        createdTimeMap.put(activity.getClass().getName() + "@" + activity.hashCode(), uptimeMillis());
    }
}

@Override
public void onActivityDestroyed(Activity activity) {
    MatrixLog.i(TAG, "activeActivityCount:%d", activeActivityCount);
    //Activity计数自减
    activeActivityCount--;
}

温启动判断:当前Activity个数为0说明需要重新创建Activity,并且coldCost不是初始值0说明app进程依然在。

将Activity的启动时间点存到createdTimeMap中,在Activity的onWindowFocusChanged执行时,会回调onActivityFocused作为Activity启动完成的时间点,然后计算差值就是Activity启动耗时。

onActivityFocused

结合着官方注释看下面代码

startup_tracer.png

	@Override
	public void onActivityFocused(Activity activity) {
       //正常情况下会执行 ActivityThreadHacker.sApplicationCreateScene = msg.what覆盖默认值
        if (ActivityThreadHacker.sApplicationCreateScene == Integer.MIN_VALUE) {
            Log.w(TAG, "start up from unknown scene");
            return;
        }
	     //获取当前回调的activityName
        String activityName = activity.getClass().getName();
       //coldCost为0表示冷启动
        if (isColdStartup()) {
            //是否时候启动页
            boolean isCreatedByLaunchActivity = ActivityThreadHacker.isCreatedByLaunchActivity();
		  //从createdTime取出Activity创建开始时间点
            String key = activityName + "@" + activity.hashCode();
            Long createdTime = createdTimeMap.get(key);
            if (createdTime == null) {
                createdTime = 0L;
            }
            // Activity启动耗时 = 当前时间点-开始时间点,复用并存到createdTimeMap
            createdTimeMap.put(key, uptimeMillis() - createdTime);
		  //firstScreenCost为初始值0,表示第一个获取焦点的Activity,记录时间差为首屏启动耗时
            if (firstScreenCost == 0) {
                //当前时间 - sApplicationCreateBeginTime
                this.firstScreenCost = uptimeMillis() - ActivityThreadHacker.getEggBrokenTime();
            }
            //如果配置了Splash页并且已经展示过,也就是当前是'careActivity',记录此时的时间差为冷启动耗时,见上图的coldCost
            if (hasShowSplashActivity) {
                coldCost = uptimeMillis() - ActivityThreadHacker.getEggBrokenTime();
            } else {
              	//如果当前Activity是Splash页,hasShowSplashActivity置为true,此时coldCost还是为0
                if (splashActivities.contains(activityName)) {
                    hasShowSplashActivity = true;
                } else {
                    //如果没有配置Splash页的情况
                  
                    if (isCreatedByLaunchActivity) {
                        //如果启动的是Activity,记录首屏耗时为冷启动耗时
                        coldCost = firstScreenCost;
                    } else {
                        //冷启动启动的不是Activity,记录applicationCost为冷启动耗时
                        firstScreenCost = 0;
                        coldCost = ActivityThreadHacker.getApplicationCost();
                    }
                }
            }
            //冷启动耗时分析
            if (coldCost > 0) {
                Long betweenCost = createdTimeMap.get(key);
                //过滤异常情况
                if (null != betweenCost && betweenCost >= 30 * 1000) {
                    MatrixLog.e(TAG, "%s cost too much time[%s] between activity create and onActivityFocused, "
                            + "just throw it.(createTime:%s) ", key, uptimeMillis() - createdTime, createdTime);
                    return;
                }
                analyse(ActivityThreadHacker.getApplicationCost(), firstScreenCost, coldCost, false);
            }

        } else if (isWarmStartUp()) {
            //温启动耗时分析,温启动时间 = 当前时间 - 温启动onActivityCreated时记录的开始时间点lastCreateActivity
            isWarmStartUp = false;
            long warmCost = uptimeMillis() - lastCreateActivity;
            MatrixLog.i(TAG, "#WarmStartup# activity:%s, warmCost:%d, now:%d, lastCreateActivity:%d", activityName, warmCost, uptimeMillis(), lastCreateActivity);

            if (warmCost > 0) {
                analyse(0, 0, warmCost, true);
            }
        }
    }

接着analyse根据冷启动和温启动耗时大于等于阈值获取对应的堆栈信息并上报,上一篇文章(APM框架Matrix源码分析(十)EvilMethodTracer之慢函数监控)分析过不再分析了。

小结

  • applicationCost(Application创建耗时):Application的attachBaseContext到ActivityThread中的mH收到组件启动的msg

  • coldCost(冷启动耗时):Application的attachBaseContext到第一个careActivity的onWindowFocusChanged(配置的Splash页启动也算在这个时间内)

  • warmCost(温启动耗时):Launcher Activity的启动耗时