Android應用測速組件實現原理

Rabbit是目前我正在開發的一個框架,它主要用來提升App開發的效率和質量,整體定位上偏向於一個APM框架。html

統計應用冷啓動時長頁面渲染時長APM系統不可缺乏一個功能,Rabbit中這個功能的實現主要參考自Android自動化頁面測速在美團的實踐,目前已經完成下面功能點:java

  1. Application.onCreate耗時統計
  2. 應用冷啓動耗時統計
  3. Activity.onCreate耗時統計
  4. Activity首次inflate耗時統計
  5. Activity首次渲染耗時
  6. 頁面網絡請求耗時監控

具體統計時機以下圖:android

最終輸出的效果以下:git

應用啓動測速github

頁面啓動測速json

網絡耗時測速canvas

使用方法

整個測速組件實現的核心思路是:利用Gradle插件在應用編譯時動態注入監控代碼。所以使用時須要在應用的build.gradle中應用插件:api

apply plugin: 'rabbit-tracer-transform'
複製代碼

爲了支持網絡監控功能,須要在OkHttpClient初始化時插入攔截器(目前只支持OkHttp的網絡監控):bash

OkHttpClient.Builder().addInterceptor(Rabbit.getApiTracerInterceptor())
複製代碼

後面會考慮把Interceptor的初始化作成AOP的方式。微信

除此以外Rabbit的測速功能不須要其餘的初始化代碼,接下來就大概過一下上面功能的實現原理:

應用onCreate耗時統計

實現思路:

  1. 編譯應用時在Application.attachBaseContext()開始Application.onCreate()結束方法中插入耗時統計代碼。
  2. SDK收集測速數據,而後展現。

對於編譯時的字節碼插入本文就不作詳細實現分析,具體實現能夠參考Rabbit源碼中的實現,最終插入效果以下:

public class CustomApplication extends Application {

    protected void attachBaseContext(Context base) {
        AppStartTracer.recordApplicationCreateStart();
        super.attachBaseContext(base);
    }

    public void onCreate() {
        super.onCreate();
        Rabbit.init(this);
        AppStartTracer.recordApplicationCreateEnd();
    }
}
複製代碼

頁面渲染耗時統計

何時纔算頁面渲染完成呢?

Rabbit定義ActivityContentView繪製完成就是頁面渲染完成,咱們能夠經過監聽ViewGroup.dispatchDraw()來監聽Activity.ContentView繪製完成。

具體實現思路是: 手動爲Activity.setContentView()設置的View添加一層自定義父View,用於計算繪製完成的時間

public class ActivitySpeedMonitor extends FrameLayout {

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        RabbitTracerEventNotifier.eventNotifier.activityDrawFinish(getContext(), System.currentTimeMillis());
    }

    public static void wrapperViewOnActivityCreateEnd(Activity activity) {
        FrameLayout contentView = activity.findViewById(android.R.id.content);
        ViewGroup contentViewParent = (ViewGroup) contentView.getParent();

        if (contentView != null && contentViewParent != null) {
            ActivitySpeedMonitor newParent = new ActivitySpeedMonitor(contentView.getContext());
            if (contentView.getLayoutParams() != null) {
                newParent.setLayoutParams(contentView.getLayoutParams());
            }
            contentViewParent.removeView(contentView);
            newParent.addView(contentView);
            contentViewParent.addView(newParent);
        }
    }
}
複製代碼

上面ActivitySpeedMonitor.wrapperViewOnActivityCreateEnd()代碼會在編譯時插入在Activity.onCreate方法中:

public class TransformTestActivity extends AppCompatActivity {

    protected void onCreate(Bundle savedInstanceState) {
        ActivitySpeedMonitor.activityCreateStart(this);
        super.onCreate(savedInstanceState);
        this.setContentView(2131296286);
        ActivitySpeedMonitor.wrapperViewOnActivityCreateEnd(this);
    }

}
複製代碼

Activity首次inflate耗時統計

咱們知道ViewGroup.dispatchDraw()方法在ViewTree發生改變時就會調用,而通常第一次會致使dispatchDraw()被調用代碼是:

setContentView(R.layout.activity_transform_test);
複製代碼

所以RabbitActivity的第一dispatchDraw()方法完成時間當作Activity首次Inflate結束時間點。

其實這個時間的長短能夠表明Activity的佈局複雜度。

Activity首次渲染耗時

這個耗時統計的時間結束點爲: 頁面發起網絡請求拿到數據,並完成頁面渲染

舉個例子,好比你的應用首頁有3個接口,這3個接口的數據組成了整個首頁的UI, 首頁的渲染耗時就是3個接口完成請求,而且數據渲染完成

Rabbit中對頁面的渲染耗時統計須要配置,即配置一個頁面哪些接口完成纔算頁面渲染完成, 具體配置約定爲assest文件夾下提供rabbit_speed_monitor.json文件:

{
  "home_activity": "MainActivity",  
  "page_list": [
    {
      "page": "MainActivity",
      "api": [
        "xxx/api/getHomePageRecPosts",
        "xxx/api/getAvailablePreRegistrations",
        "xxxx/api/appHome"
      ]
    }
    ...
  ]
}
複製代碼

home_activity配置統計應用冷啓動耗時。

page_list配置須要統計渲染耗時的頁面。

Rabbit會在指定的全部接口都完成而且ViewGroup.dispatchDraw()方法完成時記錄下這個時間點來做爲渲染耗時:

RabbitAppSpeedMonitor.java

fun activityDrawFinish(activity: Any, drawFinishTime: Long) {
    val apiStatus = pageApiStatusInfo[currentPageName]
    if (apiStatus != null) {
        if (apiStatus.allApiRequestFinish()) { //全部請求已經完成
            pageSpeedCanRecord = false //只統計一次
            pageSpeedInfo.fullDrawFinishTime = drawFinishTime
            RabbitDbStorageManager.save(pageSpeedInfo)
        }
    }   
}
複製代碼

如何統計接口完成呢?

網絡請求耗時監控

也是利用RabbitAppSpeedInterceptor,不過這裏監控的網絡耗時時間並非咱們真正理解的網絡請求耗時,時間大概介於 : 網絡請求耗時 ~ 應用網絡處理耗時,具體實現核心代碼以下:

class RabbitAppSpeedInterceptor : Interceptor {

    override fun intercept(chain: Interceptor.Chain): Response {

        val startTime = System.currentTimeMillis()
        val request = chain.request()
        val requestUrl = request.url().url().toString()
        val response = chain.proceed(request)

        if (!RabbitTracer.monitorRequest(requestUrl)) return response // 不須要監控這個請求

        val costTime = System.currentTimeMillis() - startTime

        RabbitTracer.markRequestFinish(requestUrl, costTime)

        return response
    }
}
複製代碼

App冷啓動耗時統計

結合上面的敘述,Rabbit定義App冷啓動耗時HomeActivity渲染完成時 - Application.attachBaseContext()開始時

對於HomeActivity能夠經過rabbit_speed_monitor.json進行配置:

{
  "home_activity": "MainActivity",  
  "page_list": [
    ...
  ]
}
複製代碼

總結

應用測速組件的實現原理並非很複雜,不過仍是涉及到了不少點。對於具體實現邏輯能夠參考 : Rabbit

Rabbit中目前使用的統計時機可能並非最合適的,若是你知道更合適的統計時機,歡迎交流。

Rabbit功能的實現原理見:Rabbit實現原理剖析

最後,歡迎關注個人公衆微信號:

相關文章
相關標籤/搜索