返回介绍

6.7 系统碎片化相关的异常

发布于 2024-08-17 23:46:12 字数 13511 浏览 0 评论 0 收藏 0

这类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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文