之前的一篇文章,讲到了应用程序无响应(ANR)时Android的处理逻辑。这篇文章,就来分析下应用进程发生崩溃(Crash)时,Android是如何处理的?总的说来,Android主要有两大类Crash:
- Java(JVM)层: 应用程序发生运行时错误(如空指针,浮点运算错误,数据索引超出界限)或者系统进程崩溃(长时间无响应);
- Native层: native进程或者kernel发生运行时错误;
之前的一篇文章,讲到了应用程序无响应(ANR)时Android的处理逻辑。这篇文章,就来分析下应用进程发生崩溃(Crash)时,Android是如何处理的?总的说来,Android主要有两大类Crash:
Activity是Android负责与用户交互的实体。一个应用一般由多个Activity组成;一个Activity不仅可以打开应用内的Activity,也可以打开其他应用内的Activity,比如通过一个名为android.intent.action.VIEW
的Intent事件可以打开浏览器;通过android.intent.action.DIAL
可以向CALL发送拨号请求。一系列的Activity组合在一起(完成某个特定的“任务“)构成了一个任务(一个后进先出的队列,被称为back stack),而多个任务又构成了一个任务栈(后台可以有多个任务栈,但系统如需恢复内存,则会杀死部分应用,清空部分任务,以释放内存,因此长时间后应用状态会丢失)。以下图为例,图中的back stack一开始只有一个Activity1;接着在Activity1中启动Activity2,此时Activity2位于队列的顶部,而Activity1位于栈底(Activity1处于Stop状态),而Activity2又启动一个Activity3,同样,Activity3压入栈顶。如果此时通过回退键(Back)用户离开Activity3,则Activity3从栈中清除,被系统销毁,而Activity2则恢复到可见状态(resume)。
ANR即Application Not Responding, 应用无响应:当应用在某一个时间内无法及时响应用户请求时,系统会弹出一个对话框,告知用户应用无法响应。此时,用户可以选择关闭应用。对于Android应用来说,有一个UI线程(主线程)专门负责与用户交互,如果在此线程中进行耗时的操作,比如读取磁盘数据、从网络下载资源等IO操作,通常会导致UI线程阻塞,从而无法及时处理用户的输入请求,发生ANR。
Android是通过AMS跟WMS来监控应用响应状态的,一般有如下两种ANR情况:
对于输入无响应的情况,native监控到输入无响应时,则告知WMS,最后由WMS发送消息告诉AMS,某个应用发送了ANR,最后AMS会弹出Application Not Responding的对话框,请求用户关闭应用;对于广播无响应的情况是由AMS负责处理的,AMS对于每个发送出去给广播接收者的广播都有一个10s的定时,如果广播接收者在10s内尚未处理完,则视为无响应,因此也会弹出Application Not Responding。
Java语言synchronized
关键字自带了一个内置的隐性锁(implicit lock),使用起来方便简单,但是内置锁一旦使用,则会强制将某个代码块加锁或者解锁,而且内置锁并不支持可中断的获取锁。从Java5.0开始,提供了一个并发工具包java.util.concurrent.*
,实现了显性锁(explicit lock)ReentrantLock
(可重入锁,可多次获取同一个锁);ReentrantLock
实现了与synchronized
一样的功能,确保并发过程中数据的互斥访问与可见性。获取ReentrantLock
相当于进入一个synchronized
代码块,而释放ReentrantLock
则相当于从一个synchronized
代码块退出。ReentrantLock
实现了如下Lock.java
接口:
1 |
|
在古希腊时代,哲学家Democritus(德谟克利特)提出了关于物质构成的理论:所有物质都由不可分割的元素组成,这种元素被称之为原子(atom)。编程中所说的原子性(atomic)借用了这个概念, 用于表示某个代码指令动作的不可中断性。在之前的一篇文章(Java Memory Model)时,提到多线程并发访问共享数据时,会出现数据竞争,从而导致数据不一致的情况。比如对一个整型变量进行加一的操作: ++counter
,表面上看,这是一个单一原子操作,但实际上这个操作有三个步骤:首先,需要从内存中(有可能是cache中)加载到寄存器;接着,将该值加一;最后需要将寄存器中的值写入内存。这样,在多个线程访问的情况,上述几个步骤出现交织执行,就可能出现各个线程读写数据不一致的情形。
Atomic Theory: http://www.softschools.com/timelines/atomic_theory_timeline/95/
于是,有了原子操作。原子操作确保了执行的不可中断,因而能避免数据冲突。Java从5.0开始有一个atomic
的工具包专门支持int
,long
以及引用变量的原子操作,而在硬件层面,目前大部分处理器都支持诸如CAS
(CompareAndSet/CompareAndSwap),FAA
(FetchAndAdd)等原子指令。在介绍Java中的原子操作类之前,先来了解下硬件层面的原子指令。
在之前的一篇文章(Glide架构分析 )中介绍了Glide的具体原理。这篇文章,用一个下载图片的示例来说明Glide加载图片的整个过程。以下是用Glide从网络上加载一个图片的代码片段:
1 |
|
大致说来,在Glide中图片的加载有如下几个过程:
GenericRequest<T>
;Engine
,由其负责资源的加载以及数据解码任务的管理;EngineRunnable
首先尝试从DISK加载资源,完成后进行回调,将数据加载到ImageView
中;Target
对象;现在就来详细的看下Glide加载图片的整个过程。
如何专注你的聪明才智,比你有多聪明更重要
在之前一篇文章里,讲到了利用synchronized
关键字来进行同步,从而避免多线程并发执行时可能出现的竞争条件,那么JVM又是如何实现线程之间的通信的?换句话说,线程A写共享数据的结果怎么确保被其他线程可见,使得线程之间共享的数据对每个线程而言都是一致的?Java从5.0开始定义了一个新的Memory Model(Java Specification Request 133, JSR133),在多线程情况下,为Java程序提供了一个最少程度的保证:正确同步的情况下,一个线程写共享变量对于其他线程是可见的。Java Memory Model(JMM)抽象了不同计算机平台底层内存读写细节(Register; Cache; Main Memory),为程序员提供了一个访问内存的统一的模型与视角,从而确保在不同平台上程序能有同样的结果,实现Java“编写一次,可以在任何平台上执行”的目标。
与Java程序类似,Android应用程序框架层(Application Framework)以及APP运行在一个Dalvik Virtual Machine之上,那么,Android启动时框架层是如何初始化的,从何处初始化的?为此,Android在启动时会首先初始化一个专门的系统进程zygote
来负责启动与初始化Java代码,比如系统服务进程system_server
的启动,系统启动时各种Java服务的初始化,APP资源文件的加载,APP进程的创建与启动。这篇文章,主要讲zygote
进程的两个问题:
zygote
进程如何初始化的;zygote
创建的?在计算机诞生初期,由于其高昂的计算成本,只能允许一个用户运行一个任务或者一批任务(Batch processing)。随着技术的发展,人们开始思考一个问题,如何在计算机上实现多个任务“同时”运行?一开始,采用的是分时策略(Time sharing),就是允许多个用户共享一台计算机,但每个用户占用计算机的一个时间片,在这个时间后,由另一个用户接着运行使用。分时系统在一定程度上提高了计算效率,实现了资源的共享。但其实际上并没有真正实现任务的“同时”(并发,concurrency)运行。
现代并发编程(Concurrent Pragramming)概念的出现一方面是受到操作系统中如进程、中断以及抢占的影响;一方面由于计算机硬件技术的发展而出现的多核处理器。