浅析如何打造一款自己的安卓APM SDK

浅析如何打造一款自己的安卓APM SDK

背景

首先,APM是什么?APM即(Applicaton Performance Management)应用性能管理,主要针对企业应用的一些关键业务和用户体验,行为逻辑进行检测和优化。随着互联网行业的发展,企业对于应用的可靠性和用户行为的检测提取提出了更高的要求,以此来达到优化产品,提高用户体验的目的。目前,市场上针对这一业务场景2b的需求,涌现出了很多的APM产品,包括BAT之内的大厂,都纷纷向企业推出了自己的产品,更有一些新兴企业,以此打开一片市场。
但对某些公司来说,产品形态多为SDK。作为同样2b的产品,不能与企业的app冲突,并且出于安全性的考虑,我们也不能将自身的数据依托第三方分析,最后,同样也希望给商户提供平台化的配套服务。出于这些考虑,就需要自行研发一个APM SDK,来满足这部分要求。
本文,将从整体框架的角度浅析如何打造一款可用性较好,且功能较为齐全的APM SDK.

从功能和实现方式上分解APM SDK

一款商业化运营的APM SDK在功能上是比较繁多的,为了减少企业集成的人力成本和学习成本,我们在方针上,采取在尽量增加SDK稳定性的前提下,尽量多的采用非侵入式的实现方式。而在具体的实现方式上,我们分为两大类:侵入式方式,非侵入式方式;在非侵入式方式当中,我们可以细分为三种方式,即原生方式,自定义gradle插件方式,和hook方式。
在这里插入图片描述

第一,讲一下侵入式方式:

侵入式方式其实是我们最常用,也是市面上绝大部分统计类SDK的主要实现方式,即要求接入企业采用硬编码的方式来完成一系列的数据采集需求。这种方式在实现上并没有太大的难点,但是也是一种极为重要且不可缺少的实现方式。
首先,我们的SDK需要一个统一且唯一的初始化入口,这一点是必不可少的,原因有两点:一、我们的SDK中需要获取很多的设备信息和设备环境信息,这些信息,我们希望在用户打开商户APP的时候,能尽早的获取。并且一些诸如崩溃信息采集的功能也需要在启动后就进行监听。二、我们的SDK当中必然或多或少的使用了context上下文的参数,在入口方法中,我们需要商户传入他们的上下文对象。但是对此,我们依然要提醒一句,避免内存泄漏,请合理使用context上下文的类型。而在这里,我们需要且仅需要商户的ApplicationContext对象即可。
而后,我们在设计SDK的时候,同样要照顾到商户接入的自由度,为此,自定义事件的功能也是必不可少的,CustomEvent作为一个基础功能,我们需要给商户提供到基本的功能以及自定义的功能。即这个自定义事件是商户可配置,且可扩展的。具体实现上,我们可以使用接口和接口实现类的方式,即为商户提供一个接口和默认实现类。商户可以直接使用该默认实现类上报参数,也可以通过实现上报接口类,进行自定义的扩展。
再者,我们在以往的经验当中,发现有一些商户会用一些比较非主流的方式(即不使用Activity或者Fragment)去实现所有的界面跳转,常见的有使用纯View的方式,在单一或者极少的activity中实现界面切换和跳转。这样,我们在下文的非侵入式方式当中,就难以去获取这种类型商户的跳转信息,这样,就用户行为分析自然无从谈起。对此,我们还是提供给了商户一套手动插入生命周期变化的方法,来配合这些商户的使用。

第二,讲一下非侵入式的方式:

非侵入式的方式,给了商户良好的用户体验,因此,也是我们SDK中的重中之重。而这种实现方式,针对不同的场景,实现方式可以说是完全不同,大相径庭,因此,在这里着重展开讲一下。

一、原生方式实现非侵入式探针功能。

他的具体实现,在这里主要体现在Activity生命周期和一些手机或者app状态的获取上,Activity生命周期的灵感来源与LeakCanary源码中。我们利用了Application.ActivityLifecycleCallbacks的安卓原生接口。通过实现这个接口,我们会复写这个接口下所有的生命周期方法,而每个方法下都暴露了一个Activity对象。没有错,这个对象,正是当前环境下,所有产生生命周期变化的activity的事例对象。即当A Activity发生onCreate时,在Application.ActivityLifecycleCallbacks下的onActivityCreated方法下,就会暴露A Activity的实例对象。对此,我们可以进行封装,在初始化时,对传进来的context进行registerActivityLifecycleCallbacks(this)的注册。
完成了以上操作,我们还需要在用户的使用行为,也就是界面跳转链条上进行一些处理和优化,主要包含在页面跳转链条和前后台切换的判断。

二、采用自定义gradle插件来字节码插桩的方式实现探针功能。

这部分可以说是APM SDK中的重中之重,他是非侵入式实现方法的核心,担任了绝大部分的非侵入式功能实现。原因有两个。第一,它比hook的方式更加稳定,不容易受系统版本碎片化的影响。第二,它比原生方法的自由程度要大的多,可以实现相当多的功能。
这部分功能实现的入口,就是依托自定义gradle插件和groovy语言,在Transform接口当中,暴露了app在编译过程中产生的所有字节码,我们可以通过实现Transform接口,以及asm框架,来进行字节码插桩。至于如何具体实现自定义gradle插件,我会在以后的文章中详加说明,本文就不再赘述。
在说明了自定义gradle插件的实现原理之后,其实,这部分也是各个商业化APM产品,需要消耗人力最多的部分之一。为什么这么说呢?原因其实很简单,因为android的第三方接口请求库种类繁多,使用最广泛的包含OkHttp、以及在okhttp上演进而来的Retrofit。这些第三方库,甚至在不同版本,他的实现方式可能都是有所区别的,这就需要开发人员对每一种第三方库的业务流程进行分析,寻找合适的切面进行代码注入。通常情况下,我们会寻找关键变量,比如URL类型的变量,或者是关键入口比如HttpURLConnection的实现类,来进行代码注入。
除此之外,我们还可以对所有界面的onClick事件,以及fragment的生命周期变化进行代码注入,在onClick方法中,已经暴露了点击的view对象,因此我们很容易获取到它的信息,包含点击的界面,这个界面的控件树,控件ID等。
说完了以上几点,其实,自定义gradle插件是有一定局限性的,即他只能暴露app在编译过程当中暴露的字节码内容。因此我们也只能对这些内容做修改。为此,必须要有hook的方式来进行补充。

三、采用Hook的方式来实现探针功能。

这部分是对gradle插件的一个补充。毕竟,hook的方式,直接影响了稳定性,而且android的API版本众多,需要做好兼容工作。
这部分的功能,我们以android原生服务器请求为例。HttpURLConnection和HttpClient,这部分代码不会在app编译的时候暴露出来,因此必须从hook入手来实现。同样的,这部分需要我们深入理解远吗的执行机制,寻找其中的切面来进行修改,但是和gradle的直接注入不同,我们主要依靠给系统原生方法设置代理,来获取我们所需要的参数信息。其中HttpURLConnection我们主要从URL中的openConnection方法和URLConnection类入手来实现。
除此以外,android绝大多数的“黑科技”也都是靠Hook来完成的,例如利用Activity的启动过程来改变他启动的activity对象,来实现没有在manifest中声明而打开activity,以此来实现插件化框架,比如360的Replugin就是例子。

整体框架上的扩展性和易用性

在分析完APM SDK的实现方式之后,我们要考虑SDK的扩展性和易用性,以此来决定我们整体SDK的框架。
SDK的框架如图所示,主要分为三层:最上层为接口层,暴露了少量的方法供商户使用,接口中分离出了两个类LianAPM和LianHelper,其中LianHelper类并不提供商户直接调用,而是提供给自定义Gradle插件,LianPlugin来进行代码插入。接口以下包含了采集策略,除此之外,我们还提供了LianAPMConfiguration的Builder模式的类,提供给商户配置参数,和限定采集范围。第二层为服务层,这一层包含了所有互相之间完全独立的功能服务模块。功能模块之间的交互,完全依托LianAPM作为中间件来进行调度。第三层为数据层,为数据提供了多线程sqlite本地存储功能,以及根据上传策略,进行上传的功能。当然,诸如崩溃信息的统计是实时上传的。
在这里插入图片描述

总结

APM其实是一个比较大的议题,同时也是被各个互联网企业越来越重视的议题。本文仅在实践过程中简单总结了一些思路和实现手法供大家参考。

发布了5 篇原创文章 · 获赞 0 · 访问量 623

猜你喜欢

转载自blog.csdn.net/zhengdavid02/article/details/104071518