Rabbit is a framework I am currently working on to improve the efficiency and quality of App development. It is generally positioned as an APM framework.

APM is an essential function for calculating application cold startup time and page rendering time. This function is implemented in Rabbit based on the implementation of Android automatic page speed measurement in Meituan. The following function points have been achieved:

  1. Application.onCreateTime-consuming statistics
  2. Example Collect statistics about the cold start time
  3. Activity.onCreateTime-consuming statistics
  4. ActivityFor the first timeinflateTime-consuming statistics
  5. ActivityFirst render time
  6. Monitor page network request time

The specific statistical timing is shown below:

The final output looks like this:

Apply start speed measurement

Page start speed measurement

Network time measurement

Method of use

The core idea of the whole speed measurement component is to use Gradle plug-in to dynamically inject monitoring code during application compilation. Build. Gradle: build.gradle: build.gradle: build.gradle

apply plugin: 'rabbit-tracer-transform'
Copy the code

To support network monitoring, an interceptor needs to be inserted during OkHttpClient initialization (currently only OkHttp network monitoring is supported):

OkHttpClient.Builder().addInterceptor(Rabbit.getApiTracerInterceptor())
Copy the code

We’ll consider making Interceptor’s initialization AOP later.

No additional initialization code is required for the Rabbit speed detection function. Here’s a quick overview of how it works:

Use onCreate to collect time statistics

Implementation idea:

  1. When compiling the applicationApplication. AttachBaseContext ()andEnd Application. The onCreate ()Method to insert time statistics code.
  2. The SDK collects the speed data and presents it.

For bytecode insertion at compile time, this article will not do a detailed implementation analysis. For specific implementation, you can refer to the Rabbit source code. The final result of the insertion is as follows:

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(); }}Copy the code

Page rendering time statistics

When is the page rendered complete?

When the page is rendered, we can listen for activity.contentView drawing by listening for viewGroup.dispatchDraw ().

Manually add a layer of custom parent View to the View set by activity.setContentView (), which is used to calculate the completion time of drawing

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); }}}Copy the code

Above ActivitySpeedMonitor. WrapperViewOnActivityCreateEnd () code will be inserted in the Activity at compile time. The onCreate method:

public class TransformTestActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { ActivitySpeedMonitor.activityCreateStart(this); super.onCreate(savedInstanceState); this.setContentView(2131296286); ActivitySpeedMonitor.wrapperViewOnActivityCreateEnd(this); }}Copy the code

Activity Collects the first inflate time statistics

We know that the viewgroup.dispatchDraw () method is called when the ViewTree changes and normally dispatchDraw() is called the first time with the following code:

setContentView(R.layout.activity_transform_test);
Copy the code

Therefore, Rabbit treats the completion time of the Activity’s first dispatchDraw() method as the point at which the Activity’s first Inflate ends.

In fact, this length of time can represent the complexity of the Activity’s layout.

The first rendering time of the Activity

The end point of this time statistics is: the page initiates a network request to get the data and completes the page rendering

For example, if your application home page has 3 interfaces, the data of these 3 interfaces constitute the UI of the entire home page, and the rendering time of the home page is 3 interfaces to complete the request, and the data rendering is completed.

Rabbit_speed_monitor. json rabbit_speed_monitor.json file in the assest folder.

{
  "home_activity": "MainActivity"."page_list": [{"page": "MainActivity"."api": [
        "xxx/api/getHomePageRecPosts"."xxx/api/getAvailablePreRegistrations"."xxxx/api/appHome"]}... ] }Copy the code

Home_activity Collects statistics on the cold startup time of applications.

Page_list Configures the page whose rendering time needs to be counted.

Rabbit records the render time when all specified interfaces are complete and the viewgroup.dispatchDraw () method is complete:

RabbitAppSpeedMonitor.java

fun activityDrawFinish(activity: Any, drawFinishTime: Long) {
    val apiStatus = pageApiStatusInfo[currentPageName]
    if(apiStatus ! = null) {if(apiStatus allApiRequestFinish ()) {/ / all the request has been completed pageSpeedCanRecord =false. / / statistics only once pageSpeedInfo fullDrawFinishTime = drawFinishTime RabbitDbStorageManager. Save (pageSpeedInfo)}}}Copy the code

How do you count interface completion?

Network request time monitoring

Also use RabbitAppSpeedInterceptor, monitoring network takes time but here it’s not that we really understand the request for network time consuming, with time around: network request takes ~ application processing time consuming, concrete implementation core code is as follows:

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))returnThe response / / no need to monitor the request val costTime = System. CurrentTimeMillis () - startTime RabbitTracer. MarkRequestFinish (requestUrl, costTime)return response
    }
}
Copy the code

App cold startup time statistics

Combining the above narrative, Rabbit definition App cold start time for HomeActivity rendering is finished – Application. AttachBaseContext () at the beginning.

HomeActivity can be configured using rabbit_speed_monitor.json:

{
  "home_activity": "MainActivity"."page_list": [...]. }Copy the code

conclusion

The implementation principle of the application velocimetry component is not very complicated, but it still involves many points. For the implementation logic, see: Rabbit

The timing of the count currently used in Rabbit may not be the most appropriate. If you know of a better time, please contact us.

For details about how to implement the Rabbit function, see “Anatomy of Rabbit implementation”

Finally, please follow my wechat account: