利用工具排查Android疑难crash

精准定位线上代码段

当版本发布很频繁或者利用相同版本号灰度导致同一个版本的代码有可能不同的这种情况下,我们查一些线上问题就有可能不能精准定位到crash真实发生的代码行数。

我们可以利用gradle在中加入一些函数利用gradle中buildConfigField这个字段把git的信息和分支名带入到apk中,或者上报到自己的crash平台上这样我们就可以利用git SHA值还有分支名很快能够定位到crash的代码行数,并且也能够知道大概是谁的分支产生的crash

1.在自己项目的gradle中加入 底下三个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
def getVersionName = { ->
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'sh', '-c', 'git rev-list HEAD | wc -l | awk \'{print $1}\''
standardOutput = stdout
}
return stdout.toString().trim()
}
def getGitSha = { ->
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'sh', '-c', 'git rev-parse --short HEAD'
standardOutput = stdout
}
return stdout.toString().trim()
}
def getGitBranch = { ->
def stdout = new ByteArrayOutputStream()
exec {
commandLine 'sh', '-c', 'git rev-parse --abbrev-ref HEAD'
standardOutput = stdout
}
return stdout.toString().trim()
}

在defaultConfig增加下面字段

1
2
3
buildConfigField "String", "BUILD_CODE", "\"" + getVersionName() + "\""
buildConfigField "String", "GIT_SHA", "\"" + getGitSha() + "\""
buildConfigField "String", "GIT_BRANCH", "\"" + getGitBranch() + "\""

然后再编译后在BuildConfig这个类中就会多出这三个字端,我们直接调用就可以了

1
2
3
4
5
public final class BuildConfig {
public static final String VERSION_NAME = "1.3.3";
public static final String GIT_BRANCH = "develop_evilsoulm_future________";
public static final String GIT_SHA = "4b4790c";
}

利用ANR-WatchDog上报ANR日志

ANR一直是一个比较难排查和难察觉的问题,用户也去分不清楚anr和crash,并且anr在一般的crash收集平台都是采集不到的。想要解决线上anr一般只能靠联系用户复现,或者去抓取用户的trace文件,一般都是比较被动的方式,用户上报才回去解决,无法主动排查解决。

ANR-WatchDog 这个开源库就可以在运行时主动排查ANR。

原理

代码也比较少,原理也很简单,新开一个线程一直向主线程发消息,让主线程的int值改变,如果5秒(或者自定义的时间)内主线程的值没有改变也就说明线程被block住了,收集当前的堆栈信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
while (!isInterrupted()) {
lastTick = _tick;
//向主线程发消息
_uiHandler.post(_ticker);
try {
Thread.sleep(_timeoutInterval);
}
catch (InterruptedException e) {
_interruptionListener.onInterrupted(e);
return ;
}
// If the main thread has not handled _ticker, it is blocked. ANR.
//前面发的消息就是修改主线程的值,如果值没有改变的话就说明主线程被改变了
if (_tick == lastTick) {
if (!_ignoreDebugger && Debug.isDebuggerConnected()) {
if (_tick != lastIgnored)
Log.w("ANRWatchdog", "An ANR was detected but ignored because the debugger is connected (you can prevent this with setIgnoreDebugger(true))");
lastIgnored = _tick;
continue ;
}
ANRError error;
if (_namePrefix != null)
error = ANRError.New(_namePrefix, _logThreadsWithoutStackTrace);
else
error = ANRError.NewMainOnly();
_anrListener.onAppNotResponding(error);
return;
}
}

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int anrTimeOut = 30 * 1000;//在Release的时候可以提高一些时常。
if (BuildConfig.DEBUG) {
anrTimeOut = 5 * 1000;//在内部测试使用的时候条件可以严苛一点,提高代码质量
}
new ANRWatchDog(anrTimeOut).setANRListener(new ANRMonitor.ANRListener() {
@Override
public void onAppNotResponding(ANRError error) {
if (DebugConfig.isOpen()) {
throw error;//在内部使用的时候可以主动让程序crash方便开发人员排查
} else {
//线上则可以上报到自己的crash平台,来线上主动发现问题排查问题
Crashlytics.logException(error);
}
}
}).start();

利用Fabric排查线上疑难问题

fabric是一个类似于国内的友盟是一个移动信息收集平台,里面比较好用的功能就是Crash信息收集系统Crashlytics。

排查疑难crash

有的时候用户的crash信息有可能是一些系统层的堆栈信息,我们难以定位到用户的操作路径和用户发生crash的页面,比如下面这种crash基本上拿到就是处于懵逼的状态。

1
2
3
4
5
6
7
8
9
Fatal Exception: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean android.support.v7.widget.RecyclerView$LayoutManager.canScrollVertically()' on a null object reference
at android.support.v7.widget.RecyclerView.computeVerticalScrollOffset(RecyclerView.java:1540)
at android.view.View.canScrollVertically(View.java:13860)
at android.support.v4.view.ViewCompatICS.canScrollVertically(ViewCompatICS.java:35)
at android.support.v4.view.ViewCompat$ICSViewCompatImpl.canScrollVertically(ViewCompat.java:1338)
at android.support.v4.view.ViewCompat.canScrollVertically(ViewCompat.java:1840)
at android.support.v4.widget.SwipeRefreshLayout.canChildScrollUp(SwipeRefreshLayout.java:673)
at android.support.v4.widget.SwipeRefreshLayout.onInterceptTouchEvent(SwipeRefreshLayout.java:697)
at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2108)

像这种我们就可以利用crashlytics的日志上报功能上报用户的行为路径到Fabric上

1
2
3
4
5
6
7
public class AmeActivity extends AmeBaseComponentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//在基类中上报用户的当前页面名称
CrashlyticsWrapper.log("当前页面:" + this.getClass().getSimpleName());

效果

这样我们就可以清楚的知道用户的操作路径并且可以定位到最后发生crash的页面

当然我们也可以上报一些其他的信息上来,来帮定位crash

定位Native crash

crashlytics也是支持native crash收集上报的

一般我们看的crash信息就是

1
2
3
4
5
Crashed: droid.ugc.aweme
0 libffmpeg.so 0xd0e20048 av_read_frame
1 libffmpeg-invoker.so 0xd525ff71 playAudioSamples
2 libffmpeg-invoker.so 0xd5256055 Java_com_ss_android_medialib_FFMpegInvoker_playAudioSamples
3 data@app@com.ss.android.ugc.aweme-2@base.apk@classes.dex 0xe1f02ef7 (Missing)

只能定位到某个方法crash了但是无法定位到方法中的行数,当然如果上传了ndk的符号表上去,就可以定位到crash行数,当然也可以比较蠢,利用日志上报来定位https://docs.fabric.io/android/crashlytics/ndk.html