Rabbit是目前我正在開發的一個框架,它主要用來提升App開發的效率和質量,整體定位上偏向於一個APM
框架。html
統計應用冷啓動時長、頁面渲染時長是APM
系統不可缺乏一個功能,Rabbit
中這個功能的實現主要參考自Android自動化頁面測速在美團的實踐,目前已經完成下面功能點:java
Application.onCreate
耗時統計Activity.onCreate
耗時統計Activity
首次inflate
耗時統計Activity
首次渲染耗時具體統計時機以下圖: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
的測速功能不須要其餘的初始化代碼,接下來就大概過一下上面功能的實現原理:
實現思路:
Application.attachBaseContext()開始
和Application.onCreate()結束
方法中插入耗時統計代碼。對於編譯時的字節碼插入本文就不作詳細實現分析,具體實現能夠參考
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
定義Activity
的ContentView
繪製完成就是頁面渲染完成,咱們能夠經過監聽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);
}
}
複製代碼
咱們知道ViewGroup.dispatchDraw()
方法在ViewTree
發生改變時就會調用,而通常第一次會致使dispatchDraw()
被調用代碼是:
setContentView(R.layout.activity_transform_test);
複製代碼
所以Rabbit
將Activity
的第一dispatchDraw()
方法完成時間當作Activity首次Inflate
結束時間點。
其實這個時間的長短能夠表明
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
}
}
複製代碼
結合上面的敘述,Rabbit
定義App冷啓動耗時爲HomeActivity渲染完成時 - Application.attachBaseContext()開始時。
對於HomeActivity
能夠經過rabbit_speed_monitor.json
進行配置:
{
"home_activity": "MainActivity",
"page_list": [
...
]
}
複製代碼
應用測速組件的實現原理並非很複雜,不過仍是涉及到了不少點。對於具體實現邏輯能夠參考 : Rabbit
Rabbit
中目前使用的統計時機可能並非最合適的,若是你知道更合適的統計時機,歡迎交流。
Rabbit
功能的實現原理見:Rabbit實現原理剖析
最後,歡迎關注個人公衆微信號: