先看一个简单的代码示例,演示如何集成360 Argus APM:
public class ArgusAPMApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
boolean isUi = TextUtils.equals(getPackageName(), ProcessUtils.getCurrentProcessName());
Config.ConfigBuilder builder = new Config.ConfigBuilder()
.setAppContext(this)
.setRuleRequest(new RuleSyncRequest())
.setUpload(new CollectDataSyncUpload())
.setAppName("apm_demo")
.setAppVersion("1.0.0")
.setApmid("apm_demo");//该ID是在APM的后台进行申请的
//单进程应用可忽略builder.setDisabled相关配置。
if (!isUi) { //除了“主进程”,其他进程不需要进行数据上报、清理等逻辑。“主进程”通常为常驻进行,如果无常驻进程,即为UI进程。
builder.setDisabled(ApmTask.FLAG_DATA_CLEAN)
.setDisabled(ApmTask.FLAG_CLOUD_UPDATE)
.setDisabled(ApmTask.FLAG_DATA_UPLOAD)
.setDisabled(ApmTask.FLAG_COLLECT_ANR)
.setDisabled(ApmTask.FLAG_COLLECT_FILE_INFO);
}
//builder.setEnabled(ApmTask.FLAG_COLLECT_ACTIVITY_AOP); //activity采用aop方案时打开,默认关闭即可。
builder.setEnabled(ApmTask.FLAG_LOCAL_DEBUG); //是否读取本地配置,默认关闭即可。
Client.attach(builder.build());
Client.isDebugOpen(false);//设置成true的时候将会打开悬浮窗
Client.startWork();
}
}
Config类对Argus进行一些基本的配置,其中IRuleRequest向云端请求规则,IUpload则对采集的数据进行上传。Client.startWork()
会调用Manager.getInstance().startWork()
,Manager.getInstance().startWork()
又通过调用TaskManager.getInstance().startWorkTasks()
。Manager.getInstance().startWork()
的代码如下:
public void startWorkTasks() {
if (taskMap == null) {
LogX.d(Env.TAG, SUB_TAG, "taskMap is null ");
return;
}
if (taskMap.get(ApmTask.TASK_ACTIVITY).isCanWork()) {
// 云控为TaskConfig.ACTIVITY_TYPE_NONE,则本地开关优先
int type = ArgusApmConfigManager.getInstance().getArgusApmConfigData().controlActivity;
if (type == TaskConfig.ACTIVITY_TYPE_NONE) {
if (Manager.getInstance().getConfig().isEnabled(ApmTask.FLAG_COLLECT_ACTIVITY_INSTRUMENTATION)) {
LogX.o("activity local INSTRUMENTATION");
InstrumentationHooker.doHook();
} else {
LogX.o("activity local aop");
}
} else if (type == TaskConfig.ACTIVITY_TYPE_INSTRUMENTATION) {
LogX.o("activity cloud INSTRUMENTATION");
InstrumentationHooker.doHook();
} else {
LogX.o("activity cloud type(" + type + ")");
}
}
List<ITask> taskList = getAllTask();
for (ITask task : taskList) {
if (!task.isCanWork()) {
continue;
}
if (DEBUG) {
LogX.d(Env.TAG, SUB_TAG, "start task " + task.getTaskName());
}
task.start();
}
}
taskMap是一个<String, ITask>
类型的HashMap,通过TAskManager类中的registerTask方法将各种task(fps,activity等)添加到taskMap中。public void registerTask()
被Manager.getInstance().init()
调用,可上溯到Client.attach(builder.build())
。startWorkTasks方法先判断是Activity任务是否可以work,如果可以,就从配置中获取Activity的收集方式,如果云控策略是TaskConfig.ACTIVITY_TYPE_NONE,那么就以本地的开关优先:如果本地配置了ApmTask.FLAG_COLLECT_ACTIVITY_INSTRUMENTATION,就以instrumentation方式收集,否则,以aop方式收集Activity数据。startWorkTasks()方法最后的for循环,逐个检查taskMap中的task是否可以work,如果可以,就调用task的方法start,开启任务。每种task都有自己的task()实现,后文会详细讨论。
我们先看一下InstrumentationHooker类的doHook()方法:
public static void doHook() {
try {
hookInstrumentation();
isHookSucceed = true;
} catch (Exception e) {
if (DEBUG) {
LogX.e(TAG, "InstrumentationHooker", e.toString());
}
}
}
doHook()方法会调用本类的hookInstrumentation()方法,如果调用成功,就把属性isHookSucceed设置成true。hookInstrumentation()的代码如下:
private static void hookInstrumentation() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
Class<?> c = Class.forName("android.app.ActivityThread");
Method currentActivityThread = c.getDeclaredMethod("currentActivityThread");
boolean acc = currentActivityThread.isAccessible();
if (!acc) {
currentActivityThread.setAccessible(true);
}
Object o = currentActivityThread.invoke(null);
if (!acc) {
currentActivityThread.setAccessible(acc);
}
Field f = c.getDeclaredField("mInstrumentation");
acc = f.isAccessible();
if (!acc) {
f.setAccessible(true);
}
Instrumentation currentInstrumentation = (Instrumentation) f.get(o);
Instrumentation ins = new ApmInstrumentation(currentInstrumentation);
f.set(o, ins);
if (!acc) {
f.setAccessible(acc);
}
}
hookInstrumentation()方法通过反射的方式获取当前ActivityThread的mInstrumentation属性的实例,返回给currentInstrumentation,并且用currentInstrumentation初始化一个ApmInstrumentation的实例ins,最后用ins的替换到当前ActivityThread中mInstrumentation的值,实现hook。ApmInstrumentation类的实现在后文详细分析。
Instrumentation方式是一种在运行期进行hook的方式。
Activity的另一种采集方式是AOP,在编译过程织入代码进行hook。后文详细分析。