Rabbit是目前我正在開發(fā)的一個框架,它主要用來提高App開發(fā)的效率和質(zhì)量,總體定位上偏向于一個APM
框架梳杏。
統(tǒng)計應用冷啟動時長钱反、頁面渲染時長是APM
系統(tǒng)不可缺少一個功能盛垦。Rabbit
中這個功能的實現(xiàn)主要參考自Android自動化頁面測速在美團的實踐,目前已經(jīng)完成下面功能點:
-
Application.onCreate
耗時統(tǒng)計 - 應用冷啟動耗時統(tǒng)計
-
Activity.onCreate
耗時統(tǒng)計 -
Activity
首次inflate
耗時統(tǒng)計 -
Activity
首次渲染耗時 - 頁面網(wǎng)絡請求耗時監(jiān)控
具體統(tǒng)計時機如下圖:
最終輸出的效果如下:
應用啟動測速
app_speed.png
頁面啟動測速
page_render_speed.png
網(wǎng)絡耗時測速
page_request_speed.png
使用方法
整個測速組件實現(xiàn)的思路主要是:利用Gradle插件在應用編譯時動態(tài)注入監(jiān)控代碼潮剪。因此使用時需要在應用的build.gradle
中應用插件:
apply plugin: 'rabbit-tracer-transform'
為了支持網(wǎng)絡監(jiān)控功能,需要在OkHttpClient
初始化時插入攔截器(目前只支持OkHttp的網(wǎng)絡監(jiān)控):
OkHttpClient.Builder().addInterceptor(Rabbit.getApiTracerInterceptor())
后面會考慮把
Interceptor
的初始化做成AOP的方式。
除此之外Rabbit
的測速功能不需要其他的初始化代碼,接下來就大概過一下上面功能的實現(xiàn)原理:
應用onCreate耗時統(tǒng)計
實現(xiàn)思路:
- 編譯應用時在
Application.attachBaseContext()開始
和Application.onCreate()結(jié)束
方法中插入耗時統(tǒng)計代碼环肘。 - SDK收集測速數(shù)據(jù),然后展示集灌。
對于編譯時的字節(jié)碼插入本文就不做詳細實現(xiàn)分析悔雹,具體實現(xiàn)可以參考
Rabbit
源碼中的實現(xiàn),最終插入效果如下:
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();
}
}
頁面渲染耗時統(tǒng)計
什么時候才算頁面渲染完成呢?
Rabbit
定義Activity
的ContentView
繪制完成就是頁面渲染完成,我們可以通過監(jiān)聽ViewGroup.dispatchDraw()
來監(jiān)聽Activity.ContentView
繪制完成欣喧。
具體實現(xiàn)思路是: 手動為Activity.setContentView()
設(shè)置的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耗時統(tǒng)計
我們知道ViewGroup.dispatchDraw()
方法在ViewTree
發(fā)生改變時就會調(diào)用,而一般第一次會導致dispatchDraw()
被調(diào)用代碼是:
setContentView(R.layout.activity_transform_test);
因此Rabbit
將Activity
的第一dispatchDraw()
方法完成時間當做Activity首次Inflate
結(jié)束時間點唆阿。
其實這個時間的長短可以代表
Activity
的布局復雜度益涧。
Activity首次渲染耗時
這個耗時統(tǒng)計的時間結(jié)束點為: 頁面發(fā)起網(wǎng)絡請求拿到數(shù)據(jù),并完成頁面渲染
舉個例子驯鳖,比如你的應用首頁有3個接口饰躲,這3個接口的數(shù)據(jù)組成了整個首頁的UI, 首頁的渲染耗時就是3個接口完成請求,并且數(shù)據(jù)渲染完成臼隔。
Rabbit
中對頁面的渲染耗時統(tǒng)計需要配置嘹裂,即配置一個頁面哪些接口完成才算頁面渲染完成, 具體配置約定為在assest
文件夾下提供rabbit_speed_monitor.json
文件:
{
"home_activity": "MainActivity",
"page_list": [
{
"page": "MainActivity",
"api": [
"xxx/api/getHomePageRecPosts",
"xxx/api/getAvailablePreRegistrations",
"xxxx/api/appHome"
]
}
...
]
}
home_activity
配置統(tǒng)計應用冷啟動耗時。
page_list
配置需要統(tǒng)計渲染耗時的頁面摔握。
Rabbit
會在指定的所有接口都完成并且ViewGroup.dispatchDraw()
方法完成時記錄下這個時間點來作為渲染耗時:
RabbitAppSpeedMonitor.java
fun activityDrawFinish(activity: Any, drawFinishTime: Long) {
val apiStatus = pageApiStatusInfo[currentPageName]
if (apiStatus != null) {
if (apiStatus.allApiRequestFinish()) { //所有請求已經(jīng)完成
pageSpeedCanRecord = false //只統(tǒng)計一次
pageSpeedInfo.fullDrawFinishTime = drawFinishTime
RabbitDbStorageManager.save(pageSpeedInfo)
}
}
}
如何統(tǒng)計接口完成呢寄狼?
網(wǎng)絡請求耗時監(jiān)控
也是利用RabbitAppSpeedInterceptor
,不過這里監(jiān)控的網(wǎng)絡耗時時間并不是我們真正理解的網(wǎng)絡請求耗時,時間大概介于 : 網(wǎng)絡請求耗時 ~ 應用網(wǎng)絡處理耗時,具體實現(xiàn)核心代碼如下:
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 // 不需要監(jiān)控這個請求
val costTime = System.currentTimeMillis() - startTime
RabbitTracer.markRequestFinish(requestUrl, costTime)
return response
}
}
App冷啟動耗時統(tǒng)計
結(jié)合上面的敘述氨淌,Rabbit
定義App冷啟動耗時為HomeActivity渲染完成時 - Application.attachBaseContext()開始時泊愧。
對于HomeActivity
可以通過rabbit_speed_monitor.json
進行配置:
{
"home_activity": "MainActivity",
"page_list": [
...
]
}
總結(jié)
應用測速組件的實現(xiàn)原理并不是很復雜,不過還是涉及到了很多點盛正。具體實現(xiàn)邏輯可以參考 : Rabbit
Rabbit
中目前使用的統(tǒng)計時機可能并不是最合適的删咱,如果你知道更合適的統(tǒng)計時機,歡迎交流豪筝。
Rabbit
功能的實現(xiàn)原理見:Rabbit實現(xiàn)原理剖析