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
[java] view plain copy
/**
* Create a new instance of a Fragment with the given class name. This is
* the same as calling its empty constructor.
*/
publicstaticFragment 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){
thrownewInstantiationException("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){
thrownewInstantiationException("Unable to instantiate fragment "+ fname
+": make sure class name exists, is public, and has an"
+" empty constructor that is public", e);
}catch(IllegalAccessException e){
thrownewInstantiationException("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()将参数还原。
解决方案: 为了尽量的少的改动,提供新的静态构造方法传递参数。
[java] view plain copy
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()将参数还原:
[java] view plain copy
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源码中抛出的异常代码如下:
[java] view plain copy
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源码中抛出的异常代码如下:
[java] view plain copy
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中都有这样的问题。
客官有没有遇到过上述的问题呢, 试试吧,或许对你有用哦. 有用就给我点个赞