转载请标注:http://blog.csdn.net/friendlychen/article/details/73299095
一、捕获bug
Android开发中经常会遇到各种异常,如果在开发阶段还好,直接在log控制平台上捕获。万一要是遇到线上的问题,有的还真不好处理。不过呢,现在有好多第三方平台来供我们捕获线上的bug,比如友盟等。友盟的错误统计功能使用起来也非常方便,使用官方文档,相信大家都会集成。这个错误统计功能也非常强大,可以实时统计线上的各种bug,出现这种问题的机型,问题时间,APP版本号,次数等等,还有最重要的log日志了。
二、解决bug
当我们遇到这些问题的时候,又有什么样好的方法来解决呢。看着那些大段的错误日志千万不能慌了手脚,其实这些日志都是解决这些bug的重要依据。
1、NullPointerException分析
java.lang.NullPointerException: Attempt to invoke virtual method 'boolean co......Repo.isSuccess()' on a null object reference
at com.r......Utils$1.onResponse(Utils.java:42)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run(ExecutorCallAdapterFactory.java:68)
at android.os.Handler.handleCallback(Handler.java:743)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:5621)
......
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684)
这个问题相信基本上所有开发者都会处理。就是一个对象没有初始化。下面贴出源码,其实这里的问题就是使用Retrofit来加载数据,但是加载的数据是null的,所以导致repo对象是空的,空的当然不能使用当前的方法啦。 (哎。服务器最近不知道出了什么问题,这两天经常是返回的数据为null,而我们原来的项目中并没有做非空判断!!!这点非常重要,谨记,什么情况一定要做非空判断!PS:不是我写的。。。真的)。
ApiModule.apiService().getToken(rand).enqueue(new Callback<Repo>() {
@Override
public void onResponse(Call<Repo> call, Response<Repo> response) {
if (response != null){
Repo repo = response.body();
if (repo != null && repo.isSuccess()) {//这里不做判断的话,有可能导致repo.isSuccess()空指针异常
......
} else {
......
}
}
}
@Override
public void onFailure(Call<Repo> call, Throwable t) {
......
}
}
空指针异常没什么特殊的,基本就是对象米有初始化,找到出问题的那个类,就可以很轻松的找到未初始化的对象了。
2、IllegalArgumentException分析
java.lang.IllegalArgumentException: You cannot start a load for a destroyed activity
at com.bumptech.glide.manager.RequestManagerRetriever.assertNotDestroyed(RequestManagerRetriever.java:134)
at com.bumptech.glide.manager.RequestManagerRetriever.get(RequestManagerRetriever.java:102)
at com.bumptech.glide.manager.RequestManagerRetriever.get(RequestManagerRetriever.java:87)
at com.bumptech.glide.Glide.with(Glide.java:629)
at com.r......utils.glide.GlideHelper.loadImage(GlideHelper.java:229)
at com.r......DetailActivity.init(.....DetailActivity.java:423)
......
......
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:887)
这个是在使用Glide的时候遇到的一个问题。这里如果单纯的找到日志中出现的我们写的类,并不能很好的找到解决思路。像这类问题呢,其实有个重要的方法那就是追本溯源,什么意思呢。别看着错误log日志一大堆,这段日志第一行大意是非法参数异常,不能为一个销毁的activity加载数据。第一行只是告诉我们错误大致意思,其实也知道了错误原因了。但是从第二行才可以找到问题的根源了。代码是:at com.bumptech.glide.manager.RequestManagerRetriever.assertNotDestroyed(RequestManagerRetriever.java:134)。这段代码可以从Glide源码中找出。找到这个RequestManagerRetriever类的assertNotDestroyed()方法。源码如下:
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
private static void assertNotDestroyed(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed()) {
throw new IllegalArgumentException("You cannot start a load for a destroyed activity");
}
}
这里我们可以看出You cannot start a load for a destroyed activity这句话就是从这里向IllegalArgumentException()中作为参数传入,并且抛出了这个异常。在看这段代码,有个判断,一个是版本号大于J,并且activity.isDestroyed()都为真时,才抛出这个异常。版本号现在基本都是大于J了,而当我们activity销毁后,Glide还在工作,这时就抛出了这个异常了。相当于我主线程都销毁了,你Glide特么还工作个毛线?加载个数据谁要?因此程序不干了,给你报错吧。这里说明Glide写的真心不错的。这里抛出的这个异常信息还是非常明确的。下面来看看怎么处理:
if (Util.isOnMainThread()){
Glide.with(getApplicationContext()).load(url).into(view);
}
......
@Override
protected void onDestroy() {
super.onDestroy();
if (Util.isOnMainThread()){
Glide.with(this).pauseRequest();
}
}
在onDestory()中加入了判断,如果Activity销毁的话,那么Glide停止工作啦。老板都走了你加班给谁看呢???
从这个例子可以看出,log日志中除了我们自己写的类注意观察一下外,前两行日志还是非常重要的。
3、IllegalStateException分析
java.lang.IllegalStateException: Fragment U...eFragment{1c8b3dfa} not attached to Activity
at android.support.v4.app.Fragment.getResources(Fragment.java:641)
at com.ru...fragment.U...Fragment.get...Bg(U...Fragment.java:1058)
at com.r...fragment.U...Fragment$11.onResponse(U...Fragment.java:833)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run(ExecutorCallAdapterFactory.java:68)
at android.os.Handler.handleCallback(Handler.java:739)
at ......
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832)
这里我们还是可以从刚说的前两行代码去分析错误原因。第一行是java.lang.IllegalStateException: Fragment U…eFragment{1c8b3dfa} not attached to Activity,这里意思是一个非法状态异常,Fragment没有和Activity依附。从第二行中,android.support.v4.app.Fragment.getResources(Fragment.java:641),找到相应的源码:
/**
* Return <code>getActivity().getResources()</code>.
*/
final public Resources getResources() {
if (mHost == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
return mHost.getContext().getResources();
}
这是Fragment中getResource()的源码,跟上个例子一样,”Fragment ” + this + ” not attached to Activity”是作为IllegalStateException()中参数,并且判处异常。源码里的这个判断很重要,从判断可知,如果mHost为空的话,则抛出异常。说明这里已经为空了,所以程序抛出了异常。
// Host this fragment is attached to.
这里是对mHost的解释,意思就是这个fragment依附的地方。这里指的就是当前Fragment所依附的Activity。这里也是说如果Activity都销毁了,你getResource()还工作干嘛?这里的解决方法就是想办法判断一下,如果fragment依附在Activity,则用这个getResourc()方法:
if (isAdded()){//可以使用这个方法;这样就保证不会出这个问题了。。。
getResources();
}
这个isAdded()方法是Fragment提供的,源码:
/**
* Return true if the fragment is currently added to its activity.如果fragment正在依附在Activity中则为真。
*/
final public boolean isAdded() {
return mHost != null && mAdded;
}
这样就保证了使用getResource()方法时不会出现这个异常。
三、总结
好了,这里简单分析了两个例子,算是抛砖引玉,总结了异常处理的一个很好的思路,那就是追本溯源。错误日志中出现我们写的类之外,第一行和第二行代码还是非常重要的,尤其是第二行,也就是第一个at那一行,找到相应的源码,这就是我们错误的源头!基本上我们报错,就是在这里抛出异常,或者是别的信息,这个很重要。从这里有时候我们可以得到解决bug的思路,一般抛出异常,都会有一个判断,我们在代码中,只要避开这个判断就可以避免抛出异常啦。