前言
本文整理的是平时遇到应用停止运行的一些问题和解决思路,分享出来的目的是希望可以和大家一起学习与思考。应用接入了腾讯的bugly,捞取日志还是蛮方便的,下面遇到的很多问题也是bugly上报的。
1. 崩溃问题实例
1.1 android.view.WindowManager$BadTokenException
Unable to add window -- token android.os.BinderProxy@3d86cef3 is not valid; is your activity running?
android.view.ViewRootImpl.setView(ViewRootImpl.java:688)--操盘达人
[原因]
该异常表示不能添加窗口,通常是所要依附的view已经不存在导致的。
[解决方案]
Dialog&AlertDialog,WindowManager不能正确使用时,经常会报出该异常,原因比较多,几个常见的场景如下:
- 上一个页面没有 destroy 的时候,之前的 Activity 已经接收到了广播。如果此时之前的 Activity 进行 UI 层面的操作处理,就会造成 crash。UI 层面的刷新,一定要注意时机,建议使用 set_result 来代替广播的形式进行刷新操作,避免使用广播的方式,代码不直观且容易出错。
- Dialog 在 Actitivty 退出后弹出。在 Dialog 调用 show 方法进行显示时,必须要有一个 Activity 作为窗口的载体,如果 Activity 被销毁,那么导致 Dialog 的窗口载体找不到。建议在 Dialog 调用 show 方法之前先判断 Activity 是否已经被销毁。
- Service&Application 弹出对话框或 WindowManager 添加 view 时,没有设置 window type 为TYPE_SYSTEM_ALERT。需要在调用
dialog.show()方法前添加dialog.getWindow().SetType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)。 - 6.0的系统上, (非定制 rom 行为)若没有给予悬浮窗权限, 会弹出该问题, 可以通过
Settings.canDrawOverlays来判断是否有该权限. - 某些不稳定的 MIUI 系统 bug 引起的权限问题,系统把 Toast 也当成了系统级弹窗,android6.0 的系统Dialog 弹窗需要用户手动授权,如果 app 没有加入 SYSTEM_ALERT_WINDOW 权限就会报这个错。需要加入给 app 加系统 Dialog 弹窗权限,并动态申请权限,不满足第一条会出现没权限闪退,不满足第二条会出现没有 Toast 的情况。
1.2 java.util.concurrent.TimeoutException
android.database.BulkCursorToCursorAdaptor.finalize() timed out after 120 seconds android.os.BinderProxy.transactNative(Native Method)--玉面小色熊
[原因]
该异常表示调用超时。
[解决方案]
一般是系统在gc时,调用对象的finalize超时导致,解决办法:
- 检查分析 finalize 的实现为什么耗时较高,修复它;
- 检查日志查看 GC 是否过于频繁,导致超时,减少内容开销,防止内存泄露。
1.3 java.lang.NullPointerException
[原因]
该异常表示尝试去调用 virtual method,使用了一个空对象引用,建议您检查引用的对象是否为空。
[解决方案]:
这种异常通常是调用一个对象的方法抛出的,凡是调用一个对象的方法之前,一定要进行判空或者进行try-catch,这样基本可以规避大部分空指针异常。
1.4 java.lang.NumberFormatException
[原因]
该异常表示字符串尝试转换为其他类型出错,转换类型异常。
[解决方案]
当字符串尝试转换为数字类型失败时,抛出该异常。举例如下:
String test = "test123";
int result = Integer.parseInt(test);
此时由于字符串中含有非数值字段,将会抛出该异常。
1.5 android.database.sqlite.SQLiteDiskIOException
[解决方案]
该异常表示磁盘输入/输出错误,可能是数据操作太频繁导致的。应用程序启动时,调用 RSSDatabase 的构造函数,UI Thread 锁定了你的目标的数据库,随后你又尝试插入数据到被UI线程锁定的数据库。 将抛出一个异常,因为db被lock了。
1.6 java.lang.ClassCastException
[原因]
该异常表示类型转换异常,通常是因为一个类对象转换为其他不兼容类对象抛出的异常,检查你要转换的类对象类型。
[解决方案]
一般在强制类型转换时出现,例如如果A向B转换,而A不是B的父类时,将产生java.lang.ClassCastException异常。一般建议做这时要使用 instanceof 做一下类型判断,再做转换。
[其他场景]
例如使用 Recyclerview 的 itemView 中的控件,设置了 LayoutParams。造成转换异常。
1.7 java.lang.IllegalStateException: Fragment not attached to Activity
[原因]
出现该异常,是因为 Fragment 的还没有 Attach 到 Activity 时,调用了如 getResource() 等,需要上下文 Content 的函数。
[解决方案]
在调用需要 Context 的函数之前,增加一个判断 isAdded()
1.8 java.lang.ArrayIndexOutOfBoundsException
[原因]
概述:该异常表示数组越界。
[解决方案]
这种情况一般要在数组循环前做好length判断,index超出length上限和下限时都会报错。举例如下:一个数组int test[N],一共有N个元素分别是test[0]~test[N-1],如果调用test[N],将会报错。建议读取时,不要超过数组的长度(array.length)。
Android中一种常见情形就是上拉刷新中header也会作为listview的第0个位置,如果判断失误很容易造成越界。
[异常情况1]
TextView 中 ellipsize 使用引发 Crash,该问题为 Android 系统 bug,存在于 Android 5.0 及以下设备,问题描述参考:code.google.com/p/android/i…
[解决方案]:
使用 android:singleLine="true" 代替 android:lines="1" 和 android:maxLines="1"
1.9 java.lang.ClassNotFoundException
[原因]
该异常表示在路径下,找不到指定类,通常是因为构建路径问题导致的。
[解决方案]
类名是以字符串形式标识的,可信度比较低,在调用Class.forName(""), Class.findSystemClass(""), Class.loadClass("") 等方法时,找不到类名时将会报错。如果找不到的 Class 是系统 Class ,那么可能是系统版本兼容,厂家 Rom 兼容的问题,找到对应的设备尝试重现,解决方法可以考虑更换 Api,或用自己实现的 Class替代。
如果找不到的 Class 是应用自由 Class(含第三方SDK的 Class),可以通过反编译工具查看对应apk中是否真的缺少该 Class,再进行定位,这种往往发生在:
- 要找的Class被混淆了,存在但名字变了;
- 要找的Class未被打入Dex,确实不存在,可能是因为自己的疏忽,或编译环境的冲突;
- 要找的Class确实存在,但你的Classlorder找不到这个Class,往往因为这个Classloder是你自实现的(插件化应用中常见)。
1.10 java.lang.SecurityException
Requires READ_PHONE_STATE: Neither user 1000 nor current process has android.permission.READ_PHONE_STATE.
[原因]
该异常表示需要读取电话状态,但没有权限。
[解决方案]
此类问题一般是未申明权限导致,建议检查是否有读取电话状态的权限,解决方法:
- android6.0 以下需要在manifest中声明相应的权限;
- android6.0 及以上,在使用时需要动态申请权限;
1.11 android.content.ReceiverCallNotAllowedException
BroadcastReceiver components are not allowed to register to receive intents com.bocharov.xposed.fscb.util.EventsReceiver$class.startReceive(SourceFile:46)
[解决方案]
- bindService 不能在 BroadcastReceiver 中调用,你可以在里面调用 StartService 并把要传递参数放到intent中
- registerReceiver 不能在 BroadcastReceiver 调用,可以通过
context.getApplicationContext().registerReceiver()
1.12 java.lang.IllegalArgumentException
[解决方案]
参数不匹配异常,通常由于传递了不正确的参数导致。
常见于:
- Activity、Service状态异常;
- 非法URL;
- UI线程操作。
- Fragment中嵌套了子Fragment,Fragment被销毁,而内部Fragment未被销毁,所以导致再次加载时重复,在onDestroyView() 中将内部Fragment销毁即可
1.13 android.os.TransactionTooLargeException
[原因]
Binder 传输的数据太大导致的异常。
如果 Binder 的参数或返回值太大,不适合的事务缓冲区,然后调用将失败,并将被抛出TransactionTooLargeException。
[解决方法]
不要将大量数据传入Binder
1.14 java.lang.IncompatibleClassChangeError
[解决方案]
不兼容的类变化错误。
当正在执行的方法所依赖的类定义发生了不兼容的改变时,抛出该异常。一般在修改了应用中的某些类的声明定义而没有对整个应用重新编译而直接运行的情况下,容易引发该错误
1.15 java.lang.OutOfMemoryError
[原因]
该异常表示未能成功分配字节内存,通常是因为内存不足导致的内存溢出。
[解决方案]
OOM 就是内存溢出,即 Out of Memory。也就是说内存占有量超过了 VM 所分配的最大。怎么解决 OOM,通常 OOM 都发生在需要用到大量内存的情况下(创建或解析 Bitmap,分配特大的数组等),这里列举常见避免 OOM 的几个注意点:
- 适当调整图像大小。
- 采用合适的缓存策略。
- 采用低内存占用量的编码方式,比如 Bitmap.Config.ARGB_4444 比 Bitmap.Config.ARGB_8888 更省内存。
- 及时回收Bitmap。
- 不要在循环中创建过多的本地变量。
- 自定义对内存分配大小。
- 特殊情况可在 mainfests 的 Application 中增加 android:largeHeap="true" 属性,比如临时创建多个小图片(地图 marker)
1.16 android.os.DeadSystemException
Caused by: android.os.DeadObjectException at android.os.BinderProxy.transactNative(Native Method) at android.os.BinderProxy.transact(BinderProxy.java:685)
【原因】 BInder对象虽然已经检查,在执行期间发生死亡
解决方案
if (asBinder()?.isBinderAlive == true) {
try {
onBroadcastReceived(action)
} catch (e: RemoteException) {
// Binder object is dead, do something here
}
}
或者postDelay执行上述任务,或者WeakReference持有对象
1.17 java.lang.RuntimeException
Can't create handler inside thread that has not called Looper.prepare()
【原因】
该异常表示不能在非 UI 线程里面创建 handler 对象,通常是因为在工作线程中处理 UI 相关的操作或者在非 UI 线程中 new 新的 Handler 导致,不能在子线程里 Toast 等操作 UI 线程
[解决方案]
android 中的 UI 操作都必须在主线程中处理的,在涉及UI操作时通常可以:
- 使用mHandler = new Handler(Looper.getMainLooper()),然后在handler中处理操作;
- 使用 Activity.runOnUiThread()方法。
1.18 java.lang.IllegalArgumentException
java.lang.IllegalArgumentException:Receiver not registered
[原因]
Receiver没有注册
1.19 java.lang.IllegalStateException: Fragment not attached to Activity
[原因]
isAdded() 方法可以判断当前的 Fragment 是否已经添加到 Activity 中,只有当 Fragment 已经添加到 Activity 中时才执行 getResources() 等方法。
1.20 Caused by: java.lang.IllegalStateException: BT Adapter is not turned ON
1.21 android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)
[原因]
database is locked 产生的原因:sqlite同一时间只能进行一个写操作,当同时有两个写操作的时候, 后执行的只能先等待, 如果等待时间超过5秒, 就会产生这种错误. 同样一个文件正在写入, 重复打开数据库操作更容易导致这种问题的发生。
1.22 java.util.ConcurrentModificationException
[原因]
该异常表示迭代器迭代过程中,迭代的对象发生了改变,如数据项增加或删除。
[解决方案]
由于迭代对象不是线程安全,在迭代的过程中,会检查 modCount 是否和初始 modCount 即 expectedModCount 一致,如果不一致,则认为数据有变化,迭代终止并抛出异常。
常出现的场景是,两个线程同时对集合进行操作,线程1对集合进行遍历,而线程2对集合进行增加、删除操作,此时将会发生 ConcurrentModificationException 异常。 具体方法:多线程访问时要增加同步锁,或者建议使用线程安全的集合:
- 使用ConcurrentHashMap替换HashMap,CopyOnWriteArrayList替换ArrayList;
- 或者使用Vector替换ArrayList,Vector是线程安全的。Vector的缺点:大量数据操作时,由于线程安全,性能比ArrayList低.
1.23 Caused by: kotlin.TypeCastException: null cannot be cast to non-null type
[原因]
该异常表示类型转换异常,空的实例不能转化为非空类型,比如
mAccountName = view.findViewById<View>(R.id.account_name) as TextView
前面获取的实例可能为空
[解决方案]
在转化的类型TextViwe后面加上?即可
1.24 Caused by: java.lang.RuntimeException: stub
[原因]
该异常表示引入的库异常,比如
Caused by: java.lang.RuntimeException: stub Telephony$Sms.<clinit>(Unknown Source:36)
这里使用的是自定义的jar包中的库文件
[解决方案]
改为引用 android.provider.Telephony
1.25 java.lang.IllegalStateException: Cannot call this method while RecyclerView is computing a layout or scrolling
[原因]
源码有如下这样一段描述,从描述中可知:
如果下面方法返回true,表示RecyclerView处于锁定状态,RecyclerView正在尝试计算布局,此时任何对adapter内容的更改都会导致异常。
因为它是发生布局遍历或RecyclerView开始滚动时由框架调用响应系统事件,代码不太可能在这种状态下运行。
如果你有一些自定义逻辑来改变adapter内容去响应view回调,上述情况可能会发生。
[建议] 在这些情况下,您应该使用Handler或类似的机制延迟改变
/**
* Returns whether RecyclerView is currently computing a layout.
* <p>
* If this method returns true, it means that RecyclerView is in a lockdown state and any
* attempt to update adapter contents will result in an exception because adapter contents
* cannot be changed while RecyclerView is trying to compute the layout.
* <p>
* It is very unlikely that your code will be running during this state as it is
* called by the framework when a layout traversal happens or RecyclerView starts to scroll
* in response to system events (touch, accessibility etc).
* <p>
* This case may happen if you have some custom logic to change adapter contents in
* response to a View callback (e.g. focus change callback) which might be triggered during a
* layout calculation. In these cases, you should just postpone the change using a Handler or a
* similar mechanism.
*
* @return <code>true</code> if RecyclerView is currently computing a layout, <code>false</code>
* otherwise
*/
public boolean isComputingLayout() {
return mLayoutOrScrollCounter > 0;
}
该变量在dispatchLayout或滚动期间递增,在这期间不能调用适配器数据更改的方法,这样做会产生难以发现的错误,因此我们检查并抛出异常
/**
* This variable is incremented during a dispatchLayout and/or scroll.
* Some methods should not be called during these periods (e.g. adapter data change).
* Doing so will create hard to find bugs so we better check it and throw an exception.
*
* @see #assertInLayoutOrScroll(String)
* @see #assertNotInLayoutOrScroll(String)
*/
private int mLayoutOrScrollCounter = 0;
void onEnterLayoutOrScroll() {
mLayoutOrScrollCounter++;
}
类似于mLayoutOrScrollCounter 但记录警告而不是抛出异常, 开发人员在滚动回调中更新数据是一种不好的做法,因为它可能在布局期间调用
/**
* Similar to mLayoutOrScrollCounter but logs a warning instead of throwing an exception
* (for API compatibility).
* <p>
* It is a bad practice for a developer to update the data in a scroll callback since it is
* potentially called during a layout.
*/
private int mDispatchScrollCounter = 0;
[解决方案]
1.27 读写buffer过小
val fos = FileOutputStream(filePic)
【解决方法】
val fos = BufferedOutputStream(FileOutputStream(filePic))
这里使用了 BufferedOutputStream 来代替 FileOutputStream,这样可以提高写入的效率。同时,由于 BufferedOutputStream 内部使用了缓冲区,因此可以避免一次性将整个图片加载到内存中。
FileOutputStream 是直接将数据写入到文件中,没有使用缓冲区,因此写入速度相对较慢。而 BufferedOutputStream 内部使用了缓冲区,可以将数据先写入到缓冲区中,然后再批量写入到文件中,这样可以提高写入的效率。
此外,BufferedOutputStream 还提供了一些其他的功能,比如可以设置缓冲区的大小、可以将数据写入到多个输出流中等。因此,在需要频繁进行文件写入操作时,使用 BufferedOutputStream 可以提高程序的性能。
1.28 java.lang.IllegalStateException: Fragment not attached to a context
【解决方法】 通常发生在Fragment实例访问getContext()或getResources()时,但Fragment未附加到活动的上下文中。 要解决这个问题,您可以在访问getContext()或getResources()之前检查Fragment是否已附加到上下文中。以下是示例代码:
// 在访问getContext()或getResources()之前检查Fragment是否已附加到上下文中
if (isAdded) {
val context = requireContext()
val resources = requireResources()
// 这里进行其他相关的操作
} else {
// Fragment未附加到上下文中,可能需要进行适当的处理
}
2 ANR 问题实例
2.1 Subject: Input dispatching timed out (Waiting because no window has focus but there is a focused application that may eventually add a window when it finishes starting up.)
[解决方案]
1.查看 /data/anr/traces.txt 文件,搜索包名关键字,找到打印出的堆栈信息
2.2 Binder 超时出现的ANR
android.os.BinderProxy.transactNative(Native method) android.os.BinderProxy.transact(Binder.java:764)
2.3 bindApplication超时
06-26 10:45:26.778 618 618 E ANR_LOG : >>> msg's executing time is too long
06-26 10:45:26.779 618 618 E ANR_LOG : Blocked msg = { when=-2s822ms what=100 target=android.app.ActivityThread$H obj=ActivityRecord{79acb8f token=android.os.BinderProxy@eae8a6d {com.jiayuan/com.igexin.sdk.GActivity}} } , cost = 2819 ms
06-26 10:45:26.779 618 618 E ANR_LOG : >>>Current msg List is:
OOM是常见的java错误,OOM主要有:
- OOM fo heapjava.lang:OutOfMemoryError: Java heap space,此OOM是由于JVM中heap的最大值不满足需要,将设置heap的最大值调高即可。
- OOM for Perm:java.lang:OutOfMemoryError: Java perm space,此OOM是由于JVM中perm的最大值不满足需要,将设置perm的最大值调高即可,参数样例为:-XX:MaxPermSize=512M
- OOM for GC=>例如:java.lang:OutOfMemoryError: GC overhead limit exceeded,此OOM是由于JVM在GC时,对象过多,导致内存溢出,建议调整GC的策略
- OOM for native thread created:java.lang.OutOfMemoryError: unable to create new native thread,此OOM是由于进程剩余的空间不足,导致创建进程失败
- OOM for allocate huge array:Exception in thread "main": java.lang.OutOfMemoryError: Requested array size exceeds VM limit,此类信息表明应用程序(或者被应用程序调用的APIs)试图分配一个大于堆大小的数组
- OOM for small swap:Exception in thread "main": java.lang.OutOfMemoryError: request bytes for . Out of swap space?,抛出这类错误,是由于从native堆中分配内存失败,并且堆内存可能接近耗尽
- OutOfMemoryError thrown while trying to throw OutOfMemoryError; no stack trace available,抛出这类错误,一般是由于方法重复调用、死循环引起,直至内存耗尽
[解决方案]
The core Android system has died and is going through a runtime restart. All running apps will be promptly killed.
应用调用相关接口时,此时会抛出 RuntimeException,而不会抛出 DeadSystemException,此处可以应用可以捕获运行时异常 RuntimeException。