6.7 系统碎片化相关的异常
这类Crash由两部分组成,一方面是和Android系统的版本不同有关,比如说在Android 4.2的手机执行了Android 5.0的语法,崩溃是必然的;另一方面和ROM的不同有关,即使是相同的Android 4.2版本,由于各个硬件厂商随意定制自己的ROM,改写其中的系统方法,那么就会表现为App的某个页面,不同手机看到不同的效果,甚至是崩溃。
6.7.1 NoSuchMethodError
异常中的关键字:
java.lang.NoSuchMethodError
发生频率:★★★★★
举个例子,错误信息如下:
java.lang.NoSuchMethodError:android.os.Bundle.getString
android.os.Bunde中怎么可能没有getString这个方法呢?
其实吧,getString方法有两种参数类型:
getString(key); getString(key, defaultValue);
而前面一种是旧的版本,后面这种加了defaultValue参数的,是在2.3之后的Android版本里才加入的,所以,如果你的android project设置的target version是2.3.3,而你又用了后面这种新的getString()方法的话,那么在2.3系统上就会报这个异常。
NoSuchMethodError异常,只能防范,不能根治,因为Android碎片化问题很严重:
一方面Android系统的升级,会提供一些新方法,程序员在App中使用了这些新方法,而这在老版本的Android系统中不存在,就会崩溃。相应的解决方案是,在DailyBuild机器上准备不同版本的SDK,每天晚上自动打包时,把App在所有这些SDK上都编译一遍,如果缺少方法,首先在编译期间就会报错,自动打包会第一时间发现这些错误。
另一方面,随着Android系统的升级,也会有一些旧方法会被废弃,有些厂商的ROM有可能删除这些被废弃的方法,于是当程序员在App中使用了这些废弃的方法,该App在这些厂商的ROM中运行就会崩溃。
相应的解决方案是,在开发阶段检查Android Lint,里面有被废弃的方法的警告,谨慎使用就是了。
如果在项目中一定要使用上述这些新方法或者废弃的旧方法,那么在使用时,要进行Android系统版本的判断:
int sysVersion = Integer.parseInt(android.os.Build.VERSION.SDK); if(sysVersion > 9){ // do something } else { // do other this }
Android碎片化问题很严重,getString只是其中的一种情况,类似的问题还有很多,但基本逃不出我上述的分析。
6.7.2 RemoteViews
异常中的关键字:
android.widget.RemoteViews$RefiectionAction.writeToParcel(RemoteViews.java:763)
发生频率:★★★
一直以为在项目中使用RemoteViews是件很逼格的事情。这玩意儿一般用在两个地方,一个是在AppWidget,另外一个是在Notification。对于应用类App而言,有机会用到的是后者。
比如说,App应用都有下载更新的功能,一般都是用AsyncTask来做这个事情。下载过程中显示进度条,就是使用Notification,它有一个contentView属性,就是RemoteViews类型的,我们要为其设置2个很关键的值:
·给ImageView绑定图片资源id。
·给TextView绑定字符串资源Id。
如下面的例子所示:
notification.contentView = new RemoteViews( context.getPackageName(), R.layout.notification); notification.contentView.setImageViewResource( R.id.imageview, R.drawable.icon); notification.contentView.setProgressBar( R.id.progressbar, 100, 0, false); notification.contentView.setTextViewText( R.id.textview, "正在更新 " + "\n" + "0%");
异常就是在绑定时出现的,而且有特定的情况:
1)当你的Bitmap为null时;
2)当你的String为""或者null时;
3)Android版本是4.0.3和4.0.4时;
如果Android版本是4.1以上的,则不会出现上述的异常,读不到图片就是控件不显示图片而已,并不会导致程序崩溃。
6.7.3 pointerIndex out of range
异常中的关键字:
java.lang.IllegalArgumentException:pointerIndex out of rangeat android.view.MotionEvent.nativeGetAxisValue(Native Method)
发生频率:★★★
关于这个异常有好几种说法,我们逐个进行分析。
首先我们定位问题,在做多点触控放大缩小,操作自己所绘制的图形时发生这个异常,如果是操作图片的放大缩小、多点触控不会出现这个错误。这个bug是Android系统原因导致的,所以简单有效的办法是在绘图时捕获这个异常,如下所示:
public fioat spacing(MotionEvent event) { try { x = event.getX(0) - event.getX(1); y = event.getY(0) - event.getY(1); } catch(IllegalArgumentException e) { e.printStackTrace(); } // 以下省略若干语句 }
另一种解决方案是:
1)让你的view(可能是ScrollView、WebView、MapView等),创建一个子view继承自它们中的某一个。
2)重写这个view的onInterceptTouchEvent和onTouchEvent方法。
3)为上述这两个方法增加try…catch…语句,捕获已知的异常,如下所示:
try { super.onInterceptTouchEvent(MotionEvent ev); } catch (IllegalArgumentException ex) { } return false; try { super.onTouchEvent(MotionEvent ev); } catch (IllegalArgumentException ex) { } return false;
这种解决方案,至少在Android4.1上是好用的。
按照这个思路,还是有点问题,如果是用ViewPager的话,onInterceptTouchEvent返回false会导致ViewPager翻页出现bug,CSDN上有人给出了相应的解决方案,可以参考。
6.7.4 SecurityException之一:Intent中图片太大
异常中的关键字:
Unable to find app for caller android.app.ApplicationThreadProxy@41868f10(pid=24370)when stopping service Intent{cmp=xxxx}
发生频率:★★
在跳转activity的过程中携带的extras中有Bitmap,应尽量减小要传输的图片的体积,或者通过保存图片到SD卡中或者通过URI方式传递图片参数;否则,图片太大,就会报上述的异常信息。
果然,在去掉了resultIntent.putExtra("bitmap",bitmap);这条语句后,就不报错了。
一般而言,超过1MB的数据,就不要通过Intent来传递了。
6.7.5 SecurityException之二:动态加载其他apk的activity
异常中的关键字:
java.lang.SecurityException:Given caller package com.jianqiang.abc is not running in process ProcessRecord{41e74e5028637:com.zhao3546.launcher/u0a10142}
发生频率:★★
如果在apk中使用了动态注册BroadcastReceiver,那么Launcher动态加载该apk时,就有可能出现java.lang.SecurityException异常。
相应的解决方案是,修改之前注册BroadcastReceiver的地方,通过ContextHolder()来注册BroadcastReceiver,把apk重新部署验证即可。 [1]
6.7.6 SecurityException之三:No permission to modify thread
异常中的关键字:
java.lang.SecurityException:No permission to modify given thread at
android.os.Process.setThreadPriority(Native Method)at
android.webkit.WebViewCore$WebCoreThread$1.handleMessage(WebViewCore.java:764)
发生频率:★★★
在很多设备上,Android 4.0.4系统都会有这个问题发生。 [2]
App经常会申请一些权限,而有些手机的ROM出于安全考虑,则会禁止这些权限,那么当App使用到这些权限时,就会发生崩溃。
相应的解决方案是,在执行某些安全相关的操作时,要么加上if语句跳过这个操作,要么使用try…catch…捕获这类异常,宁肯点击后没有反应,也不能崩溃了。
比如拨打电话,我们一般会直接这么写:
Intent intent = new Intent( Intent.ACTION_CALL,Uri.parse("tel:13800000000")); startActivity(intent);
但是有些手机系统会禁止App拨打电话,即使AndroidManifest.xml配置了拨打电话的权限也不行。这时我们就要改写上述代码,预判是否有打电话的权限,以确保不发生崩溃,如下所示:
PackageManager pm = getPackageManager(); boolean hasPermission = pm.checkPermission(Manifest.permission.CALL_PHONE, getPackageName()) == PackageManager.PERMISSION_GRANTED; if (hasPermission) { Intent intent = new Intent( Intent.ACTION_CALL,Uri.parse("tel:13800000000")); startActivity(intent); }
6.7.7 view的getDrawingCache()返回null
异常中的关键字:
java.lang.NullPointerException at
android.view.View.buildDrawingCache(View.java:6578)at
android.view.View.getDrawingCache(View.java:6428)at……
发生频率:★★★
当背景图太大,超过了屏幕的大小,就会导致getDrawingCache()返回的结果是null,从而抛出NullPointException的异常。
查看Android源码,会发现buildDrawingCache方法中有这样几行代码:
if (width <= 0 || height <= 0 || (width * height * (opaque && !translucentWindow ? 2 : 4) > ViewConf?iguration.get(mContext) .getScaledMaximumDrawingCacheSize())) { destroyDrawingCache(); return; }
在上面的代码中,width和height是所要cache的view绘制的宽度和高度,所以width*height*(opaque&&!translucentWindow?2:4)计算的是当前所需要的cache大小。
Android系统在计算当前所需要的DrawingCache大小时,发现这个值超过了系统所提供的最大DrawingCache值,这时会直接返回null。
总之,万恶之源在于图片太大,那我们就控制一下图片的大小,裁减或者等比例缩放,总之不要超过系统所提供的最大DrawingCache值,这个值是这么计算的:当前屏幕的分辨率的高和宽相乘,再乘以4。 [3]
6.7.8 DeadObjectException
异常中的关键字:
DeadObjectException
发生频率:★★★★
很多开发者在想如何通过编写代码的方式重启Android设备。大多数设备都没有Root权限,想让设备重启比较简单的方法就是想办法制造一些系统级的错误,强迫Android系统自动重启,类似于Windows上的Ring0级应用崩溃出现蓝屏。对于Android来说产生一个android.os.DeadObjectException异常是一个不错的方法。
对于App应用而言,我从未写过这样的语句来重启系统,网上各路达人对此异常的讨论、发生场景和解决方案也不尽相同,但基本上都是停留在App的某个页面,放置一段时间后就崩溃,有的机器能坚持的时间长一些,半个多小时,有些机器也就十几秒的样子。
由此可推测出来,发生DeadObjectException,其实就是某个对象已经被系统回收了,可我们却还在使用它。 [4]
6.7.9 Android 2.1不支持SSL
异常中的关键字:
java.lang.NullPointerException at
android.webkit,SslErrorHandler.handleMessage(SslErrorHandler.java:62)
发生频率:★★
Android 2.1版本不支持SSL,所以发起https的请求会导致崩溃。解决方案是,调用https的网络请求时,要事先判断Android系统的版本,版本过低要提示用户不能进行操作。
6.7.10 ViewFlipper引发的血案
异常中的关键字:
java.lang.IllegalArgumentException:Receiver not registered:
android.widget.ViewFlipper$1@4083a4d0 at
android.app.LoadedApk.forgetReceiverDispatcher(LoadedApk.java:634)
发生频率:★★★
在Activity中使用ViewFlipper控件,进行横竖屏切换操作时就会发生这种异常。这是由于onDetachedFromWindow()在onAttachedToWindow()之前被调用所致。
这个Crash很有名,业界公认的解决方案是,重写ViewFlipper的onDetachedFromWindow()方法:
@Override protected void onDetachedFromWindow() { try { super.onDetachedFromWindow(); } catch(IllegalArgumentException e) { stopFlipping(); } }
6.7.11 ActivityNotFoundException
异常中的关键字:
android.content.ActivityNotFoundException:Unable to find explicit activity class{com.android.settings/com.android.settings.WirelessSettings};have you declared this activity in your AndroidManifest.xml?
发生频率:★★★
看了一下发生错误的操作系统分布,发现都是在4.0以上才会出现这类错误信息。究其原因,是4.0以上把原来的打开网络设置方式舍弃了,如下修改代码可以解决这个问题:
// 3.2以上打开设置页面 // 也可以直接用 ACTION_WIRELESS_SETTINGS打开到 WiFi页面 if (Build.VERSION.SDK_INT > 13) { startActivity(new Intent( android.provider.Settings.ACTION_SETTINGS)); } else { startActivity(new Intent( android.provider.Settings.ACTION_WIRELESS_SETTINGS)); }
6.7.12 Android 2.2不支持xlargeScreens
异常中的关键字:
No resource identifier found for attribute'xlargeScreens'in package'android'
发生频率:★★
错误出现在AndroidManifest.xml文件的supports-screens标记中,原因是xlargeScreens属性在API9(Android 2.3)中才支持。
解决办法:将Android 2.2移除,添加Android 2.3即可解决。
6.7.13 Package manager has died
异常中的关键字:
Package manager has died at
android.app.ApplicationPackageManager.getApplicationInfo(ApplicationPackageManager.java:213)
发生频率:★★★★
我们一般这样使用PackageManager,如下所示:
try { String channelId = getPackageManager() .getApplicationInfo( getPackageName(), PackageManager.GET_META_DATA) .metaData.getString("UMENG_CHANNEL"); PackageInfo info = this.getPackageManager() .getPackageInfo(getPackageName(), 0); } catch (PackageManager.NameNotFoundException e) { }
PackageManager如果已经died,说明该进程不存在了,由于某些错误原因Package-Manager进程已经退出,此时任何向它进行的请求都将失效,让设备重启可能是一个办法。还有一种情况是,App本身已经处于崩溃状态,这个时候如果App已经弹出错误框,再调用PackageManager也会出错或卡死。
解决方案就是每次获取PackageManager的时候用try…catch…捕获异常。
6.7.14 SpannableString与富文本字符串
异常中的关键字:
java.lang.IndexOutOfBoundsException:setSpan(-1…-1)starts before 0 at
android.text.SpannableStringBuilder.checkRange(SpannableStringBuilder.java:951)at……
发生频率:★★
有一种异常表面看起来是数组越界,但其实并非如此。
从上面的异常信息中能看出,是SpannableString的setSpan方法越界导致出现崩溃。但是检查相应的代码,并没有刻意使用这个方法。
TextView要显示的富文本恰好要被换行符截断的时候,因为富文本是使用Spannable-String技术来显示的,所以会报这种异常。所幸,这个Crash不是必现的,取决于机型、分辨率、字体大小、文字和样式很多因素。
相应的解决方案是,在执行TextView的setText方法时,加上try…catch…语句捕获IndexOufOfBoundsException。因为这种情况发生的概率极小,所以即使抛出异常,最多是不显示文本,也不会让App崩溃。 [5]
还有一种情况是,在长按一段文本时,有些Android系统对于EditText的get-SelectionStart方法,会返回-1,这就会导致上述异常情况的抛出,如下所示:
public void afterTextChanged(Editable s){ if(StringUtils.isNullOrEmpty(s.toString())) return; int editStart = mTxInput.getSelectionStart(); int editEnd = mTxInput.getSelectionEnd(); mTxInput.removeTextChangedListener(this); while((s.toString().length()) > MAX_INPUT){ s.delete(editStart-1,editEnd); editStart--; editEnd--; } mTxInput.setSelection(editStart); }
所以在使用getSelectionStart方法获得值的时候,要判断这个值是否为-1。 [6]
6.7.15 Can not perform this action after onSaveInstanceState
异常中的关键字:
java.lang.IllegalStateException:
Can not perform this action after onSaveInstanceState at
android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1314)……
android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:595)
发生频率:★★★
commit方法在Activity的onSaveInstanceState()之后调用就会出错,因为onSaveInstance-State方法是在Activity即将被销毁前调用,以保存Activity数据的,如果在保存完状态后再给它添加Fragment就会出错。
解决办法就是把commit()方法替换成commitAllowingStateLoss(),其效果是一样的,如下代码所示:
FragmentTransaction ft = fragmentActivity.getSuppotFragmentManager().beginTransaction(); ft.add(fragmentContentId, fragments.get(0), fragments.get(0).getClass().toString());
此外,有时候按后退键触发onBackPressed方法也会引发类似的异常,网上有一篇文章详细分析了这类问题的发生原因和解决方案,这里不再赘述。 [7]
6.7.16 Service Intent must be explicit
异常中的关键字:
Service Intent must be explicit
发生频率:★★★
Android在升级到5.0系统后会产生这样的崩溃。直接通过action启动Service,就会导致这个问题,所以我们必须指定component或package才能避免这类问题,如下所示:
Intent intent = new Intent(); intent.setAction("your action name"); intent.setPackage(getPackageName()); context.startService(intent);
很多第三方SDK都存在这个问题,我们需要更新SDK到最新版本,才能保证Android 5.0系统下的App不会因此而崩溃。
[1] 关于这个Crash的更详细信息,请参见http://blog.csdn.net/zhao_3546/article/details/11195881。
[2] 关于这个Crash的更详细信息,请参见http://stackoverf iow.com/questions/11025182/webview-java-lang-securityexception-no-permission-to-modify-given-thread。
[3] 关于这个Crash的更详细分析,请参见http://zartzwj.iteye.com/blog/1098839。社区上关于这个Crash的解决方案众说纷坛,目前还没有统一的解决方案。
[4] 在StackOverf iow上对DeadObjectException有更详细的讨论,请参见http://stackoverf iow.com/questions/7037093/android-dead-object-exception。
[5] 关于这种情况的详细描述,请参见http://hold-on.iteye.com/blog/1943437。
[6] 关于这种情况的详细描述,请参见http://stackoverf iow.com/questions/22810147/error-when-selecting-text-from-textview-java-lang-indexoutofboundsexception-se。
[7] 详细内容请参见:http://zhiweiof ii.iteye.com/blog/1539467。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论