文章出處

前言

對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編寫)


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()