360 Argus APMソース分析(3)——argus-apm-aopソース分析
Argus-apm-aopソース分析
Argus-apm-aopは主にActivity向けの接面とHttpClientとURLConnection向けのスライスを実現した.コードを読むにはAOPとaspectjの基礎知識が必要です. TraceActivity TraceActivityクラスはActivity(Applicationもある)に対する接面を実現する.
@Aspect注記はこれがaspectjの接面クラスであることを示している.最初のPointCutでは、com.argusapm.android.aopとcom.argusapm.android.core.job.activityのすべての接点を除外します.2番目のPointCutはandroid.app.ApplicationのonCreateメソッドを接点として指定し、メソッドのパラメータを取得します.ApplicationOnCreateAdviceメソッドにはAfter注釈があり、前に定義したアプリケーションOnCreateはandroid.app.Application.onCreateで実行された最後に、コード:AH.applicationOnCreate(context)を織り込む.この方法は実際には1行のlogしか印刷されていない.アプリケーションAttachBaseContextAdviceメソッドには、android.app.Application.attachBaseContextメソッドに接点があるBefore注記があります.したがって、このメソッドの実行の開始時に、AH.アプリケーションAttachBaseContext(context)を呼び出す必要があります.この方法は現在のシステム時間を取得し,Activity CoreのappAttachTime属性に保存し,1行のlogも印刷する.activityOnXXXAdviceメソッドには、android.app.Activityのonで始まる一連のコールバックメソッドに接点があり、前に定義したbaseCondition、すなわち指定した2つのパッケージの下にないクラスを満たす必要があります.Around注記なので、接点前後で織り込み作業が行われます.ProceedingJoinPoint.getTarget()からtarget Activityのオブジェクトactivityを取得し、現在のシステム時間startTimeを取得します.次に、接点の元のコードを実行します.Activityのcanonical nameと接点関数の署名を取得し,さらに関数の名前を得る.activityのcanonical nameと関数の署名が空の文字列ではなく、関数署名の文字列にactivityのcanonical nameが含まれている場合は、AH.invoke(activity,startTime,methodName,sign)を呼び出します.AH.invoke(activity,startTime,methodName,sign)関数のコードは次のとおりです.
まずActivity taskが実行されているかどうかを判断します.Activity task運転の目安は、ApmTask.FLAG_COLLECT_ACTIVITY_AOPタグが設定され、TaskManagerリストにApmTask.TASK_がありますACTIVITYタイプのtaskであり、このtaskのmIsCanWorkのタグはtrueである.Activity taskが実行されていない場合は、直接戻ります.3番目のパラメータlifeCycleが「onCreate」、すなわち現在実行されているActivityのonCreateコールバック関数であれば、Activity Core.onCreateInfo(activity,startTime)を呼び出します.他のライフサイクルでは、パラメータlifeCycleから対応するlifecycleの整数定数を変換する必要があります.取得した値は適切な範囲内でなければなりません(Activity Info.TYPE_UNKNOWNより大きく、Activity Info.TYPE_DESTROY以下).適切なライフサイクルにあるメソッドコールバックの場合、Activity Core.saveActivity Info(Activity,Activity Info.HOT_START,System.currentTimeMillis(-startTime,lc)を呼び出します.以下はActivity Core.onCreateInfoのコードです.
まず、コールドスタートかホットスタートかを判断します.ActivityのDecorViewに新しいFirstFrameRunnableを送信します.現在時刻curTimeを記録します.これがonCreateの時刻です.saveActivity Info(activity,startType,curTime-startTime,Activity Info.TYPE_CREATE)を呼び出し、onCreateに関する情報を保存します.以下はsaveActivity Infoのコードです.
1番目のパラメータは現在のActivityで、2番目のパラメータは起動タイプ(コールド起動かホット起動か)です.3つ目はライフサイクルの消費時間です.4つ目は現在のライフサイクルです.timeの値がActivityライフサイクルの最小データ収集間隔単位より小さい場合は、このデータは記録せずに無視します.プラグインの名前pluginNameとactivityの名前activityNameを取得します.activityInfoのデータをリセットし、activityInfoのactivityName,starを順に設定しますtType,time,lifecycleなどの属性の値.ApmTask.TASK_ACTIVITYのtaskを取得し、taskがnullでない場合task.save(activityInfo)を呼び出すActivity Infoの情報を保存します.現在debugモードで動作している場合は、Activity ParseTaskのparseメソッドを使用して、Activity Infoの内容を解析し、ブロードキャストによってフローティングウィンドウに送信する必要があります.ITaskは直接実装クラス、BaseTaskを持つインタフェースです.BaseTaskのsaveメソッドは、最終的にはIStorageのsaveメソッドを呼び出すことで、入力パラメータIInfoの保証を実現する必要があります保存します.ここで保存する機会はActivity Infoタイプのデータです.Activity InfoはBaseInfoから拡張されています.もちろん、BaseInfoはインタフェースIInfoの具体的な実装です.IStorage自体もインタフェースで、直接実装クラスTableStorageがあります.これは抽象クラスで、Activity Stroageはこのクラスを拡張しています.しかし、Activity Storageはsaveメソッドを複写していないので、私たちは直接実装クラスTableStorageを拡張することができます.Table Storageのsaveのコードを見てください.
まず、パラメータvalueをContentValueタイプに変換する必要があります.ここで実際に呼び出されるのはActivity Infoクラスのこのメソッドの実装です.valuesにBaseInfo.KEY_TIME_RECORDというkeyが含まれていない場合、現在のシステム時間をBaseInfo.KEY_TIME_RECORDとして保存します.アプリケーションのContentResolverで指定されたUriにvaluesを挿入します.ここでUriはcontent://{package name}.apm.storage/activity.ApmProviderクラスのinsertメソッドでは、saveDataToDB(new DbCache.InfoHolder(values,table.getTable Name())を呼び出してデータベースにデータを書き込みます.データの格納や読み込みなどの操作は後で詳しく分析します. TraceNetTrafficMonitorコードここには貼らない.baseCondition定義接点は、com.argusapm.android.aop,com.argusapm.android,com.argusapm.android.job.net.i,com.argusapm.android.core.job.net.i,com.argusapm.android.core.job.job.net.impl,com.qihoo 360.mobilesafe.mms.transaction.MmmmHttpClient,com.qi 360.mobilesafe.mm.mm.mm.com.qi 360.mobilesafee.mm.mm.s.transaction.MmshttpClientおよびそのサブクラス.httpClientExecuteOne定義接点:org.apache.http.HttpResponse org.apache.http.client.execute(org.apache.http.client.methods.HttpUriRequest)の呼び出しポイント、第1のパラメータはHttpClientとそのサブクラスであり、第2のパラメータはHttpUriRequestタイプである.同時にbaseConditionの接点要求を満たす.httpClientExecuteOneAdviceの注記はAroundであり、条件を満たす接点でQHC.execute(httpClient,request)を呼び出す.QHCのexecuteは、実装が大きく異なる関数のセットです.たとえば、ここで使用するコードは次のとおりです.
まず、ApmTask.TASK_NETが実行されているかどうかを確認し、実行されていない場合はclientのexecuteを直接呼び出し、実行されている場合はAopHttpClient.execute(client,request)を呼び出し、そのコードは次のようになります.
メソッドのコアはhandleRequestとhandleResponseです.まずhandleRequestのコードを見てみましょう.
requestからURIを取得し、data中のURLを設定します.requestのタイプがHttpEntity EnclosingRequestでない場合は、そのままrequestを戻します.HttpEntity EnclosingRequestであれば、まずHttpEntity EnclosingRequestタイプに変換し、変換後の結果はHttpEntity EnclosingRequestに保存し、元のEntityとdataで新しいAopHttpRequestEntityを生成し、entiとしてtyRequestの新しいentity.handleRequestメソッドの場合、このdataはまだ新しく生成された空のNetInfoオブジェクトにすぎない.handleRequestメソッドによって新しいHttpUriRequestが得られた後、httpClient.executeはこのリクエストを実行し、その一方でリクエスト応答responseが得られ、次にhandleResponse(response,data)は応答データに対して処理する.
コードは比較的単純であることが明らかである.返された情報から返されたコードを解析してdataに設定する.ヘッダが空でなく、長さが0より大きい場合、ヘッダから受信バイト数を解析してdataに設定する.あるいは、responseのentityが空でない場合、responseのentityとdataによって、responseの新しいentityとして新しいAopHttpResponseEntityを生成する.では、dataの受信バイト数を0に設定します.このとき、dataのデータは、いったいいつ設定されているのでしょうか.実際、AopHttpResponseEntityはAopHttpEntityから拡張され、AopHttpEntityはHttpEntity、IStreamCompleteListenerの2つのインタフェースを実現し、インタフェースのwriteTo、onOutputstreamComplete、onOutputstrtstreamCompleterror,onInputstreamComplete,onInputstreamErrorは,受信バイト数を書き込む.同様にAopHttpRequestEntityもAopHttpEntityから継承され,リロードされた以上のいくつかのメソッドは送信バイト数をdataに書き込む.
TraceNetTrafficMonitorクラスに戻ると、後の方法は前と似ていますが、異なる関数呼び出しに対して接点を設定するだけです.
Argus-apm-aopは主にActivity向けの接面とHttpClientとURLConnection向けのスライスを実現した.コードを読むにはAOPとaspectjの基礎知識が必要です.
@Aspect
public class TraceActivity {
@Pointcut("!within(com.argusapm.android.aop.*) && !within(com.argusapm.android.core.job.activity.*)")
public void baseCondition() {
}
@Pointcut("execution(* android.app.Application.onCreate(android.content.Context)) && args(context)")
public void applicationOnCreate(Context context) {
}
@After("applicationOnCreate(context)")
public void applicationOnCreateAdvice(Context context) {
AH.applicationOnCreate(context);
}
@Pointcut("execution(* android.app.Application.attachBaseContext(android.content.Context)) && args(context)")
public void applicationAttachBaseContext(Context context) {
}
@Before("applicationAttachBaseContext(context)")
public void applicationAttachBaseContextAdvice(Context context) {
AH.applicationAttachBaseContext(context);
}
@Pointcut("execution(* android.app.Activity.on**(..)) && baseCondition()")
public void activityOnXXX() {
}
@Around("activityOnXXX()")
public Object activityOnXXXAdvice(ProceedingJoinPoint proceedingJoinPoint) {
Object result = null;
try {
Activity activity = (Activity) proceedingJoinPoint.getTarget();
// Log.d("AJAOP", "Aop Info" + activity.getClass().getCanonicalName() +
// "\r
kind : " + thisJoinPoint.getKind() +
// "\r
args : " + thisJoinPoint.getArgs() +
// "\r
Class : " + thisJoinPoint.getClass() +
// "\r
sign : " + thisJoinPoint.getSignature() +
// "\r
source : " + thisJoinPoint.getSourceLocation() +
// "\r
this : " + thisJoinPoint.getThis()
// );
long startTime = System.currentTimeMillis();
result = proceedingJoinPoint.proceed();
String activityName = activity.getClass().getCanonicalName();
Signature signature = proceedingJoinPoint.getSignature();
String sign = "";
String methodName = "";
if (signature != null) {
sign = signature.toString();
methodName = signature.getName();
}
if (!TextUtils.isEmpty(activityName) && !TextUtils.isEmpty(sign) && sign.contains(activityName)) {
invoke(activity, startTime, methodName, sign);
}
} catch (Exception e) {
e.printStackTrace();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return result;
}
public void invoke(Activity activity, long startTime, String methodName, String sign) {
AH.invoke(activity, startTime, methodName, sign);
}
}
@Aspect注記はこれがaspectjの接面クラスであることを示している.最初のPointCutでは、com.argusapm.android.aopとcom.argusapm.android.core.job.activityのすべての接点を除外します.2番目のPointCutはandroid.app.ApplicationのonCreateメソッドを接点として指定し、メソッドのパラメータを取得します.ApplicationOnCreateAdviceメソッドにはAfter注釈があり、前に定義したアプリケーションOnCreateはandroid.app.Application.onCreateで実行された最後に、コード:AH.applicationOnCreate(context)を織り込む.この方法は実際には1行のlogしか印刷されていない.アプリケーションAttachBaseContextAdviceメソッドには、android.app.Application.attachBaseContextメソッドに接点があるBefore注記があります.したがって、このメソッドの実行の開始時に、AH.アプリケーションAttachBaseContext(context)を呼び出す必要があります.この方法は現在のシステム時間を取得し,Activity CoreのappAttachTime属性に保存し,1行のlogも印刷する.activityOnXXXAdviceメソッドには、android.app.Activityのonで始まる一連のコールバックメソッドに接点があり、前に定義したbaseCondition、すなわち指定した2つのパッケージの下にないクラスを満たす必要があります.Around注記なので、接点前後で織り込み作業が行われます.ProceedingJoinPoint.getTarget()からtarget Activityのオブジェクトactivityを取得し、現在のシステム時間startTimeを取得します.次に、接点の元のコードを実行します.Activityのcanonical nameと接点関数の署名を取得し,さらに関数の名前を得る.activityのcanonical nameと関数の署名が空の文字列ではなく、関数署名の文字列にactivityのcanonical nameが含まれている場合は、AH.invoke(activity,startTime,methodName,sign)を呼び出します.AH.invoke(activity,startTime,methodName,sign)関数のコードは次のとおりです.
public static void invoke(Activity activity, long startTime, String lifeCycle, Object... extars) {
boolean isRunning = isActivityTaskRunning();
if (Env.DEBUG) {
LogX.d(Env.TAG, SUB_TAG, lifeCycle + " isRunning : " + isRunning);
}
if (!isRunning) {
return;
}
if (TextUtils.equals(lifeCycle, ActivityInfo.TYPE_STR_ONCREATE)) {
ActivityCore.onCreateInfo(activity, startTime);
} else {
int lc = ActivityInfo.ofLifeCycleString(lifeCycle);
if (lc <= ActivityInfo.TYPE_UNKNOWN || lc > ActivityInfo.TYPE_DESTROY) {
return;
}
ActivityCore.saveActivityInfo(activity, ActivityInfo.HOT_START, System.currentTimeMillis() - startTime, lc);
}
}
まずActivity taskが実行されているかどうかを判断します.Activity task運転の目安は、ApmTask.FLAG_COLLECT_ACTIVITY_AOPタグが設定され、TaskManagerリストにApmTask.TASK_がありますACTIVITYタイプのtaskであり、このtaskのmIsCanWorkのタグはtrueである.Activity taskが実行されていない場合は、直接戻ります.3番目のパラメータlifeCycleが「onCreate」、すなわち現在実行されているActivityのonCreateコールバック関数であれば、Activity Core.onCreateInfo(activity,startTime)を呼び出します.他のライフサイクルでは、パラメータlifeCycleから対応するlifecycleの整数定数を変換する必要があります.取得した値は適切な範囲内でなければなりません(Activity Info.TYPE_UNKNOWNより大きく、Activity Info.TYPE_DESTROY以下).適切なライフサイクルにあるメソッドコールバックの場合、Activity Core.saveActivity Info(Activity,Activity Info.HOT_START,System.currentTimeMillis(-startTime,lc)を呼び出します.以下はActivity Core.onCreateInfoのコードです.
public static void onCreateInfo(Activity activity, long startTime) {
startType = isFirst ? ActivityInfo.COLD_START : ActivityInfo.HOT_START;
activity.getWindow().getDecorView().post(new FirstFrameRunnable(activity, startType, startTime));
//onCreate
long curTime = System.currentTimeMillis();
saveActivityInfo(activity, startType, curTime - startTime, ActivityInfo.TYPE_CREATE);
}
まず、コールドスタートかホットスタートかを判断します.ActivityのDecorViewに新しいFirstFrameRunnableを送信します.現在時刻curTimeを記録します.これがonCreateの時刻です.saveActivity Info(activity,startType,curTime-startTime,Activity Info.TYPE_CREATE)を呼び出し、onCreateに関する情報を保存します.以下はsaveActivity Infoのコードです.
public static void saveActivityInfo(Activity activity, int startType, long time, int lifeCycle) {
if (activity == null) {
if (DEBUG) {
LogX.d(TAG, SUB_TAG, "saveActivityInfo activity == null");
}
return;
}
if (time < ArgusApmConfigManager.getInstance().getArgusApmConfigData().funcControl.activityLifecycleMinTime) {
return;
}
String pluginName = ExtraInfoHelper.getPluginName(activity);
String activityName = activity.getClass().getCanonicalName();
activityInfo.resetData();
activityInfo.activityName = activityName;
activityInfo.startType = startType;
activityInfo.time = time;
activityInfo.lifeCycle = lifeCycle;
activityInfo.pluginName = pluginName;
activityInfo.pluginVer = ExtraInfoHelper.getPluginVersion(pluginName);
if (DEBUG) {
LogX.d(TAG, SUB_TAG, "apmins saveActivityInfo activity:" + activity.getClass().getCanonicalName() + " | lifecycle : " + activityInfo.getLifeCycleString() + " | time : " + time);
}
ITask task = Manager.getInstance().getTaskManager().getTask(ApmTask.TASK_ACTIVITY);
boolean result = false;
if (task != null) {
result = task.save(activityInfo);
} else {
if (DEBUG) {
LogX.d(TAG, SUB_TAG, "saveActivityInfo task == null");
}
}
if (DEBUG) {
LogX.d(TAG, SUB_TAG, "activity info:" + activityInfo.toString());
}
if (AnalyzeManager.getInstance().isDebugMode()) {
AnalyzeManager.getInstance().getActivityTask().parse(activityInfo);
}
if (Env.DEBUG) {
LogX.d(TAG, SUB_TAG, "saveActivityInfo result:" + result);
}
}
1番目のパラメータは現在のActivityで、2番目のパラメータは起動タイプ(コールド起動かホット起動か)です.3つ目はライフサイクルの消費時間です.4つ目は現在のライフサイクルです.timeの値がActivityライフサイクルの最小データ収集間隔単位より小さい場合は、このデータは記録せずに無視します.プラグインの名前pluginNameとactivityの名前activityNameを取得します.activityInfoのデータをリセットし、activityInfoのactivityName,starを順に設定しますtType,time,lifecycleなどの属性の値.ApmTask.TASK_ACTIVITYのtaskを取得し、taskがnullでない場合task.save(activityInfo)を呼び出すActivity Infoの情報を保存します.現在debugモードで動作している場合は、Activity ParseTaskのparseメソッドを使用して、Activity Infoの内容を解析し、ブロードキャストによってフローティングウィンドウに送信する必要があります.ITaskは直接実装クラス、BaseTaskを持つインタフェースです.BaseTaskのsaveメソッドは、最終的にはIStorageのsaveメソッドを呼び出すことで、入力パラメータIInfoの保証を実現する必要があります保存します.ここで保存する機会はActivity Infoタイプのデータです.Activity InfoはBaseInfoから拡張されています.もちろん、BaseInfoはインタフェースIInfoの具体的な実装です.IStorage自体もインタフェースで、直接実装クラスTableStorageがあります.これは抽象クラスで、Activity Stroageはこのクラスを拡張しています.しかし、Activity Storageはsaveメソッドを複写していないので、私たちは直接実装クラスTableStorageを拡張することができます.Table Storageのsaveのコードを見てください.
@Override
public boolean save(IInfo value) {
ContentValues values = value.toContentValues();
if (!values.containsKey(BaseInfo.KEY_TIME_RECORD)) {
values.put(BaseInfo.KEY_TIME_RECORD, System.currentTimeMillis());
}
try {
return null != Manager.getInstance().getConfig().appContext.getContentResolver().insert(getTableUri(), values);
} catch (Exception e) {
if (Env.DEBUG) {
LogX.d(Env.TAG, "save ex : " + Log.getStackTraceString(e));
}
}
return false;
}
まず、パラメータvalueをContentValueタイプに変換する必要があります.ここで実際に呼び出されるのはActivity Infoクラスのこのメソッドの実装です.valuesにBaseInfo.KEY_TIME_RECORDというkeyが含まれていない場合、現在のシステム時間をBaseInfo.KEY_TIME_RECORDとして保存します.アプリケーションのContentResolverで指定されたUriにvaluesを挿入します.ここでUriはcontent://{package name}.apm.storage/activity.ApmProviderクラスのinsertメソッドでは、saveDataToDB(new DbCache.InfoHolder(values,table.getTable Name())を呼び出してデータベースにデータを書き込みます.データの格納や読み込みなどの操作は後で詳しく分析します.
public static HttpResponse execute(HttpClient client, HttpUriRequest request) throws IOException {
return isTaskRunning()
? AopHttpClient.execute(client, request)
: client.execute(request);
}
まず、ApmTask.TASK_NETが実行されているかどうかを確認し、実行されていない場合はclientのexecuteを直接呼び出し、実行されている場合はAopHttpClient.execute(client,request)を呼び出し、そのコードは次のようになります.
public static HttpResponse execute(HttpClient httpClient, HttpUriRequest request) throws IOException {
NetInfo data = new NetInfo();
HttpResponse response = httpClient.execute(handleRequest(request, data));
handleResponse(response, data);
return response;
}
メソッドのコアはhandleRequestとhandleResponseです.まずhandleRequestのコードを見てみましょう.
private static HttpUriRequest handleRequest(HttpUriRequest request, NetInfo data) {
data.setURL(request.getURI().toString());
if (request instanceof HttpEntityEnclosingRequest) {
HttpEntityEnclosingRequest entityRequest = (HttpEntityEnclosingRequest) request;
if (entityRequest.getEntity() != null) {
entityRequest.setEntity(new AopHttpRequestEntity(entityRequest.getEntity(), data));
}
return (HttpUriRequest) entityRequest;
}
return request;
}
requestからURIを取得し、data中のURLを設定します.requestのタイプがHttpEntity EnclosingRequestでない場合は、そのままrequestを戻します.HttpEntity EnclosingRequestであれば、まずHttpEntity EnclosingRequestタイプに変換し、変換後の結果はHttpEntity EnclosingRequestに保存し、元のEntityとdataで新しいAopHttpRequestEntityを生成し、entiとしてtyRequestの新しいentity.handleRequestメソッドの場合、このdataはまだ新しく生成された空のNetInfoオブジェクトにすぎない.handleRequestメソッドによって新しいHttpUriRequestが得られた後、httpClient.executeはこのリクエストを実行し、その一方でリクエスト応答responseが得られ、次にhandleResponse(response,data)は応答データに対して処理する.
private static HttpResponse handleResponse(HttpResponse response, NetInfo data) {
data.setStatusCode(response.getStatusLine().getStatusCode());
Header[] headers = response.getHeaders("Content-Length");
if ((headers != null) && (headers.length > 0)) {
try {
long l = Long.parseLong(headers[0].getValue());
data.setReceivedBytes(l);
if (DEBUG) {
LogX.d(TAG, SUB_TAG, "-handleResponse--end--1");
}
data.end();
} catch (NumberFormatException e) {
if (Env.DEBUG) {
LogX.d(TAG, SUB_TAG, "NumberFormatException ex : " + e.getMessage());
}
}
} else if (response.getEntity() != null) {
response.setEntity(new AopHttpResponseEntity(response.getEntity(), data));
} else {
data.setReceivedBytes(0);
if (DEBUG) {
LogX.d(TAG, SUB_TAG, "----handleResponse--end--2");
}
data.end();
}
if (DEBUG) {
LogX.d(TAG, SUB_TAG, "execute:" + data.toString());
}
return response;
}
コードは比較的単純であることが明らかである.返された情報から返されたコードを解析してdataに設定する.ヘッダが空でなく、長さが0より大きい場合、ヘッダから受信バイト数を解析してdataに設定する.あるいは、responseのentityが空でない場合、responseのentityとdataによって、responseの新しいentityとして新しいAopHttpResponseEntityを生成する.では、dataの受信バイト数を0に設定します.このとき、dataのデータは、いったいいつ設定されているのでしょうか.実際、AopHttpResponseEntityはAopHttpEntityから拡張され、AopHttpEntityはHttpEntity、IStreamCompleteListenerの2つのインタフェースを実現し、インタフェースのwriteTo、onOutputstreamComplete、onOutputstrtstreamCompleterror,onInputstreamComplete,onInputstreamErrorは,受信バイト数を書き込む.同様にAopHttpRequestEntityもAopHttpEntityから継承され,リロードされた以上のいくつかのメソッドは送信バイト数をdataに書き込む.
TraceNetTrafficMonitorクラスに戻ると、後の方法は前と似ていますが、異なる関数呼び出しに対して接点を設定するだけです.