Android Crash与ANR详细介绍
作者:明智的健哥
Crash
Crash是指程序闪退,导致APP不能正常使用。Crash产生的原因有很多,下面只是列举了一些常见原因。
空指针
空指针应该是项目中最容易产生crash的情况了,举个例子,我们获取某个对象的属性或方法时,这个对象为Null时,如何没有判空,则会出现空指针异常NullPointException,所以这就要求使用对象的时候进行非空判断,在这点,我觉得kotlin就做得很好,利用空安全可以很好地避免NullPointException。
角标越界
在使用数组或者集合的时候会出现IndexOutOfBoundsException,在根据index进行取值时,最好先判断该索引值是否存在或者使用try-catch捕捉异常。
集合元素删除操作
比如我们需要将集合中满足条件的元素删除掉
list.forEach { if (it == 3) { list.removeAt(it) } }
这样做会引起Crash,会报ConcurrentModificationException,针对这个问题,我们可以从后面开始遍历
for (index in list.size - 1 downTo 0) { if (list[index] == 3) { list.removeAt(index) } }
也可以使用迭代器进行遍历删除元素
val iterator = list.iterator() while (iterator.hasNext()) { val a = iterator.next() if (a == 3) { iterator.remove() } }
当多个线程同时操作某个数组时,不要进行数组的增删改查等操作,这样同样也会引起相关的Crash或数据查询不准确等问题。
异步操作后对界面元素的处理
在fragment中使用Context前最好先加上判断isAdded判断,特别是异步操作后使用Context,很有可能出现报错(Fragment not attached to a context)而闪退,所有的异步回调后若要操作View,都要判断view是否为空,否则会出现界面销毁后View为空,空指针闪退问题。
Intent传递数据过大
Intent传512K以下的数据可以正常传递,高于512K则会出错,因为考虑到Intent还要包括要启动的Activity等信息,所以实际可以传的数据应该略小于512K。
val data = ByteArray(1024 * 1024) val intent = Intent(this, ExpActivity::class.java) intent.putExtra("test", data) startActivity(intent)
这段代码会导致Crash
Caused by: android.os.TransactionTooLargeException: data parcel size 1049012 bytes
因为我们在Intent中携带的数据要从APP进程传输到AMS进程,再由AMS进程传输到目标Activity所在进程,普通的由 Zygote 孵化而来的用户进程,所映射的Binder内存大小是不到1M,但是,在使用Intent传递数据时,1M并不是安全上限,因为Binder可能正在处理其它的传输工作。总而言之,startActivity携带的数据会经过Binder内核再传递到目标Activity中去,因为binder映射内存的限制,所以startActivity也会这个限制。
在子线程中操作UI
子线程中是不能操作UI的,如果在子线程中某个时机想要改变UI,可以使用Handler或者kotlin协程切换,需要注意的是,在子线程中也不可以操作Dialog和Toast。但是,这有个很有意思的点,举个例子,如果你在onCreate中开启一个子线程改变UI,会发现程序运行正常,没报错,像这样
class ExpActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_exp) val name = findViewById<TextView>(R.id.name) Thread { name.text = "name" }.start() } }
但是,你延迟一秒后再操作UI,又会闪退报错
class ExpActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_exp) val name = findViewById<TextView>(R.id.name) Thread { Thread.sleep(1000) name.text = "name" }.start() } }
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
这到底是为什么呢?这个的关键是ViewRootImpl类,它会去检查当前线程是不是主线程,如果不是就会抛出异常。像上面的情况,在onCreate中未延时直接操作UI不闪退,是因为此时ViewRootImpl还没有被初始化,这个时候程序没有去检测当前线程是不是主线程,所以没有抛异常。严格地讲,在ViewRootImpl构造的时候赋值的,赋值的就是当前的Thread对象,也就是说,你ViewRootImpl在哪个线程创建的,你后续的UI更新就需要在哪个线程执行,跟是不是UI线程毫无关系。
ANR
ANR是指程序未响应,在Android系统中,AMS和WMS会检测App的响应时间,如果App在特定时间无法响应屏幕触摸或键盘输入事件,或者特定事件没有处理完毕,就会出现ANR。
不同Context规定的上限时间不同:
- 主线程对输入事件5秒内没有处理完毕。
- 主线程在执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕。
- 主线程在Service的各个生命周期函数时20秒内没有处理完毕。
避免ANR就要尽量避免在主线程中做耗时操作,耗时操作尽量放在子线程中。
我们可以通过/data/anr/traces.txt文件来分析ANR的产生,通过adb命令可以导出该文件,不过traces文件记录的东西可能比较多,分析的时候需要针对性地搜索出相关记录,该文件会记录进程ID,包名,造成ANR的原因和产生ANR的具体行数。
到此这篇关于Android Crash与ANR详细介绍的文章就介绍到这了,更多相关Android Crash与ANR内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!