JasonWang's Blog

JasonWang's Blog

Android进程Crash处理流程

之前的一篇文章,讲到了应用程序无响应(ANR)时Android的处理逻辑。这篇文章,就来分析下应用进程发生崩溃(Crash)时,Android是如何处理的?总的说来,Android主要有两大类Crash:

  • Java(JVM)层: 应用程序发生运行时错误(如空指针,浮点运算错误,数据索引超出界限)或者系统进程崩溃(长时间无响应);
  • Native层: native进程或者kernel发生运行时错误;
Android中的任务管理

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)。

Android ANR处理流程

ANR即Application Not Responding, 应用无响应:当应用在某一个时间内无法及时响应用户请求时,系统会弹出一个对话框,告知用户应用无法响应。此时,用户可以选择关闭应用。对于Android应用来说,有一个UI线程(主线程)专门负责与用户交互,如果在此线程中进行耗时的操作,比如读取磁盘数据、从网络下载资源等IO操作,通常会导致UI线程阻塞,从而无法及时处理用户的输入请求,发生ANR。

Android是通过AMS跟WMS来监控应用响应状态的,一般有如下两种ANR情况:

  • 应用在给定时间内没有对用户输入(按键或者触屏操作)做出响应,通常是5s;
  • 广播接收者(BroadcastReceiver)没有在10s内处理完成;

对于输入无响应的情况,native监控到输入无响应时,则告知WMS,最后由WMS发送消息告诉AMS,某个应用发送了ANR,最后AMS会弹出Application Not Responding的对话框,请求用户关闭应用;对于广播无响应的情况是由AMS负责处理的,AMS对于每个发送出去给广播接收者的广播都有一个10s的定时,如果广播接收者在10s内尚未处理完,则视为无响应,因此也会弹出Application Not Responding

Java并发编程之锁

Java语言synchronized关键字自带了一个内置的隐性锁(implicit lock),使用起来方便简单,但是内置锁一旦使用,则会强制将某个代码块加锁或者解锁,而且内置锁并不支持可中断的获取锁。从Java5.0开始,提供了一个并发工具包java.util.concurrent.*,实现了显性锁(explicit lock)ReentrantLock(可重入锁,可多次获取同一个锁);ReentrantLock实现了与synchronized一样的功能,确保并发过程中数据的互斥访问与可见性。获取ReentrantLock相当于进入一个synchronized代码块,而释放ReentrantLock则相当于从一个synchronized代码块退出。ReentrantLock实现了如下Lock.java接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public interface Lock {
//获取锁
void lock();

//获取锁,可响应中断
void lockInterruptibly() throws InterruptedException;

//尝试获取锁,如果可用则返回TRUE,否则返回FALSE
boolean tryLock();

//尝试在给定时间内获取锁,如果超时或者发生中断,则返回FALSE
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

//释放锁
void unlock();

//创建与该锁绑定的Conidition变量
Condition newCondition();
}


Java并发编程之原子操作

在古希腊时代,哲学家Democritus(德谟克利特)提出了关于物质构成的理论:所有物质都由不可分割的元素组成,这种元素被称之为原子(atom)。编程中所说的原子性(atomic)借用了这个概念, 用于表示某个代码指令动作的不可中断性。在之前的一篇文章(Java Memory Model)时,提到多线程并发访问共享数据时,会出现数据竞争,从而导致数据不一致的情况。比如对一个整型变量进行加一的操作: ++counter,表面上看,这是一个单一原子操作,但实际上这个操作有三个步骤:首先,需要从内存中(有可能是cache中)加载到寄存器;接着,将该值加一;最后需要将寄存器中的值写入内存。这样,在多个线程访问的情况,上述几个步骤出现交织执行,就可能出现各个线程读写数据不一致的情形。

Atomic Theory: http://www.softschools.com/timelines/atomic_theory_timeline/95/

于是,有了原子操作。原子操作确保了执行的不可中断,因而能避免数据冲突。Java从5.0开始有一个atomic的工具包专门支持intlong以及引用变量的原子操作,而在硬件层面,目前大部分处理器都支持诸如CAS(CompareAndSet/CompareAndSwap),FAA(FetchAndAdd)等原子指令。在介绍Java中的原子操作类之前,先来了解下硬件层面的原子指令。

Glide详解之图片加载过程分析

在之前的一篇文章(Glide架构分析 )中介绍了Glide的具体原理。这篇文章,用一个下载图片的示例来说明Glide加载图片的整个过程。以下是用Glide从网络上加载一个图片的代码片段:

1
2
3
4
5
6
7
8
9
    
Glide
.with(activity) // Activity
.load(url)
.centerCrop()
.placeholder(R.drawable.loading_spinner)
.crossFade()
.into(myImageView);

大致说来,在Glide中图片的加载有如下几个过程:

  • 产生一个图片加载的请求GenericRequest<T>;
  • 将图片加载请求发送给资源引擎中心Engine,由其负责资源的加载以及数据解码任务的管理;
  • EngineRunnable首先尝试从DISK加载资源,完成后进行回调,将数据加载到ImageView中;
  • 磁盘中没有请求对应的资源,则尝试从网络端下载,并解码完成后回调,将数据加载到Target对象;

现在就来详细的看下Glide加载图片的整个过程。

Java并发编程之Java Memory Model

在之前一篇文章里,讲到了利用synchronized关键字来进行同步,从而避免多线程并发执行时可能出现的竞争条件,那么JVM又是如何实现线程之间的通信的?换句话说,线程A写共享数据的结果怎么确保被其他线程可见,使得线程之间共享的数据对每个线程而言都是一致的?Java从5.0开始定义了一个新的Memory Model(Java Specification Request 133, JSR133),在多线程情况下,为Java程序提供了一个最少程度的保证:正确同步的情况下,一个线程写共享变量对于其他线程是可见的。Java Memory Model(JMM)抽象了不同计算机平台底层内存读写细节(Register; Cache; Main Memory),为程序员提供了一个访问内存的统一的模型与视角,从而确保在不同平台上程序能有同样的结果,实现Java“编写一次,可以在任何平台上执行”的目标。

Zygote进程启动过程详解

与Java程序类似,Android应用程序框架层(Application Framework)以及APP运行在一个Dalvik Virtual Machine之上,那么,Android启动时框架层是如何初始化的,从何处初始化的?为此,Android在启动时会首先初始化一个专门的系统进程zygote来负责启动与初始化Java代码,比如系统服务进程system_server的启动,系统启动时各种Java服务的初始化,APP资源文件的加载,APP进程的创建与启动。这篇文章,主要讲zygote进程的两个问题:

  • zygote进程如何初始化的;
  • APP进程是如何通过zygote创建的?
Java并发编程综述

在计算机诞生初期,由于其高昂的计算成本,只能允许一个用户运行一个任务或者一批任务(Batch processing)。随着技术的发展,人们开始思考一个问题,如何在计算机上实现多个任务“同时”运行?一开始,采用的是分时策略(Time sharing),就是允许多个用户共享一台计算机,但每个用户占用计算机的一个时间片,在这个时间后,由另一个用户接着运行使用。分时系统在一定程度上提高了计算效率,实现了资源的共享。但其实际上并没有真正实现任务的“同时”(并发,concurrency)运行。

现代并发编程(Concurrent Pragramming)概念的出现一方面是受到操作系统中如进程、中断以及抢占的影响;一方面由于计算机硬件技术的发展而出现的多核处理器。

  • 进程,是程序执行的一个实体,是操作系统对CPU,寄存器,堆栈,内存,文件系统等计算资源的一种执行时的抽象;有了进程,计算机就可以通过调度系统来实现多个任务“同时”运行了,这里所谓的“同时”并不是多个程序真的在一个CPU中同时运行,而是说调度程序快速的在多个进程之间切换,交替执行不同程序,从而更有效的利用了计算机资源;
  • 多核CPU的出现,为并发、并行计算提供了另一种可能。以前程序只能在一个CPU上运行,现在单个程序可以同时在多个CPU上同时运行了(Parallel Computing);或者多个进程同时在多个CPU上运行。
avatar
JasonWang
生命短暂,莫空手而归