前言
对app的线上bug的收集(友盟、云捕等)有时会得到这样的异常堆栈信息:没有一行代码是有关自身程序代码的。这使得对bug的解决无从下手,根据经验,内存不足OOM,Dialog关闭,ListView等相关代码很容易引起这类错误。下面总结下BaseAdapter.getView崩溃bug,然后给出如何编写代码来方便以后对它的定位。
BaseAdapter.getView
如果getView方法返回null,那么对应的ListView在显示时就直接触发NullPointerException异常。但是无论是哪个界面的哪个ListView发生了这个错误,对应的异常信息总是这样的:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.hxw.myapplication, PID: 1662
java.lang.NullPointerException
at android.widget.AbsListView.obtainView(AbsListView.java:2274)
at android.widget.ListView.makeAndAddView(ListView.java:1790)
at android.widget.ListView.fillDown(ListView.java:691)
at android.widget.ListView.fillFromTop(ListView.java:752)
at android.widget.ListView.layoutChildren(ListView.java:1630)
at android.widget.AbsListView.onLayout(AbsListView.java:2087)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1671)
at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1525)
at android.widget.LinearLayout.onLayout(LinearLayout.java:1434)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.widget.FrameLayout.layoutChildren(FrameLayout.java:453)
at android.widget.FrameLayout.onLayout(FrameLayout.java:388)
at android.view.View.layout(View.java:14817)
at android.view.ViewGroup.layout(ViewGroup.java:4631)
at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:1983)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1740)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:996)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5600)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:761)
at android.view.Choreographer.doCallbacks(Choreographer.java:574)
at android.view.Choreographer.doFrame(Choreographer.java:544)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:747)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5001)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
at dalvik.system.NativeStart.main(Native Method)
可以看到,整个异常信息提供的堆栈是不含任何与自己代码相关的调用信息的。
实际上,错误是因为getView返回null引起的,但是从上面的信息无法定位到到底哪个Adapter发生问题。
如果可以收集到用户是在哪个页面发生崩溃那么问题的定位会准确许多,但是,如果自己的Adapter需要返回好多种View(也就是getViewTypeCount的值,比如一个包含很多不同布局的对话列表),那么你还是需要仔细分析代码来找到具体哪个View的生成逻辑出了问题。
更多时候,除了得到以上的错误堆栈,对于真正的bug再无更多信息。实际上是无法判断出具体出问题的Adapter的。解决办法只能从根源上进行:
在我们编写getView方法时,对最终返回的参数自己进行非空判断,当针对不同position处的getItemViewType得到的View对象为null时,可以自己抛出一个NullPointerException,而不是等getView的调用者(框架API)来抛出上面给出的“没用”的信息。
也可以针对null的情况返回一个有用的信息给用户,而不是让程序崩溃。
// 在自己的Adapter子类中
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return buildView(position, convertView, parent);
}
private View buildView(int position, View convertView, ViewGroup parent) {
// ... 这里是根据getItemViewType生成不同View的逻辑,将View对象存储在convertView
if (convertView == null) {
// throw 一个Exception,包含position,getItemViewType的数据,方便定位
// 或者生成一个默认的View,提供给用户有用的信息——如果的确不至于让app crash的话
}
return convertView;
}
通过上面的方式,如果自己的getView的逻辑返回了null的话,就可以根据堆栈直接定位到错误代码的位置。
注意:getView返回null从java语法上是没问题的,虽然根据约定,它返回null肯定会引发空指针异常——但是这是对调用getView的方法而言。根据堆栈,在页面的ListView显示其childView时,如果getView返回null,
android.widget.AbsListView.obtainView
方法就抛出异常。堆栈信息只跟踪到LisView,而不会指向具体的Adapter。
(本文使用Atom编写)