6.10 其他情况的异常
最后,是一些不太好归类的异常信息。这就像武侠小说中的独行大侠,无门无派但也不能小觑。
6.10.1 TimeoutException
异常中的关键字:
com.android.internal.BinderInternal$GcWatcher.finalize()timed out after 10 seconds
发生频率:★
GC回收超时会抛出该异常,注意重写finalize方法时不要有超时的操作。 [1]
6.10.2 JSON解析异常
异常中的关键字:
org.json.JSONException:No value for UserName at
org.json.JSONObject.get(JSONObject.java:354)at……
发生频率:★★★
在JSON解析中经常会遇到这种异常。
这是因为我们在解析JSON的时候,使用了getString("UserName")而不是optString("UserName"),如果UserName这个key在JSON字符串中不存在,前者会抛出上述异常,后者则会返回空。
类似地,还有getJsonArray方法,建议的解决方案是改用optJsonArray方法,才不会发生崩溃。
6.10.3 JSONArray在初始化时为空
异常中的关键字:
java.lang.NullPointerException at
org.json.JSONTokener.nextCleanInternal(JSONTokener.java:116)at
org.json.JSONTokener.nextValue(JSONTokener.java:94)t……
发生频率:★★★
我们知道JSONArray的初始化如下所示:
public void simulateJSONException() throws JSONException { String jsonString = ""; JSONArray array = new JSONArray(jsonString); for (int i = 0; i < array.length(); i++) { JSONObject jsonObject = array.getJSONObject(i); } }
当jsonString这个值为空时,就会报上述的异常信息。
6.10.4 第三方SDK抛出的Crash
在引入第三方SDK的同时,也会引入SDK导致的崩溃。举个例子:GoogleAnalytics,简称GA。Google提供的这个工具,很多公司用来搜集线上Crash。殊不知,有些手机只要启动这个记录Crash的功能,就会Crash,每天会有一两千个崩溃就是因为这个原因导致的,异常信息如下所示:
java.lang.RuntimeException: Package manager has died at android.app.ApplicationPackageManager.getPackageInfo(Application PackageManager.java:82) at com.google.analytics.tracking.android.StandardExceptionParser.setIn cludedPackages(Unknown Source)
我也是在把线上Crash收集到自己的服务器后,才发现这个问题的。后来把GA的这个Crash发送功能禁用掉,就不再因此而崩溃了。
6.10.5 两个不同类型的View有相同的id
异常中的关键字:
java.lang.IllegalArgumentException:Wrong state class,expecting View State but received class android.widget.ScrollView$SavedState instead.This usually happens when two views of different type have the same id in the same hierarchy.This view's id is id/0xff0000.Make sure other views do not use the same id.
发生频率:★★★
异常信息中不一定每次都是ScrollView,也有TextView或者其他控件。
异常信息已经解释得很清楚了,在一个页面中,两个不同类型的View有相同的id,就会导致崩溃。
这个悲剧的发生,是Android系统的内部机制导致的。ViewPager中有两个页面,每个页面的layout布局文件中都有一个id名叫scroll_view的控件,那么当我们重写onSaveInstanceState这个方法的时候,如果要保存scroll_view的状态,比如scrollX和scrollY的值,那么在onRestoreInstanceState方法中恢复这两个值时,就会分不清楚究竟是哪一个。
Android官方建议最好保证每个View的id都是唯一的,或者至少在一个局部的layout文件中这么做,因为很显然,如果同一个layout文件中有两个id都是"android:id="@+id/button"的按钮,那么通过findViewById的时候只能找到前面的按钮,后面的那个就没机会被找到了,所以Android官方的说法是合理的。
此外,还应该加上特别重要的一条:当在Activity中,确定要保存/恢复一个View的状态的时候,一定要保证它们有唯一的id,因为Android内部用id作为保存、恢复状态时使用的key,否则就会发生一个覆盖另一个的悲剧。
6.10.6 LayoutInfiater.from().infiate()使用不当导致的崩溃
异常中的关键字:
No package identifier when getting value for resource number 0x00000001
发生频率:★★★
在程序中使用LayoutInfiater.from().infiate()语句时,必须写在具体的子类中,一定不能工作在父类或虚类里,如下所示:
View view = LayoutInfiater.from(mContext) .infiate(LAYOUT_ID, this, true);
这里有个this指针的问题,当initView方法让虚类调用时,这个this指向谁?是虚类自己还是子类?正是因为Android系统搞不清楚所以就崩溃了,另外这个infiate本身就有一定的特殊性,是不能随便乱用this的。我尝试过把BaseGuideView里的initView方法不写成虚方法,而是一个空的函数,但依旧报错。所以遇到这种情况,加载布局一定要由各个子View自行加载并初始化。 [2]
6.10.7 ViewGroup中的玄机
异常中的关键字:
java.lang.IllegalArgumentException:parameter must be a descendant of this view
发生频率:★★★
这个崩溃,是通过ViewGroup的offsetRectBetweenParentAndChild方法抛出来的。
offsetRectBetweenParentAndChild方法抛出来的。 void offsetRectBetweenParentAndChild(void descendant, Rect rect, boolean offsetFromChildToParent, boolean clipToBounds)
该方法就是用来计算父子重叠的区域。它是通过所给的descendant这个View逐级向上寻找Parent View,同时将Rect转换为同级坐标系来计算的。
在这个方法的末尾,如果最终找到的Parent View和当前View不一致,则会抛出这个异常。说白了,就是descendant参数必须是当前View的子孙。 [3]
那么什么时候descendant不是当前View的子孙呢?在UI调整的时候,会改变当前界面中拥有焦点的控件。我们应该实时确保这个控件是当前View的子孙,所以相应的解决方案也很简单,每次都重新设置一下焦点,让当前View始终获得焦点。与此同时,如果是ListView,还要清空ListView中其他控件抢到的焦点。
6.10.8 Monkey点击过快导致的崩溃
异常中的关键字:
java.lang.NullPointerException at
android.view.ViewRootImpl$ViewRootHandler.handleMessage(ViewRootImpl.java:3046)at……
发生频率:★★
有种Crash,只有执行Monkey脚本时才会抛出来。没办法,人手点的速度远不及Monkey点击的速度。这种Crash,我们就不深究了,只要确保手点的时候不崩溃即可。
有一种相对成熟的解决方案,那就是为每个点击事件加一个延迟函数,如下所示:
public void onClick(View v) { if(isWindowLocked()) return; // 接下来的代码执行点击按钮后的逻辑 }
我们把isWindowLocked这个延迟方法写到BaseActivity中:
public Boolean isWindowLocked() { long current = SystemClock.elapsedRealtime(); if (current - mLastOnClickTime > 500) { mLastOnClickTime = current; return false; } return true; }
这样Monkey就不会跑那么快了。
代码中500的意思是延迟0.5秒。这取决于Monkey中事件的间隔时间,一般我们设置为0.5秒。
6.10.9 图片缩放很多倍
异常中的关键字:
java.lang.IllegalArgumentException:bitmap size exceeds 32bits
发生频率:★★★
当图片缩放了很多倍时,导致内存溢出,就会抛出这个异常,多发生在全屏显示一张图片的时候。
如下所示,postScale方法中的参数就是宽和高比例,要在这里增加try…catch…捕获这个异常。
// srcWindth和 srcHeight是缩放前 // tagetWidth和 targetHeight缩放后 Float scaleW = (fioat)targetWidth / (fioat)srcWidty; Float scaleH = (fioat)targetHeight / (fioat)srcHeight; Matrix matrix = new Matrix(); Matrix.postScale(scalew,scaleH);
6.10.10 图片宽高为0
异常中的关键字:
java.lang.IllegalArgumentException:width and height must be>0 at
android.graphics.Bitmap.nativeCreate(Native Method)
发生频率:★★
产生这个异常,通常是因为没有取到图片的宽和高,于是就返回默认值0了。
这是件很诡异的事情,因为任何一张图片都是有宽和高的,那么唯一的一种解释就是,没加载到这个图片(比如说缓冲数据被清空),或者提前调用了获取图片的宽和高的方法,这时候就得到0值了。
暂时还没有完美的解决方案,只能看到哪个页面有这样的异常信息,就加try…catch…语句防止获取图片宽高时出错。
6.10.11 不能重复添加组件
异常中的关键字:
View xxxx has already been added to the window manager
发生频率:★★★
这个异常发生在windowmanger.addView(view)这行代码中,意思大体是说这个view在Window Manager中已经存在,不能再添加相同的了。
通常的解决办法是在添加view时,捕获这个异常,但是并没有解决问题,想要添加的view并没有被加入到Window Manager中。
于是我们想到,先执行windowmanger.removeView(view),再执行addView方法,这样就不会出问题了。但是问题接踵而至,当Window Manager中并不存在这个view时,执行remove方法反而会抛出View not attached to window manager的异常信息。基于此,得到终极解决方案,如下所示:
try { windowmanager.removeView(view); } catch(IllegalStateException ex) { e.printStackTrace(); } try { windowmanager.addView(view); } catch(IllegalStateException ex) { e.printStackTrace(); }
也就是说,即使removeView失败,也能继续执行接下来的addView操作。
[1] 关于这个异常的不完全诊断,请参见http://stackoverf iow.com/questions/24021609/how-to-handle-java-util-concurrent-timeoutexception-android-os-binderproxy-f in。
[2] 关于这个崩溃的详细信息,请参见http://blog.csdn.net/yanzi1225627/article/details/37338565。
[3] 关于这个崩溃的详细信息,请参见http://blog.sina.com.cn/s/blog_5704bfaf0102v3bn.html。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论