接入Android
SDK 引入
本地集成
(1)将qapmsdk.aar文件拷贝到所需的App的libs目录下
(2)在App的gradle中引入模块(studio版本在3.0以下的请使用compile的引用头)
参考代码
implementation(name: 'qapmsdk-5.3.3-pub-private, ext: 'aar')
(3)由于QAPM工程使用了kotlin混合开发,需要添加以下kotlin插件依赖
a. 在工程的gradle下加入如下代码
参考代码
ext.kotlin_version = '1.3.41'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
b. 在App的gradle下加入如下代码
参考代码
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
(4)将repo.zip解压缩,放在项目的根目录下,需要注意目录层级是否正确
(5)在工程的gradle文件中加入以下配置
参考代码
buildscript {
repositories {
maven { url uri('repo') }
}
dependencies {
classpath 'com.tencent.qapmplugin:qapm-plugin:2.38
}
}
(6)在App的gradle文件中加入以下配置
参考代码
apply plugin: 'qapm-plugin'
QAPMPluginConfig {
// 可选,默认为空,请在Application所在的类中输入attachBaseContext,看有没有这个的重写方法,如果没有则需要配置该项,如下图所示就是无需配置该项的校验
// tinkerApplication = 'com/tencent/qapm/demoApplication'
}
参数配置
权限配置
QAPM需要网络相关权限执行数据上报操作,请在AndroidManifiest.xml中添加以下权限
<!--上报信息所需-->
<uses-permission android:name="android.permission.INTERNET" />
<!--采集信息所需-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
混淆设置
请勿混淆qapm,在App的proguard-rules.pro文件中增加以下配置即可避免QAPM被混淆。
-keep class com.tencent.qapmsdk.**{*;}
设备唯一标识符与手机型号
受当前监管部门要求,在用户同意隐私合规之前请勿调用QAPM的任何接口,同时SDK类产品禁止获取手机型号、IMEI等个人信息,但是QAPM需要设备级的唯一标识符用于确定设备的唯一性并计算相关的用户性能指标,因此QAPM开放设备标识符的相关接口供开发者自行设置相关字段,请开发者根据自身业务情况设置设备唯一标识符与手机型号信息。若配置的相关字段不符合预期则有可能影响相关指标的准确性。
参考代码
// 当用户授权后,方可正常初始化QAPM
if (isAgree) {
// 需要传入设备的唯一标识
QAPM.setProperty(QAPM.PropertyKeyDeviceId, "设备的唯一标识");
// 需要传入手机型号
QAPM.setProperty(QAPM.PropertyKeyModel, "填写手机型号");
// 正常初始化代码贴入,参考文档1.3部分
}
首次启动耗时数据
当前监管要求在用户同意隐私政策前SDK类产品禁止采集相关数据,该限制会影响首次启动的信息采集,如需要上报首次启动的耗时数据,请调用以下接口进行上报(请确保在用户同意隐私政策后再调用)。
// startTime 应用最开始的时间,建议打在Application的attachBaseContext方法的最上方
// endTime 应用结束的时间,可以自定义,QAPM打在第一个Activity的onResume方法的最下方
QAPM.sendFirstLaunch(startTime, endTime);
a. 请确保用户在同意隐私政策之前不调用任何QAPM的接口。
b. 如果未传入deviceId,则会影响用户级指标数据的计算,如用户崩溃率等。
c. 如果未传入手机型号信息,则会影响前端问题个例,指标维度的手机型号聚类。
SDK初始化
SDK初始化配置
请拷贝下面代码(除日志外其余均是必需的接口设置,其余接口配置请参考SDK初始化自检,修改其中必需字段,建议在Application中初始化QAPM
// 设置手机型号和设备ID,在隐私合规政策下,QAPM不可获取mac等部分个人信息,因此需要用户参考1.2章节,将信息传递给我们
// 需要传入设备的唯一标识(必需!!)
QAPM.setProperty(QAPM.PropertyKeyDeviceId, "设备的唯一标识");
// 需要传入手机型号(必需!!)
QAPM.setProperty(QAPM.PropertyKeyModel, "填写手机型号");
// 设置Application(必需)
QAPM.*setProperty*(QAPM.*PropertyKeyAppInstance*, getApplication());
// 设置AppKey(必需,用于区分上报的产品,该值由移动监控的产品配置页面获取 )
QAPM.*setProperty*(QAPM.*PropertyKeyAppId*, "Your AppKey");
// 设置产品版本,用于后台检索字段(必需)
QAPM.*setProperty*(QAPM.*PropertyKeyAppVersion*, "Your App Version");
// 设置UUID,用于拉取被混淆堆栈的mapping (必需,若使用了QAPM符号表上传插件,可以直接使用该变量)
QAPM.setProperty(QAPM.*PropertyKeySymbolId*, BuildConfig.QAPM_UUID);
// 设置用户ID,用于后台检索字段(必需)
QAPM.*setProperty*(QAPM.*PropertyKeyUserId*, "123456");
// 设置Log等级,(可选),线上版本请设置成QAPM.LevelOff 或者 QAPM.LevelWarn
QAPM.*setProperty*(QAPM.*PropertyKeyLogLevel*, QAPM.*LevelInfo*);
// 设置QAPM的外网上报域名(必需)
QAPM.*setProperty*(QAPM.*PropertyKeyHost*, "https://xxx.com:30025");
因为测试环境下的entrance与appconfig的端口可能不一,因此这里需要额外配置下appconfig的url,具体url找后台部署同学获取
try {
String url = "https://{appconfig的地址} /appconfig/v7/config/{appKey后面的数字}/";
BaseInfo.*urlMeta*.setConfigUrl(url);
ConfigProxy.*INSTANCE*.setConfig(new ConfigApply(new URL(url)));
} catch (Exception e) {
e.printStackTrace();
}
// 启动QAPM
QAPM.*beginScene*(QAPM.*SCENE_ALL*, QAPM.*ModeStable*);
a.请找部署同学获取私有云配置QAPM的外网上报域名
b.AppKey请到移动监控的配置页->产品配置页获取
c.接入时请到移动监控的配置页将所设置的用户ID配置白名单,确保功能不被采样
d.多个进程需要各自初始化QAPM
SDK初始化自检
1)如打印以下日志,则代表部分必需字段设置有误或者未设置,包括(app_key,version,user_id,device_id),SDK将不会启动,请根据1.3章节修改
参考TAG : QAPM_base_UserMeta
2)如打印以下日志,则代表部分必需字段设置有误或者未设置,包括(symbol_id, model),SDK可以启动,但前端可能会对这部分的数据展示有误,请根据1.2.1章节修改
参考TAG : QAPM_base_UserMeta
3)如打印以下日志,则代表可能是域名设置错误,请找后台服务人员确认域名
参考TAG : QAPM_manager_QAPMLauncher
4)如打印以下日志,则代表初步接入成功,可以验证数据上报/尝试开启高级功能
参考TAG : QAPM_manager_QAPMPluginManager
SDK初始化接口说明
public static QAPM setProperty(int key, Object value)
用途: 设置QAPM的相关参数
参数: key —— 需要设置的Key
- QAPM.PropertyKeyLogLevel--->开启日志等级(建议Debug版本开启QAPM.LevelDebug,release版本开启QAPM.LevelWarn)
- QAPM.PropertyNeedTranslate --->堆栈是否需要翻译,这里默认是需要翻译的,如果apk是没有混淆的需要传入false,否则前端可能会全部展示为unTranslated
参数:value —— 需要设置的内容
返回值:QAPM对象
public static boolean beginScene(String sceneName, long mode)
用途: 开启监控
参数: sceneName —— 场景名
参数: mode —— 开启的功能
- QAPM.ModeStable --->开启所有稳定功能(包含卡顿、掉帧、Crash、ANR、启动、http、webview(页面加载、JsError、网络))
- QAPM.ModeDropFrame --->开启流畅度采集
public static boolean endScene(String sceneName, long mode)
用途: 结束监控(只针对流畅度采集有效)
参数: sceneName —— 需要关掉的场景名(与beginScene的要相对应)
- QAPM.ModeDropFrame--->关闭流畅度采集
功能配置
崩溃监控及ANR监控
功能开启
Stable默认会开启Crash、Anr监控,该监控会默认监控Crash和Anr信息
自定义日志上报
QAPM(3.10.x版本后)提供了相关接口,可以在不幸发生了Crash或者Anr时,上传用户自定义的日志文件,示例如下
QAPM.setProperty(QAPM.*PropertyExtraDataListener*, new IExtraDataListener() {
// 发生ANR时会走这个回调
@Override
public List<String> onAnrExtraFileHandler() {
List<String> files = new ArrayList<>();
File[] fileArray = new File("xxxx").listFiles();
for (File file : fileArray) {
files.add(file.getAbsolutePath());
}
return files;
}
// 发生Crash时会走这个回调
@Override
public List<String> onCrashExtraFileHandler() {
List<String> files = new ArrayList<>();
File[] fileArray = new File("xxxx").listFiles();
for (File file : fileArray) {
files.add(file.getAbsolutePath());
}
return files;
}
});
功能开启校验
校验功能是否正常
- 当发生Crash、Anr时,打印如下日志,则代表QAPM正常收集了此次异常
检索TAG: QAPM_crash
- 当打印如下日志,则代表QAPM将此次异常上报成功,此处举例JavaCrash的上报情况。
检索TAG分别如下:
ANR:[plugin::140]
JavaCrash:[plugin::144]
NativeCrash:[plugin::146]
a.为避免出现卡死的情况,接口回调里的逻辑请尽量简单明了
b.上传的文件大小限制为20MB,大于限制则不上传,请选择认为有帮助的日志文件哦
c.Crash可以在移动监控的崩溃分析查看,Anr可以在移动监控的Anr分析中查看
流畅度采集
功能开启
Stable默认会开启掉帧监控,掉帧率的使用需要额外的埋点,建议打点在滑动列表上,如ListView、GridView、ReclyclerView等。
在每次滑动前调用QAPM.beginScene(“xxxx滑动”, QAPM.ModeDropFrame);
在滑动结束后调用QAPM.endScene(“xxxx滑动”, QAPM. ModeDropFrame);
一般可以通过重写滑动组件的onScrollStateChanged方法来实现,示例如下:
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == AbsListView.OnScrollListener.*SCROLL_STATE_IDLE*) {
QAPM.*endScene*("xxxx滑动", QAPM.*ModeDropFrame*);
} else {
QAPM.*beginScene*("xxx滑动", QAPM.*ModeDropFrame*);
}
}
@Override
public void onScrollStateChanged(RecyclerView view, int scrollState) {
if (scrollState == RecyclerView.*SCROLL_STATE_IDLE*) {
QAPM.*endScene*("xxxx滑动", QAPM.*ModeDropFrame*);
} else {
QAPM.*beginScene*("xxxx滑动", QAPM.*ModeDropFrame*);
}
}
功能开启校验
校验功能是否正常
- 滑动结束(调用endScene)后,打印出以下日志则代表掉帧率数据已经存入了本地数据库
检索TAG:QAPM_dropframe_DropFrameMonitor
- 在有埋点的情况下,启动五分钟之后,打印出以下数据,代表上报成功
检索TAG:[plugin::101]
a.beginScene与endScene需要成对出现,多次beginScene以第一次为准
b.掉帧数据在启动5分钟后上报
卡慢监控
功能开启
Stable默认会开启卡顿监控,卡顿监控无需埋点
功能开启校验
校验功能是否正常
- 如打印以下日志,则代表卡顿监控正常
检索TAG:QAPM_looper_LooperPrinter
- 如打印以下日志,则代表卡顿上报正常
检索TAG:[plugin::102]
a.卡顿有30%的抽样,即不是每一次的卡顿都会上报,测试阶段可多触发几次
b.上报后的卡顿在移动监控页面->卡慢->卡慢分析中查看
启动体验监控
功能开启
启动监控需要使用qapmplugin插件在编译期间进行插桩操作,默认插桩点为Application与Activity的各个生命周期
SDK默认统计的启动耗时为Application的attachBaseContext到第一个Activity的onResume结束
如果想统计启动区间内的某些方法的耗时,则需要额外的打点,示例如下
参考代码
QAPM.*beginScene*(StageConstant.*QAPM_APPLAUNCH*, "xxx方法名", QAPM.*ModeResource*);
/**业务逻辑*/
QAPM.*endScene*(StageConstant.*QAPM_APPLAUNCH*, "xxx方法名", QAPM.*ModeResource*);
功能开启校验
校验功能是否正常
- 每次启动后10s,如打印以下日志,则代表启动指标数据上报成功
检索TAG:[plugin::114]
- 每次启动后10s,如打印以下日志,则代表启动个例数据上报成功
检索TAG:[plugin::166]
a.需要使用qapmplugin插件进行插桩才可用,否则无效。请注意是否有使用tinker,如有使用,请参考1.1.6章节进行配置。
b.启动总耗时大于2.5s才会上报个例数据
c.启动的问题数据可以在移动监控的启动栏目->启动体验分析中查看
网络监控
功能开启
网络监控需要使用qapmplugin插件进行插桩才可使用,qapm目前仅支持Okhttp3的网络监控。
这里可以根据使用网络通信模式选择相关的http库和版本,如下是apm在编译期间依赖的库和版本信息仅供参考(原则上只支持大于或等于以下版本)。特别的如果有依赖okhttp3那么还必须依赖okio 1.14.0以上的库版本。
implementation 'com.squareup.okio:okio:1.14.0'
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
implementation 'com.squareup.okhttp3:okhttp-urlconnection:3.11.0'
功能开启校验
校验功能是否正常
- 每次网络请求后1分钟,如打印以下日志,则代表网络数据上报成功
检索TAG:[plugin::142]
a.需要使用qapmplugin插件进行插桩才可用,否则无效
b.SDK只负责抓取网络请求的相关信息,问题数据由后台分析,如慢请求(请求速率小于xxx),网络错误(请求响应码>=400)
c.数据在移动监控的网络栏目->网络分析->网络错误\慢请求栏目查看
Webview体验监控
功能开启
初始化需要开启WebView、JsError、Web网络监控,还需要配置以下代码
WebView监控需要开启与JavaScript交互,在WebView初始化时调用如下代码开启
WebSettings webSetting = webView.getSettings();
webSetting.setJavaScriptEnabled(true);
在WebView初始化完成之后加入JAVA与JS之间的调用接口通道,目的是让js层获取到java层的一些配置信息
WebView webView = new WebView(this);
webView.addJavascriptInterface(QAPMJavaScriptBridge.getInstance(),"QAPMAndroidJsBridge");
在WebView的shouldInterceptRequest代码里加入以下方法,用于拦截web-sdk并改用本地SDK资源,请确保在该回调中的最早地方调用以下代码。 如果是x5请使用以下代码:
@Override
public WebResourceResponse shouldInterceptRequest(WebView webView, String s) {
Object response = QAPMJavaScriptBridge.getInstance().shouldInterceptRequestWithX5(s);
if (response != null) {
return (WebResourceResponse) response;
}
return super.shouldInterceptRequest(webView, s);
}
如果是原生WebView请使用以下代码:
@Override
public WebResourceResponse shouldInterceptRequest(WebView webView, String s) {
WebResourceResponse response = QAPMJavaScriptBridge.getInstance().shouldInterceptRequest(s);
if (response != null) {
return response;
}
return super.shouldInterceptRequest(webView, s);
}
在WebView的onPageFinished代码里加入以下方法,用于注入JS脚本
webView.setWebViewClient(new WebViewClient(){
@RequiresApi(api = Build.VERSION_CODES.*KITKAT*)
@Override
public void onPageFinished (WebView view) {
super.onPageFinished(view, url);
QAPMJavaScriptBridge.*getInstance*().initFileJS(view);
}
});
功能开启校验
校验功能是否正常
原生WebView、JsError监控:
1)代码中加入以下代码(用于远程调试)
WebView.setWebContentsDebuggingEnabled(true);
2)打开谷歌浏览器,地址栏输入chrome://inspect,在出现的设备中点击inspect
3)进入后找到Console模块查询日志,如出现web start success ,vxxx,则代表websdk注入成功
4)检测各个功能是否上报正常,以jserror上报为例,如下:
每次触发JsError错误,如打印以下日志,则代表JsError数据上报成功
检索TAG:[plugin::143]
其余检索TAG分别如下:
页面加载:plugin=141(每次Web页面加载完成后即会上报)
网络请求:plugin=154(出现错误网络和慢请求时会上报)
a.如需查看WebView监控是否正常需要通过chrome等浏览器调试查看
b.页面加载只有页面加载时长大于3.5s才可在问题个例详情里查看
c.网络请求只有在网络错误和网络慢时才会上报
d.如果需要处理上报的webview的url,那么请参考“webview自定义页面功能验证”文档,如果不做处理,web-sdk会默认去掉url问号后面的参数作为上报的url
e. 由于浏览器限制,当访问跨域的JS文件时会出现获取到的JS堆栈信息为“script error”的情况。 您可通过以下步骤解决该问题: 1)在HTML的script标签中设置 crossorigin="anonymous" 代码参考: 2)将存放js文件的 CDN 中做如下设置 Access-Control-Allow-Origin: *
Https双向证书校验
本功能默认不开启,如想要防止被抓包来保证数据流的安全性,请参考以下接口进行配置
参考代码
// 传入证书名称,请确保将对应名称的证书放置Asset目录下,如未放置则会读取失败
ApmCertConfig.*addCert*("athena.cer");
// 传入一组证书的名称,请确保将对应名称的证书放置Asset目录下,如未放置则会读取失败
ApmCertConfig.*addAllCert*(Arrays.*asList*("sngapm.cer", "qapm.cer", "athena.cer"));
// 传入Cer证书的PubKey
try {
CertificateFactory factory = CertificateFactory.*getInstance*("X.509");
FileInputStream is = new FileInputStream("xxx.cer");
X509Certificate certificate = factory.generateCertificate(is);
PublicKey publicKey = certificate.getPublicKey();
ApmCertConfig.addPubKey(publicKey.getEncoded());
} catch (Exception e) {
e.printStackTrace();
}
// 传入一组Cert证书的PubKey,获取证书PubKey的方式可以参考上方
ApmCertConfig.addAllPubKey(Arrays.asList(new byte[]{0x01}, new byte[]{0x01}));
// 传入自定义的SSLSocketFactory
ApmCertConfig.setCustomSSLSocketFactory(传入自定义的SSLSocketFactory);
a.上述三种类型的配置,只可选择其中一种,混合的话不生效(证书名称、pubkey、sslSocketFactory(建议))
b.一旦设置,无论是否找到证书,都会进行强校验,校验失败则会导致数据无法上报
c.请确保对应的证书放置在Asset目录下
d.证书的有效期一般为1年,请记得及时更换证书!
符号表配置
在app的gradle文件中加入如下代码
各项配置如下:
preBuild.dependsOn(UUIDGenerator) //生成UUID,用于符号表的唯一标识
在QAPM初始化时加入如下代码
参考代码
QAPM.*setProperty*(QAPM.*PropertyKeySymbolId*, BuildConfig.*QAPM_UUID*);
在有数据上报的前提下,来到前端页面,下面以Crash为例子上传符号表
1)进入到移动监控前端页面,找到崩溃-崩溃分析,往下滑找到问题列表
2)点击进入,往下滑,在右侧的模块里找到上传(更新)mapping/so文件的按钮
3)前置准备
如果您需要翻译java堆栈,请将后缀为 .txt 的mapping文件打包成 zip 文件,如果您还需要翻译Native堆栈请将翻译Native堆栈所必须的so文件一并打包到zip包中,若有多个so文件则需要打包在一起统一上传。请注意,zip包中除mapping文件和so文件外不可以含有其他的文件,否则有可能上传失败。
4)上传mapping/so文件
在对应的问题详情页中可以通过点击上传mapping/so文件或者更新mapping/so文件弹出对应的上传框。
点击选择文件并选择文件后即自动上传任务,同一时间仅可执行一个上传任务。
成功上传后您可以通过该弹窗下载和更新符号表
5)手动翻译
当成功上传文件后即可点击堆栈信息上方的翻译按钮对堆栈进行翻译。以翻译的堆栈可以重新翻译,重新翻译将会以最新上传的文件为基础重新执行翻译功能。
若未上传对应的mapping/so文件,则无法点击翻译按钮。
注意: 需要使用qapmplugin插件生成UUID,UUID用于绑定符号表文件,原则上一个UUID对应一个apk及一个符号表文件
SDK缓存机制
SDK缓存路径为 sdcard/Android/data/{包名}/files/Tencent/QAPM/ 下的所有目录及文件
默认在启动SDK后的一分钟内会执行清理操作
各模块清理逻辑如下:
1)启动:清理3天前的启动文件
2)卡慢:清理3天前的卡顿文件
3)日志数据:清理3天前的日志文件
不清理的模块如下(不涉及缓存或者在下次启动会上报):
1)Crash&Anr
2)网络
3)WebView
4)卡慢
5)流畅度
使用到的开源信息说明
Okio(一般来说会由高版本覆盖低版本使用)
包名:com.squareup.okio:okio
版本:1.14.0
作用:用于解析网络的部分信息