Android實際開發中的bug總結與解決方法(一)

 
Android開發中有很多bug,我們是完全可以在線下避免的,不要等到線上報的BUG的再去修復。下面是我在實際開發中遇到過的bug和解決方法。
BUG 1: 
java.lang.RuntimeException: Unable to start activity ComponentInfo {com.netease.caipiao.ssq/com.netease.caipiao.ssq.ExpertListActivity}: 
 android.support.v4.app.Fragment$InstantiationException:  Unable to instantiate fragment  com.netease.caipiao.ssq.tab.ExpertsListFragment: 
  make sure class name exists, is public, and has an empty constructor that is public
 
 復現:當app啓動後,進入異常頁面,然後使其進入後臺進程(按home鍵),接着改變系統設置如字體大小等方法,目的上讓app被系統殺死後恢復重現,這時候再點擊app進入應用,拋出異常。
問題描述:包含有fragment的Activity在異常被銷燬(如系統內存不足等)後,再進入恢復activity時,重新實例化fragment時拋出異常出錯。異常的原因就是因爲使用的fragment沒有public的empty constructor。
查看源代碼知:fragment在還原狀態中調用FragmentState#instantitae()->Fragment#instantitae()拋出異常。
具體Android源碼中拋出的異常代碼如下:
   
 /**
     * Create a new instance of a Fragment with the given class name.  This is
     * the same as calling its empty constructor.
     */
    public static Fragment instantiate(Context context, String fname, Bundle args) {
        try {
            Class<?> clazz = sClassMap.get(fname);
            if (clazz == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = context.getClassLoader().loadClass(fname);
                sClassMap.put(fname, clazz);
            }
            Fragment f = (Fragment)clazz.newInstance();
            if (args != null) {
                args.setClassLoader(f.getClass().getClassLoader());
                f.mArguments = args;
            }
            return f;
        } catch (ClassNotFoundException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (java.lang.InstantiationException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        } catch (IllegalAccessException e) {
            throw new InstantiationException("Unable to instantiate fragment " + fname
                    + ": make sure class name exists, is public, and has an"
                    + " empty constructor that is public", e);
        }
    }

上述代碼片的關鍵,其實就是通過java的反射機制進行實例化Fragment。實例化是調用的是Fragment f = (Fragment)clazz.newInstance();無參構造函數。
另外,如果需要傳參數的話,注意到實例化方法 public static Fragment instantiate(Context context, String fname, Bundle args)第三個構造函數,恢復時在代碼中用無參構造方法實例化fragment,然後判斷Bundle args是否爲空,將參數加載到f.mArguments = args;因此在fragment的onCreate()方法中可以使用getArguments()將參數還原。

解決方案: 爲了儘量的少的改動,提供新的靜態構造方法傳遞參數。

public static ExpertsListFragment getInstance(int pageNo, String subClassId) {
    ExpertsListFragment mFragment = new ExpertsListFragment();
    Bundle args = new Bundle();
    args.putInt("pageNo", pageNo);
    args.putString("subClassId", subClassId);
    mFragment.setArguments(args);
    return mFragment;
  }


然後在在fragment的onCreate()方法中可以使用getArguments()將參數還原:
public static ExpertsListFragment getInstance(int pageNo, String subClassId) {
    ExpertsListFragment mFragment = new ExpertsListFragment();
    Bundle args = new Bundle();
    args.putInt("pageNo", pageNo);
    args.putString("subClassId", subClassId);
    mFragment.setArguments(args);
    return mFragment;
  }


總結:當系統因爲內存緊張殺死非前臺進程(並非真正的殺死),然後用戶將被系統殺掉的非前臺app帶回前臺,如果這個時候有UI是呈現在Fragment中,那麼會因爲restore造成fragment需要通過反射實例對象,從而將之前save的狀態還原,而這個反射實例對象就是fragment需要Public的empty constructor的關鍵所在。這樣的BUG同時也出現在TrendsChartActivity和NewsListFragment中,使用同樣的方法修復。

BUG2:
java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState 
 
復現:常頁面在MainActivity中ft.commit()之前調用onstop()方法,讓MainActivity調用onSaveInstanceState和onRestoreInstanceState恢復

問題描述:根據FragmentTransaction的源碼中調用的流程是 ft.commit() -> return commitInternal(false) ->   commitInternal(boolean allowStateLoss) -> mManager.enqueueAction(this, allowStateLoss) -> checkStateLoss() -> 拋出異常。
Android源碼中拋出的異常代碼如下:
 
 private void checkStateLoss() {
        if (mStateSaved) {
            throw new IllegalStateException(
                    "Can not perform this action after onSaveInstanceState");
        }
        if (mNoTransactionsBecause != null) {
            throw new IllegalStateException(
                    "Can not perform this action inside of " + mNoTransactionsBecause);
        }
    }

解決方法一:將commit()改成commitAllowingStateLoss();源碼中調用流程:ft.commitAllowingStateLoss() -> return commitInternal(true) ->   commitInternal(boolean allowStateLoss) -> mManager.enqueueAction(this, allowStateLoss)  allowStateLoss爲true不執行checkStateLoss()沒有異常拋出

但這樣的方法:commit()函數和commitAllowingStateLoss()函數的唯一區別就是當發生狀態丟失的時候,後者不會拋出一個異常。通常不應該使用這個函數,因爲它意味可能發生狀態丟失。
解決方法二更好的解決方案是讓 commit()函數確保在 Activity的 狀態保存之前調用,這樣會有一個好的用戶體驗。可用一個狀態標誌位 isSaved 來判斷,在onSaveInstanceState(),onStop()等方法中將 isSaved 設置爲true即可。這樣在ft.commit()之前先判斷 isSaved ,若爲false執行ft.commit(),爲假執行。

BUG 3:java.lang.IndexOutOfBoundsException:

復現:下拉刷新加載上時,點擊了LIstView中在UI線程中clean了的Items,然後調用getItem(position)就會拋異常IndexOutOfBoundsException

問題描述:由刷新機制引起的。下拉刷新加載上時,點擊了沒有在UI線程clean完的Items,然後調用getItem(position)就會拋異常IndexOutOfBoundsException

Android源碼中拋出的異常代碼如下:

 public Object getItem(int position) {
        // Header (negative positions will throw an IndexOutOfBoundsException)
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return mHeaderViewInfos.get(position).data;
        }
        // Adapter
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if (mAdapter != null) {
            adapterCount = mAdapter.getCount();
            if (adjPosition < adapterCount) {
                return mAdapter.getItem(adjPosition);
            }
        }
        // Footer (off-limits positions will throw an IndexOutOfBoundsException)
        return mFooterViewInfos.get(adjPosition - adapterCount).data;
    }

解決方法:原來是刷新是數據被清除,網絡請求完成後再刷新載加載數據。如果網速不好的話,會用一段空白期。現在的機制是,在網絡請求完成後,刷新數據時,不清除數據先,當網絡數據返回 時判斷Items.size() > 0 來確定是否Items.clear()。在NewFragmentList,ExpertsListFragment,ExpertColumnActivity中都有這樣的問題。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章