Android应用测速组件实现原理

作者 : 开心源码 本文共4293个字,预计阅读时间需要11分钟 发布时间: 2022-05-13 共216人阅读

Rabbit是目前我正在开发的一个框架,它主要用来提高App开发的效率和质量,总体定位上偏向于一个APM框架。

统计应用冷启动时长页面渲染时长APM系统不可缺少一个功能。Rabbit中这个功能的实现主要参考自Android自动化页面测速在美团的实践,目前已经完成下面功能点:

  1. Application.onCreate耗时统计
  2. 应用冷启动耗时统计
  3. Activity.onCreate耗时统计
  4. Activity初次inflate耗时统计
  5. Activity初次渲染耗时
  6. 页面网络请求耗时监控

具体统计时机如下图:

测速组件名词解释.png

最终输出的效果如下:

应用启动测速

app_speed.png

页面启动测速

page_render_speed.png

网络耗时测速

page_request_speed.png

使用方法

整个测速组件实现的思路主要是:利用Gradle插件在应用编译时动态注入监控代码。因而使用时需要在应用的build.gradle中应用插件:

apply plugin: 'rabbit-tracer-transform'

为了支持网络监控功能,需要在OkHttpClient初始化时插入阻拦器(目前只支持OkHttp的网络监控):

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实现原理剖析

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » Android应用测速组件实现原理

发表回复