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

  1. [java] view plain copy
  2. /**
  3. * Create a new instance of a Fragment with the given class name. This is
  4. * the same as calling its empty constructor.
  5. */
  6. public static Fragment instantiate(Context context, String fname, Bundle args) {
  7. try {
  8. Class<?> clazz = sClassMap.get(fname);
  9. if (clazz == null) {
  10. // Class not found in the cache, see if it‘s real, and try to add it
  11. clazz = context.getClassLoader().loadClass(fname);
  12. sClassMap.put(fname, clazz);
  13. }
  14. Fragment f = (Fragment)clazz.newInstance();
  15. if (args != null) {
  16. args.setClassLoader(f.getClass().getClassLoader());
  17. f.mArguments = args;
  18. }
  19. return f;
  20. } catch (ClassNotFoundException e) {
  21. throw new InstantiationException("Unable to instantiate fragment " + fname
  22. + ": make sure class name exists, is public, and has an"
  23. + " empty constructor that is public", e);
  24. } catch (java.lang.InstantiationException e) {
  25. throw new InstantiationException("Unable to instantiate fragment " + fname
  26. + ": make sure class name exists, is public, and has an"
  27. + " empty constructor that is public", e);
  28. } catch (IllegalAccessException e) {
  29. throw new InstantiationException("Unable to instantiate fragment " + fname
  30. + ": make sure class name exists, is public, and has an"
  31. + " empty constructor that is public", e);
  32. }
  33. }

上述代码片的关键,其实就是通过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()将参数还原。

解决方案: 为了尽量的少的改动,提供新的静态构造方法传递参数。

  1. [java] view plain copy
  2. public static ExpertsListFragment getInstance(int pageNo, String subClassId) {
  3. ExpertsListFragment mFragment = new ExpertsListFragment();
  4. Bundle args = new Bundle();
  5. args.putInt("pageNo", pageNo);
  6. args.putString("subClassId", subClassId);
  7. mFragment.setArguments(args);
  8. return mFragment;
  9. }

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

  1. [java] view plain copy
  2. public static ExpertsListFragment getInstance(int pageNo, String subClassId) {
  3. ExpertsListFragment mFragment = new ExpertsListFragment();
  4. Bundle args = new Bundle();
  5. args.putInt("pageNo", pageNo);
  6. args.putString("subClassId", subClassId);
  7. mFragment.setArguments(args);
  8. return mFragment;
  9. }

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

  1. [java] view plain copy
  2. private void checkStateLoss() {
  3. if (mStateSaved) {
  4. throw new IllegalStateException(
  5. "Can not perform this action after onSaveInstanceState");
  6. }
  7. if (mNoTransactionsBecause != null) {
  8. throw new IllegalStateException(
  9. "Can not perform this action inside of " + mNoTransactionsBecause);
  10. }
  11. }

解决方法一:将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源码中抛出的异常代码如下:

  1. [java] view plain copy
  2. public Object getItem(int position) {
  3. // Header (negative positions will throw an IndexOutOfBoundsException)
  4. int numHeaders = getHeadersCount();
  5. if (position < numHeaders) {
  6. return mHeaderViewInfos.get(position).data;
  7. }
  8. // Adapter
  9. final int adjPosition = position - numHeaders;
  10. int adapterCount = 0;
  11. if (mAdapter != null) {
  12. adapterCount = mAdapter.getCount();
  13. if (adjPosition < adapterCount) {
  14. return mAdapter.getItem(adjPosition);
  15. }
  16. }
  17. // Footer (off-limits positions will throw an IndexOutOfBoundsException)
  18. return mFooterViewInfos.get(adjPosition - adapterCount).data;
  19. }

解决方法:原来是刷新是数据被清除,网络请求完成后再刷新载加载数据。如果网速不好的话,会用一段空白期。现在的机制是,在网络请求完成后,刷新数据时,不清除数据先,当网络数据返回 时判断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被执行了,应用就可能发生下面的下崩溃:

[java] view plain copy

  1. java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
  2. at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1327)
  3. at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager:1338)
  4. at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
  5. at android.support.v4.app.BackStackRecord.commit(BackStackRecord:574)
  6. at android.support.v4.app.DialogFragment.show(DialogFragment:127)

原因:不管何时,如果一个FragmentActivity放在后台,对应FragmentMangerImpl中mStateSaved的flag就会设置为true。这个flag是用来检查是否有state loss。

当试图执行一个transaction时,如果这个flag为true,那么就首先会抛出IllegalStateException异常。

那为什么会抛出这个异常呢?这个问题源于这样的事实,Bundle对象代表一个Activity在调用onSaveInstanceState()方法的一个瞬间快照。

这个transaction将不会被记住,因为它没有在第一时间记录为这个Activity的状态的一部分。Android不惜一切代价避免状态的丢失。

这意味着,当你在onSaveInstanceState()方法调用后会调用FragmentTransaction的commit方法。因此,在有些时候,都将简单的抛出一个IllegalStateException异常。

Honeycomb之前的版本                 更新版本
Activities会在onPause()调用前被结束? NO NO
Activities会在onStop()调用前被结束? YES NO
onSaveInstanceState(Bundle)会在哪些方法调用前被执行? onPause() onStop()

作为Activity生命周期已做的细微改变的结果,Fragment的Support Library有时候需要根据平台的版本来改变它的行为。

                      Honeycomb之前的版本               更新版本
commit()在onPause()前被调用 OK OK
commit()在onPause()和onStop()执行中间被调用 STATE LOSS OK
commit()在onStop()之后被调用 EXCEPTION EXCEPTION

建议一

不要在让transactions在其他的Activity生命周期函数提交,如onActivityResult()onStart()onResume(),事情将会变得微妙。

例如,你不应该在FragmentActivity的onResume()方法中提交transactions。因为有些时候这个函数可以在Activity的状态恢复前被调用。

如果你的应用要求在除onCreate()函数之外的其他Activity生命周期函数中提交transaction,你可以在FragmentActivity的onResumeFragments()函数或者Activity的onPostResume()函数中提交。

这两个函数确保在Activity恢复到原始状态之后才会被调用,从而避免了状态丢失的可能性。

nResume和onResumeFragments的区别是什么呢?下面是官方文档 对FragmentActivity.onResume的解释:

将onResume() 分发给fragment。注意,为了更好的和旧版本兼容,这个方法调用的时候,依附于这个activity的fragment并没有到resumed状态。

意味着在某些情况下,前面的状态可能被保存了,此时不允许fragment transaction再修改状态。

从根本上说,你不能确保activity中的fragment在调用Activity的OnResume函数后是否是onresumed状态,

因此你应该避免在执行fragment transactions直到调用了onResumeFragments函数。

建议二

避免在异步回调函数中提交transactions。包括常用的方法,比如AsyncTask的onPostExecute方法和LoaderManager.LoaderCallbacks的onLoadFinished方法。

在这些方法中执行transactions的问题是,当他们被调用的时候,他们完全没有Activity生命周期的当前状态。例如,考虑下面的事件序列:

  1. 一个Activity执行一个AsyncTask。
  2. 用户按下“Home”键,导致Activity的onSaveInstanceState()onStop()方法被调用。
  3. AsyncTask完成并且onPostExecute方法被调用,而它没有意识到Activity已经结束了。
  4. 在onPostExecute函数中提交的FragmentTransaction,导致抛出一个异常。

一般来说,避免这种类型异常的最好办法就是不要在异步回调函数中提交transactions。

如果你的应用需要在这些回调函数中执行transaction,而没有简单的方法可以确保这个回调函数不好在onSaveInstanceState()之后调用。

那么,可能需要使用commitAllowingStateLoss方法,并且处理可能发生的状态丢失。

建议三

作为最后的办法,使用commitAllowingStateLoss()函数。commit()函数和commitAllowingStateLoss()函数的唯一区别就是当发生状态丢失的时候,后者不会抛出一个异常。

当然,更好的解决方案是commit函数确保在Activity的状态保存之前调用,这样会有一个好的用户体验。除非状态丢失的可能无可避免,否则就不应该使用commitAllowingStateLoss()函数。

 

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


解决bug中的总结:Bitmap 内存优化相关


 XXXXX项目中相关的bug有2个:

1) 在生成圆角图片的RoundImageView的onDraw()方法中 :bug: bitmap size exceeds VM budget .

2) 在SSQSplashActivity的onCreate()方法中加载欢迎界面的图片时 bug: OutOfMemoryError.

Bitmap 内存优化:

1)   要及时回收Bitmap的内存

Bitmap类的构造方法都是私有的,所以不能直接new出一个Bitmap对象,只能通过BitmapFactory类的各种静态方法来实例化一个Bitmap。

查看BitmapFactory的源代码可以看到,生成Bitmap对象最终都是通过JNI调用方式实现的。所以,加载 Bitmap到内存里以后,是包含两部分内存区域的。简单的说,一部分是Java部分的,一部分是C部分的。

这个Bitmap对象是由Java部分分配的,不用的时候系统就会自动回收了,但是那个对应的C可用的内存区域,虚拟机是不能直接回收的,这个只能调用底层的功能释放。所以需要调用 recycle()方法来释放C部分的内存。

从Bitmap类的源代码也可以看到,recycle()方法里也的确是调用了JNI方法了的。

对于第一个bug:

RoundImageView控件中采用的生成圆角图片的方法是setXfermode(Mode.SRC_IN)+canvas来实现的,其中在onDraw()方法中共在创建了3个bitmap对象,其中一个是bitmap = b.copy(Bitmap.Config.ARGB_8888, true)的拷贝其实现实是调用的JNI的方法在C底层实现,但是在最后仅仅是将bitmap赋值为null了,如bmp = null;这样的话,可能存在Android系统对java层的bitmap做了回收,而没有用 recycle()方法调用JNI的来彻底回收C部分的内存。

解决办法:生成圆角图片有一个更好的实现方法是:BitmapShader(Bitmap bitmap,Shader.TileMode tileX,Shader.TileMode tileY)。调用这个方法来产生一个画有一个位图的渲染器(Shader)。该方法实现简单高效,节约内存开销。查看球神的源码中,就是用的这种方法。于是我们将球神中的CircleImageView控件,替换了之前的RoundImageView控件。


2)捕获异常

因为Bitmap是内存消耗大户,为了避免应用在分配Bitmap内存的时候出现OutOfMemory异常以后Crash掉,需要特别注意实例化Bitmap部分的代码。

通常,在实例化Bitmap的代码中,一定要对OutOfMemory异常进行捕获。

3) 对ImageView等图片的资源的操作


尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,
因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存。

然而,可以改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source,
decodeStream最大的不同之处在于其直接调用JNI>>nativeDecodeAsset()来完成decode,无需再使用java层的createBitmap,从而节省了java层的空间。

但是,decodeStream有这么一个缺点:

decodeStream直接拿的图片来读取字节码了, 不会根据机器的各种分辨率来自动适应,使用了decodeStream之后,需要在hdpi和mdpi,ldpi中配置相应的图片资源, 
否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。

Bitmap,占用内存的算法如下:

图片的width*height*Config。如果Config设置为ARGB_8888,那么上面的Config就是4。一张480*320的图片占用的内存就是480*320*4 byte。

对于第二个bug:

在欢迎界面加载的图片是720*1280,位深度是24,大小189kb. 在Android中图片占用的内存就是720*1280*4byte. 所以还是比较大的。建议在保证高清线度的前提下,尽量减小的图片资源。

4) 压缩图片

使用BitmapFactory.Options设置inSampleSize就可以缩小图片。属性值inSampleSize表示缩略图大小为原始图片大小的几分之一。如果知道图片的像素过大,就可以对其进行缩小。

那么如何才知道图片过大呢?

方法是:使用BitmapFactory.Options设置inJustDecodeBounds为true后,再使用decodeFile()等方法,并不会真正的分配空间,即解码出来的Bitmap为null,但是可计算出原始图片的宽度和高度,即options.outWidth和 options.outHeight。通过这两个值,就可以知道图片是否过大了。在实际项目中,先获取图片真实的宽度和高度,然后判断是否需要跑缩小。如果不需要缩小,设置inSampleSize的值为 1。如果需要缩小,则动态计算并设置inSampleSize的值,对图片进行缩小。需要注意的是,在下次使用BitmapFactory的 decodeFile()等方法实例化Bitmap对象前,别忘记将opts.inJustDecodeBound设置回false。否则获取的 bitmap对象还是null。

BUG 、使用actionProvider时出现的问题

bug复现:

解决方案:


1

2

//import android.support.v4.view.ActionProvider;

import android.view.ActionProvider;

换一种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方法进行对比。

来自为知笔记(Wiz)

时间: 2024-08-02 06:59:14

Android实际开发中的bug总结与解决方法(一)的相关文章

Android应用开发中三种常见的图片压缩方法

Android应用开发中三种常见的图片压缩方法,分别是:质量压缩法.比例压缩法(根据路径获取图片并压缩)和比例压缩法(根据Bitmap图片压缩). 一.质量压缩法 private Bitmap compressImage(Bitmap image) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里

Android 在eclipse中没有出现AVD的解决方法(转载)

转自:http://frabbit2013.blog.51cto.com/1067958/1243549 本文主要介绍在系统中成功配置好Android开发环境(即SDK is ok and ADT on eclipse is ok)后,eclipse中未出现AVD情况的解决方法.如下图所示,在Windows选项下未出现Android Virtual Device Manager: 此时,需要对eclipse做些许配置方可显示AVD.如下图所示:点击Windows—>Customize Persp

java开发中遇到的问题及解决方法(持续更新)

摘自 http://blog.csdn.net/pony12/article/details/38456261 java开发中遇到的问题及解决方法(持续更新) 工作中,以C/C++开发为主,难免与其他服务和Web进行交换,Java开发必不可少,又不想动用Eclipse大家伙,只能自己动手编写脚本进行Java代码的编译和运行,期间遇到的一些问题,记录下来供自己和大家参考.1)软件包不存在/软件包 javax.jms 不存在    这是由于javac编译时找不到javax.jms所在的软件包,因此将

安卓开发中Theme.AppCompat.Light的解决方法

styles.xml中<style name="AppBaseTheme" parent="Theme.AppCompat.Light">提示如下错误,这是版本问题. error: Error retrieving parent for item: No resource found that matches the given name 'Theme.AppCompat.Light'. 解决方法: 在Eclipse中打开任意一个.java文件,输入  

ANDROID 在eclipse中没有出现AVD的解决方法(转)

如果android安装正确的话,但是eclipse里面的导航条就是没有AVD 可以通过「Window」⇒「Customize Perspective」⇒「Tool Bar Visibility」Tab画面上选择 Android SDK and AVD Manager来显示 http://blog.sina.com.cn/s/blog_79aa896901015iyc.html

微信小程序在开发中遇到的错误与解决方法

1. 这种错误多半是该js文件中没有Page这个方法,就算是空的js也必须要把Page({ })写上去   √ 2. 这种错误多半是该json文件没有内容,所以必须要加上{ },就算是空内容也要加上{ }.√ 补一句:小程序里json文件中不允许有注释不然会报错. 3. 这个问题我不知道怎么解决了.iconClass在当前页面的js中的data里是一个数组,我只想让它使用第一个属性,但是这样不行.未解决 4. 这个错误就很明显了.小程序中的背景图片不能使用本地的路径,要么使用在服务器上的图片路径

移动Web开发中遇见的问题以及解决方法(不断更新中)

20160524 问题:在使用des加密数据后,作为url参数传递发现,系统会把+自动变为空格,以至于解密的时候出现问题. 例如:uKsRUwdU6+83+J9WfgfflA== 传到服务端后 变为uKsRUwdU6 83 J9WfgfflA== 解决:因为des加密后的数据没有空格,在服务端直接把空格采用+替换,解决.(还有其他的方式,由于时间问题,没能来得及测试)

转:移动开发中一些bug及解决方案

网页开发要面对各种各样的浏览器,让人很头疼,而移动开发中,你不但要面对浏览器,还要面对各种版本的手机,iOS好一点,而安卓就五花八门了,你可能在开发中也被它们折磨过,或者正在被它们折磨,我在这里说几个我在开发中遇到的比较难缠问题,和解决方案,给其他朋友提个醒,因为一旦碰到了这些bug,虽然解决方法不难,但是你可能要花上几个小时,几天,甚至更长时间才能解决它们,遇到这些bug的话要注意,以后发现其他bug也会继续补充,也希望你也可以把你遇到的bug或者解决方案回复给我,我会一起补充到文章里,帮助其

Android应用开发中的风格和主题(style,themes)(转)

Android应用开发中的风格和主题(style,themes) 越来越多互联网企业都在Android平台上部署其客户端,为了提升用户体验,这些客户端都做得布局合理而且美观.......Android的Style设计就是提升用户体验的关键之一.Android上的Style分为了两个方面: Theme是针对窗体级别的,改变窗体样式: Style是针对窗体元素级别的,改变指定控件或者Layout的样式.      Android系统的themes.xml和style.xml(位于\base\core