安卓版本发布之后遇到bug怎么办_Android实际开发中的bug总结与解决方法(一)...

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源码中抛出的异常代码如下:

[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.

*/

publicstaticFragmentinstantiate(Contextcontext,Stringfname,Bundleargs){

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);

}

Fragmentf=(Fragment)clazz.newInstance();

if(args!=null){

args.setClassLoader(f.getClass().getClassLoader());

f.mArguments=args;

}

returnf;

}catch(ClassNotFoundExceptione){

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.InstantiationExceptione){

thrownewInstantiationException("Unable to instantiate fragment "+fname

+": make sure class name exists, is public, and has an"

+" empty constructor that is public",e);

}catch(IllegalAccessExceptione){

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

publicstaticExpertsListFragmentgetInstance(intpageNo,StringsubClassId){

ExpertsListFragmentmFragment=newExpertsListFragment();

Bundleargs=newBundle();

args.putInt("pageNo",pageNo);

args.putString("subClassId",subClassId);

mFragment.setArguments(args);

returnmFragment;

}

然后在在fragment的onCreate()方法中可以使用getArguments()将参数还原:

[java]view plain copy

publicstaticExpertsListFragmentgetInstance(intpageNo,StringsubClassId){

ExpertsListFragmentmFragment=newExpertsListFragment();

Bundleargs=newBundle();

args.putInt("pageNo",pageNo);

args.putString("subClassId",subClassId);

mFragment.setArguments(args);

returnmFragment;

}

总结:当系统因为内存紧张杀死非前台进程(并非真正的杀死),然后用户将被系统杀掉的非前台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

privatevoidcheckStateLoss(){

if(mStateSaved){

thrownewIllegalStateException(

"Can not perform this action after onSaveInstanceState");

}

if(mNoTransactionsBecause!=null){

thrownewIllegalStateException(

"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

publicObjectgetItem(intposition){

// Header (negative positions will throw an IndexOutOfBoundsException)

intnumHeaders=getHeadersCount();

if(position

returnmHeaderViewInfos.get(position).data;

}

// Adapter

finalintadjPosition=position-numHeaders;

intadapterCount=0;

if(mAdapter!=null){

adapterCount=mAdapter.getCount();

if(adjPosition

returnmAdapter.getItem(adjPosition);

}

}

// Footer (off-limits positions will throw an IndexOutOfBoundsException)

returnmFooterViewInfos.get(adjPosition-adapterCount).data;

}

解决方法:原来是刷新是数据被清除,网络请求完成后再刷新载加载数据。如果网速不好的话,会用一段空白期。现在的机制是,在网络请求完成后,刷新数据时,不清除数据先,当网络数据返回 时判断Items.size() > 0 来确定是否Items.clear()。在NewFragmentList,ExpertsListFragment,ExpertColumnActivity中都有这样的问题。

Android实际开发中的bug总结与解决方法(二)

解决bug中的总结:Fragment Transactions 和Activity状态丢失

Fragment transactions用于在一个Activity上添加、移除或者替换fragment。大多数时候,fragment transaction会在activity的onCreate()方法中执行,也可能在与用户交互中响应。

然而,BUG是当恢复一个activity时,fragment transaction被执行了,应用就可能发生下面的下崩溃:

BUG、使用actionProvider时出现的问题

bug复现:

解决方案:

换一种import的方式即可。tmd,这就是一个坑。

BUG :背景墙设置失效

采用XUTILS的图片缓存技术做了个小米电视的app,加了一个配置图片仓库和图片数量的对话框。如果配置完,程序重启什么都ok,但是一旦关机就恢复初始状态,原因是自己

在写程序的时候大意了。

1 String tmpBucketName =LocalDataDeal.readBucketNameFromLocalData();2   String tmpBucketNum =LocalDataDeal.readBucketNumFromLocalData();3 String tmpBucketWaterMark =LocalDataDeal.readBucketWaterMarkFromLocalData();4 5 if(tmpBucketName != null && tmpBucketName != "" && tmpBucketNum != "" && tmpBucketNum != null && tmpBucketWaterMark != null && tmpBuckeWaterMark != "")6 {7 if(Integer.parseInt(tmpBucketNum) > 1)8 {9 QiNiuBucketName =tmpBucketName;10 QiNiuBucketNumber =Integer.parseInt(tmpBucketNum);11 QiNiuBucketWaterMark =tmpBucketWaterMark;12 }13 QiNiuBucketName =LocalDataDeal.readBucketNameFromLocalData();14 }

问题出在了对第五行对waterMark的处理,因为允许设置是否显示水印,而水印不存在的时候就是tmpBuckerWaterMark为null的时候,所以对于没有设置水印的仓库配置,是永远不会显示的。

还有一点,就是在对字符串比较的时候,除了和null对比可以直接用==符号,其余比较都得用equal方法进行对比。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值