0%

版本更新

在版本更新的场景中,需要注意由于某些修改造成的问题。

数据持久化

对于持久化数据结构的更改,需要特别注意,否则可能造成比较严重的问题。比如说SharedPreferences中,如果上个版本某个持久化变量为某一类型(Boolean,String等),在下个版本如果由于业务需求,要注意不要直接更改该变量的类型,最好新增一个变量,否则会崩溃。

多Module

模块间compileOnly

考虑一种场景:

module_a中的构建脚本中有一行代码,这行代码会在其module内的BuildConfig生成一个TEST_VALUE常量:

1
buildConfigField "String", "TEST_VALUE", '"testtest"'

将module_a打包成aar,在module_b中通过compileOnly引用它,然后在module_b中打印module_a的BuildConfig.TEST_VALUE的值:

1
Log.d("LLL", "value = ${BuildConfig.TEST_VALUE}")

接着将module_a中的上行代码改成:

1
buildConfigField "String", "TEST_VALUE", '"testtesttest"'

app module通过implementation引用module_a模块,打包安装apk,发现module_b中打印的值为:value = testtest

疑问:

module_b引用module_a aar包的方式是compileOnly,那么这个aar不会被打包进apk,而app module通过implementation方式引用了module_a,那么module_b中调用aar的代码最终调用的应该是module_a中的源码,按理来说打印的内容应该是value = testtesttest

于是我将apk反编译了以下,看到在module_a的BuildConfig.smali代码中有一行:

1
.field public static final TEST_VALUE:Ljava/lang/String; = "testtesttest"

这说明打包进apk的确实是testtesttest,那么为啥module_b中会打印value = testtest呢? 于是我接着看了一下打印的smali代码:

1
2
3
const-string v0, "LLL"
const-string v1, "value = testtest"
invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

结论:

在编译期的时候,这行引用了BuildConfig.TEST_VALUE的直接被编译成常量了,仔细一想也确实符合逻辑…,如果这个TEST_VALUE不是static final类型的常量,就不会出现这种问题,当时碰到问题的时候没有朝这个方向考虑,知道了原因那么就可以解决这个问题了:module_b中不直接通过BuildConfig.TEST_VALUE引用,而是在module_a中增加一个方法,其返回值为BuildConfig.TEST_VALUE,在module_b中通过调用这个方法来获取BuildConfig.TEST_VALUE的值。

概述

EventBus是一个适用于Android和Java的开源库,使用发布者/订阅者模式进行松散耦合。EventBus使通信只需几行代码即可解耦类,从而简化了代码,消除了依赖关系并加快了应用程序的开发。EventBus容易混淆程序逻辑,且不支持跨进程。

使用

第一步,定义事件:

1
2
3
4
5
6
7
8
9
10
11
public class MessageEvent {
private String message;

public MessageEvent(String message) {
this.message = message;
}

public void print() {
Log.d(TAG, Thread.currentThread().getName() + ": " + message);
}
}

第二步,创建订阅者:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(MessageEvent event) {
event.print();
}

@Override
protected void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}

@Override
protected void onStop() {
EventBus.getDefault().unregister(this);
super.onStop();
}

第三步,发送事件:

1
EventBus.getDefault().post(new MessageEvent("hearing"));

线程模型

事件模型是指事件订阅者所在线程和发布事件者所在线程的关系。

POSTING

事件的订阅和事件的发布处于同一线程。这是默认设置。该模式的开销最小,因为它完全避免了线程切换。使用此模式的事件处理程序必须快速返回,以避免阻塞可能是主线程的发布线程。

MAIN

在Android上,用户将在Android的主线程(UI线程)中被调用。如果发布线程是主线程,则将直接调用订阅方法,从而阻塞发布线程。否则,事件将排队等待传递(非阻塞)。如果不在Android上,则行为与POSTING相同。

MAIN_ORDERED

在Android上,用户将在Android的主线程(UI线程)中被调用。与MAIN不同,事件将始终排队等待传递。这确保了post调用是非阻塞的。

BACKGROUND

在Android上,用户将在后台线程中被调用。如果发布线程不是主线程,则将在发布线程中直接调用订阅方方法。如果发布线程是主线程,则EventBus使用单个后台线程,该线程将按顺序传递其所有事件。如果不在Android上,则始终使用后台线程。

ASYNC

不管是否在UI线程产生事件,都会在单独的子线程中消费事件,即每个订阅方法都会在一个单独的线程中执行。EventBus使用线程池重用来已完成的线程。

事件类型

普通事件

普通事件是指已有的事件订阅者能够收到事件发送者发送的事件,在事件发送之后注册的事件接收者将无法收到事件。发送普通事件可以调用EventBus.getDefault().post()方法进行发送。

粘性事件

粘性事件是指,不管是在事件发送之前注册的事件接收者还是在事件发送之后注册的事件接收者都能够收到事件。这里于普通事件的区别之处在于事件接收处需要定义事件接收类型,它可以通过@Subscribe(threadMode = xxx, sticky = true)的方式进行声明;在事件发送时需要调用EventBus.getDefault().postSticky()方法进行发送。事件类型默认为普通事件。

事件优先级

订阅者优先级以影响事件传递顺序。在同一传递线程ThreadMode中,优先级较高的订阅者将在优先级较低的其他订阅者之前接收事件。默认优先级为0。注意:优先级不影响具有不同ThreadMode的订阅服务器之间的传递顺序。

数据结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// key: 订阅方法类型(MessageEvent.class)
// value: 所有订阅了该类型的订阅者集合
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

// key: 订阅者(MainActivity.this)
// value: 订阅事件集合(MessageEvent.class...)
private final Map<Object, List<Class<?>>> typesBySubscriber;

// 粘滞事件集合
// key: 订阅方法类型(MessageEvent.class)
// value: 订阅事件具体实例(MessageEvent messageEvent)
private final Map<Class<?>, Object> stickyEvents;

public class SubscriberMethod {
final Method method;
final ThreadMode threadMode;
final Class<?> eventType;
final int priority;
final boolean sticky;
/** Used for efficient comparison */
String methodString;

// ...
}

// 订阅方法描述,实体类(当前类中的订阅方法)
final class Subscription {
final Object subscriber; // 订阅者(MainActivity.this)
final SubscriberMethod subscriberMethod; // 订阅方法
/**
* Becomes false as soon as {@link EventBus#unregister(Object)} is called, which is checked by queued event delivery
* {@link EventBus#invokeSubscriber(PendingPost)} to prevent race conditions.
*/
volatile boolean active;
}

EventBusAnnotationProcessor

EventBusAnnotationProcessor是EventBus提供的注解处理器,它用来在编译期通过读取@Subscribe()注解并解析,处理其中所包含的信息,然后生成类似于EventBusIndex.java的类来保存所有订阅者关于订阅的信息。可以在gradle脚本中添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [eventBusIndex: 'org.greenrobot.eventbus.MyEventBusIndex', verbose: "true"]
}
}
}
}

dependencies {
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.2.0'
}

生成的MyEventBusIndex类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package org.greenrobot.eventbus;

import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberMethodInfo;
import org.greenrobot.eventbus.meta.SubscriberInfo;
import org.greenrobot.eventbus.meta.SubscriberInfoIndex;

import org.greenrobot.eventbus.ThreadMode;

import java.util.HashMap;
import java.util.Map;

/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;

static {
SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();

putIndex(new SimpleSubscriberInfo(com.hearing.eventbusdemo.MainActivity.class, true,
new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onEvent1", com.hearing.eventbusdemo.MessageEvent.class, ThreadMode.MAIN),
new SubscriberMethodInfo("onEvent2", com.hearing.eventbusdemo.MessageEvent.class, ThreadMode.MAIN),
}));

}

private static void putIndex(SubscriberInfo info) {
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}

@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
}

EventBusAnnotationProcessor其实就是通过注解处理器的方式,在编译器生成一个记录了订阅者方法新的的类,具体生成过程可以参考源码,注解相关的可以看这里

使用时需要添加index,否则使用时依旧用的是反射的方式处理注解:

1
mEventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

EventBus创建

getDefault

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
static volatile EventBus defaultInstance;
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

public static EventBus getDefault() {
EventBus instance = defaultInstance;
if (instance == null) {
synchronized (EventBus.class) {
instance = EventBus.defaultInstance;
if (instance == null) {
instance = EventBus.defaultInstance = new EventBus();
}
}
}
return instance;
}

public EventBus() {
this(DEFAULT_BUILDER);
}

EventBus(EventBusBuilder builder) {
logger = builder.getLogger();
subscriptionsByEventType = new HashMap<>();
typesBySubscriber = new HashMap<>();
stickyEvents = new ConcurrentHashMap<>();
mainThreadSupport = builder.getMainThreadSupport();
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
backgroundPoster = new BackgroundPoster(this);
asyncPoster = new AsyncPoster(this);
indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
logSubscriberExceptions = builder.logSubscriberExceptions;
logNoSubscriberMessages = builder.logNoSubscriberMessages;
sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
throwSubscriberException = builder.throwSubscriberException;
eventInheritance = builder.eventInheritance;
executorService = builder.executorService;
}

自定义

可以通过builder自定义参数,如添加index类。

1
mEventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();

register

register

1
2
3
4
5
6
7
8
9
10
11
12
public void register(Object subscriber) {
// 获取订阅的类对象
Class<?> subscriberClass = subscriber.getClass();
// 获取订阅者所有的订阅方法,即以@Subscribe为注解的一些public方法
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
// 依次注册这些订阅方法
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}

findSubscriberMethods

SubscriberMethodFinder.java: 用来查找和缓存订阅者响应函数的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class SubscriberMethodFinder {
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();

SubscriberMethodFinder(List<SubscriberInfoIndex> subscriberInfoIndexes, boolean strictMethodVerification,
boolean ignoreGeneratedIndex) {
this.subscriberInfoIndexes = subscriberInfoIndexes; // default: null
this.strictMethodVerification = strictMethodVerification; // default: false
this.ignoreGeneratedIndex = ignoreGeneratedIndex; // default: false
}

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
// 从缓存中获取订阅方法
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
// 是否忽略注解处理器生成的MyEventBusIndex类
if (ignoreGeneratedIndex) {
// 利用反射来获取订阅类中的订阅方法信息
subscriberMethods = findUsingReflection(subscriberClass);
} else {
// 从注解器生成的MyEventBusIndex类中获得订阅类的订阅方法信息
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
// 添加缓存
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
}

对于获取注册的订阅方法,首先就是通过缓存来获取,如果没有的话则通过以下两种方式进行获取:

  1. EventBusAnnotationProcessor注解处理器在编译期通过读取@Subscribe()注解并解析,处理其中所包含的信息,然后生成MyEventBusIndex.java类来保存所有订阅者关于订阅的信息。
  2. 运行时使用反射来获得这些订阅者的信息。

很显然编译器间通过注解处理器读取订阅信息,避免运行时反射的方式,性能上更优。

findUsingReflection

FindState

FindState类: 保存了订阅者和订阅方法信息的一个实体类,包括订阅类中所有订阅的事件类型和所有的订阅方法等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
static class FindState {
final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
final Map<Class, Object> anyMethodByEventType = new HashMap<>();
final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
final StringBuilder methodKeyBuilder = new StringBuilder(128);

Class<?> subscriberClass;
Class<?> clazz;
boolean skipSuperClasses;
SubscriberInfo subscriberInfo;

void initForSubscriber(Class<?> subscriberClass) {
this.subscriberClass = clazz = subscriberClass;
skipSuperClasses = false;
subscriberInfo = null;
}

void moveToSuperclass() {
if (skipSuperClasses) {
clazz = null;
} else {
clazz = clazz.getSuperclass();
String clazzName = clazz.getName();
if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") ||
clazzName.startsWith("android.") || clazzName.startsWith("androidx.")) {
clazz = null;
}
}
}
}

findUsingReflection

1
2
3
4
5
6
7
8
9
10
11
12
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
// FindState其实就是一个里面保存了订阅者和订阅方法信息的一个实体类
FindState findState = prepareFindState();
// 初始化findState
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
// 获取订阅方法
findUsingReflectionInSingleClass(findState);
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}

prepareFindState

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
private static final int POOL_SIZE = 4;

private FindState prepareFindState() {
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
FindState state = FIND_STATE_POOL[i];
if (state != null) {
FIND_STATE_POOL[i] = null;
return state;
}
}
}
return new FindState();
}

findUsingReflectionInSingleClass

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC; // 5192
private static final int BRIDGE = 0x40;
private static final int SYNTHETIC = 0x1000;

private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// 获取本类声明的所有方法
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
try {
// 获取本类及父类的所有public方法
methods = findState.clazz.getMethods();
} catch (LinkageError error) {
// ...
throw new EventBusException(msg, error);
}
// 因为调用了getMethods,所以设置为true
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers();
// 订阅方法为public同时不是abstract、static
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
// 注解方法必须只有一个参数
if (parameterTypes.length == 1) {
// 获取订阅方法的注解
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
// 添加该注解方法
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}

getMethodsAndRelease

1
2
3
4
5
6
7
8
9
10
11
12
13
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
findState.recycle();
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
if (FIND_STATE_POOL[i] == null) {
FIND_STATE_POOL[i] = findState;
break;
}
}
}
return subscriberMethods;
}

findUsingInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}

private SubscriberInfo getSubscriberInfo(FindState findState) {
if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
if (findState.clazz == superclassInfo.getSubscriberClass()) {
return superclassInfo;
}
}
if (subscriberInfoIndexes != null) {
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
if (info != null) {
return info;
}
}
}
return null;
}

subscribe

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
// key订阅方法类型(MessageEvent.class)——value所有订阅了该类型的订阅者集合
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

// key订阅者(MainActivity.this)——value订阅事件集合(MessageEvent.class...)
private final Map<Object, List<Class<?>>> typesBySubscriber;
private final Map<Class<?>, Object> stickyEvents;

// 在synchronized中调用
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
// 订阅方法的参数类型,也是事件类型
Class<?> eventType = subscriberMethod.eventType;
// 订阅方法描述,实体类(当前类中的订阅方法)
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}

int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
// 根据优先级,将订阅者插入到指定的位置
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// 获取订阅者所有订阅的事件类型
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);

// 如果接收sticky事件,立即分发sticky事件
if (subscriberMethod.sticky) {
// 默认为true,是否考虑eventType子类的sticky事件
if (eventInheritance) {
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
// eventType是candidateEventType的父类或接口/eventType和candidateEventType是同一个类或接口
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}

private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
// --> Strange corner case, which we don't take care of here.
postToSubscription(newSubscription, stickyEvent, isMainThread());
}
}

订阅流程:

  • 首先获取订阅方法的参数类型即订阅事件类型;
  • 根据订阅事件类型获取该事件类型的所有订阅者;
  • 将该订阅者添加到该事件类型的订阅者集合中,即:subscriptionsByEventType;
  • 获取订阅者所有的订阅事件类型;
  • 将该事件类型添加到该订阅者的订阅事件类型集中即:typesBySubscriber。

post

PostingThreadState

1
2
3
4
5
6
7
8
9
10
11
final static class PostingThreadState {
// 当前线程的事件队列
final List<Object> eventQueue = new ArrayList<>();
// 是否有事件正在分发
boolean isPosting;
// post的线程是否是主线程
boolean isMainThread;
Subscription subscription;
Object event;
boolean canceled;
}

post

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
@Override
protected PostingThreadState initialValue() {
return new PostingThreadState();
}
};

public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
// 将当前事件添加进当前进程的事件队列里
eventQueue.add(event);

if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
// 逐个post当前线程的队列中的事件
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}

postSingleEvent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// key post的事件的类型(EventMessage.class)——value post事件类型本身及所有父类和接口
private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
if (eventInheritance) {
// post to 订阅了本事件类型及所有父类和接口的订阅者
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) {
// NoSubscriberEvent: This Event is posted by EventBus when no subscriber is found for a posted event.
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}

// 查找所有Class对象,包括超类和接口
private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
synchronized (eventTypesCache) {
List<Class<?>> eventTypes = eventTypesCache.get(eventClass);
if (eventTypes == null) {
eventTypes = new ArrayList<>();
Class<?> clazz = eventClass;
while (clazz != null) {
eventTypes.add(clazz);
addInterfaces(eventTypes, clazz.getInterfaces());
clazz = clazz.getSuperclass();
}
eventTypesCache.put(eventClass, eventTypes);
}
return eventTypes;
}
}

// 迭代添加指定类型的接口
static void addInterfaces(List<Class<?>> eventTypes, Class<?>[] interfaces) {
for (Class<?> interfaceClass : interfaces) {
if (!eventTypes.contains(interfaceClass)) {
eventTypes.add(interfaceClass);
addInterfaces(eventTypes, interfaceClass.getInterfaces());
}
}
}

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted;
try {
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}

postToSubscription

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
// 直接在当前线程调用
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
// 发布者处于主线程则直接调用
invokeSubscriber(subscription, event);
} else {
// 发布者不处于主线程则入队列
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
// 任何时候都入队列
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
// 单独线程入队列
backgroundPoster.enqueue(subscription, event);
} else {
// 否则直接执行
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
// 异步入队列
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}

invokeSubscriber

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void invokeSubscriber(PendingPost pendingPost) {
Object event = pendingPost.event;
Subscription subscription = pendingPost.subscription;
PendingPost.releasePendingPost(pendingPost);
if (subscription.active) {
invokeSubscriber(subscription, event);
}
}

void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}

private void handleSubscriberException(Subscription subscription, Object event, Throwable cause) {
if (event instanceof SubscriberExceptionEvent) {
if (logSubscriberExceptions) {
// Don't send another SubscriberExceptionEvent to avoid infinite event recursion, just log
logger.log(Level.SEVERE, "SubscriberExceptionEvent subscriber " + subscription.subscriber.getClass()
+ " threw an exception", cause);
SubscriberExceptionEvent exEvent = (SubscriberExceptionEvent) event;
logger.log(Level.SEVERE, "Initial event " + exEvent.causingEvent + " caused exception in "
+ exEvent.causingSubscriber, exEvent.throwable);
}
} else {
if (throwSubscriberException) {
throw new EventBusException("Invoking subscriber failed", cause);
}
if (logSubscriberExceptions) {
logger.log(Level.SEVERE, "Could not dispatch event: " + event.getClass() + " to subscribing class "
+ subscription.subscriber.getClass(), cause);
}
if (sendSubscriberExceptionEvent) {
SubscriberExceptionEvent exEvent = new SubscriberExceptionEvent(this, cause, event,
subscription.subscriber);
post(exEvent);
}
}
}

PendingPostQueue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
final class PendingPostQueue {
private PendingPost head;
private PendingPost tail;

synchronized void enqueue(PendingPost pendingPost) {
if (pendingPost == null) {
throw new NullPointerException("null cannot be enqueued");
}
if (tail != null) {
tail.next = pendingPost;
tail = pendingPost;
} else if (head == null) {
head = tail = pendingPost;
} else {
throw new IllegalStateException("Head present, but no tail");
}
notifyAll();
}

synchronized PendingPost poll() {
PendingPost pendingPost = head;
if (head != null) {
head = head.next;
if (head == null) {
tail = null;
}
}
return pendingPost;
}

synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException {
if (head == null) {
wait(maxMillisToWait);
}
return poll();
}
}

HandlerPoster

创建

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class AndroidHandlerMainThreadSupport implements MainThreadSupport {
// Looper.getMainLooper()
private final Looper looper;

public AndroidHandlerMainThreadSupport(Looper looper) {
this.looper = looper;
}

@Override
public boolean isMainThread() {
return looper == Looper.myLooper();
}

@Override
public Poster createPoster(EventBus eventBus) {
return new HandlerPoster(eventBus, looper, 10);
}
}

HandlerPoster

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class HandlerPoster extends Handler implements Poster {

private final PendingPostQueue queue;
private final int maxMillisInsideHandleMessage; // 10s
private final EventBus eventBus;
private boolean handlerActive;

protected HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
super(looper);
this.eventBus = eventBus;
this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
queue = new PendingPostQueue();
}

@Override
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}

@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
long timeInMethod = SystemClock.uptimeMillis() - started;
// 为了尽量避免ANR?
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
}

PendingPost

PendingPost类似于Android的Message,里面保存了subscription和event,且也使用了享元模式共享PendingPost池。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
final class PendingPost {
private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();

Object event;
Subscription subscription;
PendingPost next;

private PendingPost(Object event, Subscription subscription) {
this.event = event;
this.subscription = subscription;
}

static PendingPost obtainPendingPost(Subscription subscription, Object event) {
synchronized (pendingPostPool) {
int size = pendingPostPool.size();
if (size > 0) {
PendingPost pendingPost = pendingPostPool.remove(size - 1);
pendingPost.event = event;
pendingPost.subscription = subscription;
pendingPost.next = null;
return pendingPost;
}
}
return new PendingPost(event, subscription);
}

static void releasePendingPost(PendingPost pendingPost) {
pendingPost.event = null;
pendingPost.subscription = null;
pendingPost.next = null;
synchronized (pendingPostPool) {
// Don't let the pool grow indefinitely
if (pendingPostPool.size() < 10000) {
pendingPostPool.add(pendingPost);
}
}
}
}

BackgroundPoster

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
final class BackgroundPoster implements Runnable, Poster {

private final PendingPostQueue queue;
private final EventBus eventBus;

private volatile boolean executorRunning;

BackgroundPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}

@Override
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!executorRunning) {
executorRunning = true;
eventBus.getExecutorService().execute(this);
}
}
}

@Override
public void run() {
try {
try {
while (true) {
// 最多等待1s,超时抛出异常
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
executorRunning = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}

}

AsyncPoster

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class AsyncPoster implements Runnable, Poster {

private final PendingPostQueue queue;
private final EventBus eventBus;

AsyncPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}

@Override
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
queue.enqueue(pendingPost);
eventBus.getExecutorService().execute(this);
}

@Override
public void run() {
PendingPost pendingPost = queue.poll();
if(pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
eventBus.invokeSubscriber(pendingPost);
}
}

postSticky

1
2
3
4
5
6
7
8
public void postSticky(Object event) {
synchronized (stickyEvents) {
stickyEvents.put(event.getClass(), event);
}
// Should be posted after it is putted, in case the subscriber wants to remove immediately
// 将粘性事件发送给已有的事件订阅者
post(event);
}

unregister

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public synchronized void unregister(Object subscriber) {
// 获取订阅者的所有订阅的事件类型
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<?> eventType : subscribedTypes) {
// 从事件类型的订阅者集合中移除订阅者
unsubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
// 获取事件类型的所有订阅者
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
i--;
size--;
}
}
}
}

取消订阅的流程:

  • 首先获取订阅者的所有订阅事件;
  • 遍历订阅事件;
  • 根据订阅事件获取所有的订阅了该事件的订阅者集合;
  • 从订阅者集合中将该订阅者移除;
  • 将步骤1中的集合中的订阅者移除。

支持跨进程的EventBus

概述

EventBus不支持跨进程传输,可以通过AIDL结合EventBus来实现跨进程。主进程有一个Service,负责维护监听器列表,以及监听相关事件,并发送给子进程;子进程需要绑定主进程的Service,并注册监听;主进程和子进程内的事件通过EventBus发送,需要跨进程的通过Binder转发。

接下来的实例要实现:

  • 主进程发送事件,主进程和子进程的订阅者都接收到订阅事件;
  • 子进程发送事件,主进程和子进程的订阅者都接收到订阅事件。

注:自定义的Message数据类需要实现Parcelable或Serializable接口,示例代码

AIDL

IEventInterface.aidl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// IEventInterface.aidl
package com.hearing.eventbusdemo.eventbus;

import com.hearing.eventbusdemo.eventbus.IEventCallback;
import android.os.Bundle;

interface IEventInterface {
// 子进程向主进程注册事件回调
void register(IEventCallback callback);
// 子进程解注册
void unregister(IEventCallback callback);
// 子进程向主进程通知事件
void notify(in Bundle event);
}

IEventCallback.aidl

1
2
3
4
5
6
7
8
9
// IEventCallback.aidl
package com.hearing.eventbusdemo.eventbus;

import android.os.Bundle;

interface IEventCallback {
// 主进程向子进程发送消息
void notifyEvent(in Bundle event);
}

EventBus

EventWrapper.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Event包装类,用来IPC转发
public class EventWrapper {
public Bundle mBundle;

public EventWrapper(Bundle bundle) {
mBundle = bundle;
}

@NonNull
@Override
public String toString() {
return mBundle == null ? "null" : mBundle.toString();
}
}

MyEventBus.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class MyEventBus extends EventBus {
private static final String KEY = "key";

private MyEventBus() {
}

private static class SingleTon {
private static MyEventBus sInstance = new MyEventBus();
}

public static MyEventBus getInstance() {
return SingleTon.sInstance;
}

// 转发到主进程和子进程
@Override
public void post(Object event) {
super.post(event);
Bundle bundle = new Bundle();
if (event instanceof Parcelable) {
bundle.putParcelable(KEY, (Parcelable) event);
super.post(new EventWrapper(bundle));
} else if (event instanceof Serializable) {
bundle.putSerializable(KEY, (Serializable) event);
super.post(new EventWrapper(bundle));
}
}

// 只发送到本进程
public void postSingle(Object event) {
super.post(event);
}

public Object unPack(@NonNull Bundle event) {
return event.get(KEY);
}
}

LocalService

主进程的Service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class LocalService extends Service {

private final RemoteCallbackList<IEventCallback> mRemoteCallbackList = new RemoteCallbackList<>();
private Binder mBinder = new IEventInterface.Stub() {
@Override
public void register(IEventCallback callback) throws RemoteException {
Log.d(TAG, "LocalService register: " + callback);
mRemoteCallbackList.register(callback);
}

@Override
public void unregister(IEventCallback callback) throws RemoteException {
Log.d(TAG, "LocalService unregister: " + callback);
mRemoteCallbackList.unregister(callback);
}

@Override
public void notify(Bundle event) throws RemoteException {
Log.d(TAG, "LocalService notify: " + event);
// 主进程收到子进程的事件后,通过EventBus转发给主进程的订阅者
MyEventBus.getInstance().postSingle(MyEventBus.getInstance().unPack(event));
}
};

// 桥梁:监听主进程发送的事件,并转发给子进程
@Subscribe
public void handle(EventWrapper wrapper) {
Log.v(TAG, "LocalService handle: " + wrapper);
synchronized (mRemoteCallbackList) {
int n = mRemoteCallbackList.beginBroadcast();
try {
for (int i = 0; i < n; i++) {
// 转发给子进程
mRemoteCallbackList.getBroadcastItem(i).notifyEvent(wrapper.mBundle);
}
} catch (RemoteException e) {
e.printStackTrace();
}
mRemoteCallbackList.finishBroadcast();
}
}

// 主进程的事件订阅,在接收到主进程的事件后,发给主进程的消息在这里处理,发给子进程的消息在handle方法中转发
@Subscribe
public void onEvent(String event) {
Log.d(TAG, "LocalService onEvent: " + event);
}

// 主进程的事件订阅,在接收到主进程的事件后,发给主进程的消息在这里处理,发给子进程的消息在handle方法中转发
@Subscribe
public void onEvent1(Integer event) {
Log.d(TAG, "LocalService onEvent: " + event);
}

@Override
public void onCreate() {
super.onCreate();
MyEventBus.getInstance().register(this);
new Thread(new Runnable() {
@Override
public void run() {
Utils.sleep(1000);
// 主进程发送事件
MyEventBus.getInstance().post("A message from main process at " + System.currentTimeMillis());
}
}).start();
}

@Override
public void onDestroy() {
MyEventBus.getInstance().unregister(this);
super.onDestroy();
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}

RemoteService

子进程的Service:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public class RemoteService extends Service {

private IEventInterface mEventInterface;

private IEventCallback mEventCallback = new IEventCallback.Stub() {
@Override
public void notifyEvent(Bundle event) throws RemoteException {
Log.d(TAG, "RemoteService notifyEvent: " + event);
// 收到主进程的转发后,将事件转发到本进程
MyEventBus.getInstance().postSingle(MyEventBus.getInstance().unPack(event));
}
};

@Override
public void onCreate() {
super.onCreate();
MyEventBus.getInstance().register(this);

bindService(new Intent(this, LocalService.class), new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "RemoteService onServiceConnected");
try {
mEventInterface = IEventInterface.Stub.asInterface(service);
mEventInterface.register(mEventCallback);
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "RemoteService onServiceConnected");
try {
mEventInterface.unregister(mEventCallback);
} catch (Exception e) {
e.printStackTrace();
}
}
}, BIND_AUTO_CREATE);

new Thread(new Runnable() {
@Override
public void run() {
Utils.sleep(2000);
MyEventBus.getInstance().post(100);
}
}).start();
}

// 桥梁:监听本进程发送的事件,并转发给主进程
@Subscribe
public void handle(EventWrapper wrapper) {
Log.v(TAG, "RemoteService handle: " + wrapper);
try {
mEventInterface.notify(wrapper.mBundle);
} catch (RemoteException e) {
e.printStackTrace();
}
}

// 主进程的事件订阅者
@Subscribe
public void onEvent(String event) {
Log.d(TAG, "RemoteService onEvent: " + event);
}

// 主进程的事件订阅者
@Subscribe
public void onEvent1(Integer event) {
Log.d(TAG, "RemoteService onEvent: " + event);
}

@Override
public void onDestroy() {
MyEventBus.getInstance().unregister(this);
super.onDestroy();
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

总结

通过一个AIDL文件实现通用的跨进程接口调用

EventBus工作流程

概述

Android系统自带一个轻量级的关系型数据库:SQLite,支持基本的CRUD,事务等操作。

SQLite

多进程

可以配合ContentProvider实现多进程读写SQLite的操作。

事务

概述
unlock(未加锁) 1. 初始状态未加锁,在此状态下,连接还没有存取数据库。当连接到了一个数据库,甚至已经用BEGIN开始了一个事务时,连接都还处于未加锁状态。
shared(共享锁) 2. 为了能够从数据库中读(不写)数据,连接必须首先进入共享状态,也就是说首先要获得一个共享锁。多个连接可以同时获得并保持共享锁,也就是说多个连接可以同时从同一个数据库中读数据但,哪怕只有一个共享锁还没有释放,也不允许任何连接写数据库
reserved(保留锁) 3.如果一个连接想要写数据库,它必须首先获得一个保留锁。一个数据库上同时只能有一个保留锁。保留锁可以与共享锁共存,保留锁是写数据库的第1阶段。保留锁既不阻止其它拥有共享锁的连接继续读数据库,也不阻止其它连接获得新的共享锁。一旦一个连接获得了保留锁,它就可以开始处理数据库修改操作了,尽管这些修改只能在缓冲区中进行,而不是实际地写到磁盘。对读出内容所做的修改保存在内存缓冲区中。
pending(未决锁) 4.当连接想要提交修改(或事务)时,需要将保留锁提升为排它锁。为了得到排它锁,还必须首先将保留锁提升为未决锁。获得未决锁之后,其它连接就不能再获得新的共享锁了,但已经拥有共享锁的连接仍然可以继续正常读数据库。此时,拥有未决锁的连接等待其它拥有共享锁的连接完成工作并释放其共享锁
exclusive(排它锁)

事务可以开始于:DEFERRED、MMEDIATE或EXCLUSIVE。事务类型在BEGIN命令中指定:

1
BEGIN [ DEFERRED | IMMEDIATE | EXCLUSIVE ] TRANSACTION;

一个DEFERRED事务不获取任何锁(直到它需要锁的时候),BEGIN语句本身也不会做什么事情——它开始于UNLOCK状态。默认情况下就 是这样的,如果仅仅用BEGIN开始一个事务,那么事务就是DEFERRED的,同时它不会获取任何锁;当对数据库进行第一次读操作时,它会获取 SHARED锁;同样,当进行第一次写操作时,它会获取RESERVED锁。

由BEGIN开始的IMMEDIATE事务会尝试获取RESERVED锁。如果成功,BEGIN IMMEDIATE保证没有别的连接可以写数据库。但是,别的连接可以对数据库进行读操作;但是,RESERVED锁会阻止其它连接的BEGIN IMMEDIATE或者BEGIN EXCLUSIVE命令,当其它连接执行上述命令时,会返回SQLITE_BUSY错误。这时你就可以对数据库进行修改操作了,但是你还不能提交,当你 COMMIT时,会返回SQLITE_BUSY错误,这意味着还有其它的读事务没有完成,得等它们执行完后才能提交事务。

EXCLUSIVE事务会试着获取对数据库的EXCLUSIVE锁。这与IMMEDIATE类似,但是一旦成功,EXCLUSIVE事务保证没有其它的连接,所以就可对数据库进行读写操作了。

死锁:

两个连接都在死锁中结束。B首先尝试写数据库,也就拥有了一个未决锁。A再试图写,但当其INSERT语句试图将共享锁提升为保留锁时失败。

为了讨论的方便,假设连接A和B都一直等待数据库可写。那么此时,其它的连接甚至都不能够再读数据库了,因为B拥有未决锁(它能阻止其它连接获得共享锁)。那么时此,不仅A和B不能工作,其它所有进程都不能再操作此数据库了。

这个例子的问题在于两个连接最终都想写数据库,但是它们都没有放弃各自原来的锁,最终,SHARED锁导致了问题的出现。如果两个连接都以 BEGIN IMMEDIATE开始事务,那么死锁就不会发生。在这种情况下,在同一时刻只能有一个连接进入BEGIN IMMEDIATE,其它的连接就得等待。BEGIN IMMEDIATE和BEGIN EXCLUSIVE通常被写事务使用。就像同步机制一样,它防止了死锁的产生。

基本的准则是:如果你正在使用的数据库没有其它的连接,用BEGIN就足够了。但是,如果你使用的数据库有其它的连接也会对数据库进行写操作,就得使用BEGIN IMMEDIATE或BEGIN EXCLUSIVE开始你的事务。

Android中的事物,有2种,一个是IMMEDIATE,一个是EXCLUSIVE,默认SQLiteDatabase的beginTransaction()是EXCLUSIVE事物,beginTransactionNonExclusive()是IMMEDIATE事务。

SQLiteOpenHelper

概述

SQLiteOpenHelper是一个数据库辅助操作的类,在Android中实现数据库的增删查改以及升级等的控制,在开发中通常会实现一个继承SQLiteOpenHelper的操作类,然后根据业务需求实现数据库的相关操作方法。

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class MyDatabaseHelper extends SQLiteOpenHelper {

public static final String CREATE_BOOK = "create table book (" +
"id integer primary key autoincrement, " +
"author text, " +
"price real," +
"pages integer, " +
"name text)";

private Context mContext;
private SQLiteDatabase mWritableDatabase = getWritableDatabase();
private SQLiteDatabase mReadableDatabase = getReadableDatabase();

public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}

/**
* 数据库已经创建过了,则不会执行,如果不存在数据库则会执行
* @param db
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK); // 执行这句才会创建表

Toast.makeText(mContext, "create succeeded", Toast.LENGTH_SHORT).show();
}

/**
* 创建数据库时不会执行,增大版本号升级时才会执行到
* @param db
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Toast.makeText(mContext, "onUpgrade oldVersion:" + oldVersion + " newVersion:" + newVersion, Toast.LENGTH_SHORT).show();
}

// 根据业务需求编写增删查改等方法
public int insert(Bean bean) {
// mWritableDatabase.insert(String table, String nullColumnHack, ContentValues values)
return 0;
}
}

GreenDAO

概述

GreenDAO 是一款开源的面向 Android 的轻便、快捷的 ORM 框架,它针对 Android 进行了高度优化,使用了最小的内存开销、且依赖体积小,同时还是支持数据库加密。

核心类

DaoMaster

greenDAO 的入口,DaoMaster 负责管理数据库对象(SQLiteDatabase)和 DAO 类(对象),我们可以通过它的内部类 OpenHelper 和 DevOpenHelper 创建不同模式的 SQLite 数据库。

DaoSession

管理指定模式下的所有 DAO 对象,DaoSession提供了一些通用的持久性方法比如插入、负载、更新、更新和删除实体。

XxxDAO

对于每个实体类,greenDAO 会生成一个与之对应DAO对象。

Entity

可持久化对象。通常,实体对象代表一个数据库行使用标准 Java 属性(如一个POJO 或 JavaBean)。

注解

@Entity

表明这个实体类会在数据库中生成一个与之相对应的表,属性如下:

  • schema:告知GreenDao当前实体属于哪个schema
  • schema active:标记一个实体处于活跃状态,活动实体有更新、删除和刷新方法
  • nameInDb:在数据库中使用的别名,默认使用的是实体的类名
  • indexes:定义索引,可以跨越多个列
  • createInDb:标记创建数据库表(默认:true)
  • generateConstructors 自动创建全参构造方法(同时会生成一个无参构造方法)(默认:true)
  • generateGettersSetters 自动生成 getters and setters 方法(默认:true)

实例如下:

1
2
3
4
5
6
7
8
9
10
11
@Entity(
schema = "myschema",
active = true,
nameInDb = "AWESOME_USERS",
indexes = {
@Index(value = "name DESC", unique = true)
},
createInDb = true,
generateConstructors = false,
generateGettersSetters = true
)

@Id

对应数据表中的 Id 字段。

@Index

使用@Index作为一个属性来创建一个索引,默认是使用字段名。

1
2
3
4
5
6
7
8
@Entity
public class User {
@Id
private Long id;

@Index(unique = true)
private String name;
}

@Property

设置一个非默认关系映射所对应的列名,默认是使用字段名,例如:@Property(nameInDb = “userName”)。

@NotNull

设置数据库表当前列不能为空。

@Transient

添加此标记后不会生成数据库表的列。

@Unique

表名该属性在数据库中只能有唯一值。

1
2
3
4
5
6
7
@Entity
public class User {
@Id
private Long id;
@Unique
private String name;
}

@ToOne

表示一对一关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Entity
public class Order {

@Id private Long id;

private long customerId;

@ToOne(joinProperty = "customerId")
private Customer customer;
}

@Entity
public class Customer {
@Id
private Long id;
}

@OrderBy

更加某一字段排序,例如:@OrderBy(“date ASC”)。

@ToMany

定义一对多个实体对象的关系。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity
public class Customer {
@Id private Long id;

@ToMany(referencedJoinProperty = "customerId")
@OrderBy("date ASC")
private List<Order> orders;
}

@Entity
public class Order {
@Id private Long id;
private Date date;
private long customerId;
}

集成GreenDAO

配置仓库/插件

在Project下的build.gradle中配置:

1
2
3
4
5
6
7
8
9
10
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.3'
classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'
}
}

添加依赖

在Module的build.gradle中配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
apply plugin: 'org.greenrobot.greendao'

greendao {
//数据库版本
schemaVersion 1
daoPackage 'com.hearing.dao.gen'
targetGenDir 'src/main/java'
}

dependencies {
compile 'org.greenrobot:greendao:3.2.2'
compile 'org.greenrobot:greendao-generator:3.2.2'
}

Entity

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
@Entity
public class ContractVideo {
@Transient
private static final long serialVersionUID = 1404519421023545593L;

@Id(autoincrement = true)
private Long id;
private String number;
private String videoPath;

@Generated(hash = 1570774131)
public ContractVideo() {
}

@Generated(hash = 776974923)
public ContractVideo(Long id, String number, String videoPath) {
this.id = id;
this.number = number;
this.videoPath = videoPath;
}

public Long getId() {
return id;
}

public void setId(Long id) {
id = id;
}

public String getNumber() {
return number;
}

public void setNumber(String number) {
number = number;
}

public String getVideoPath() {
return videoPath;
}

public void setVideoPath(String videoPath) {
videoPath = videoPath;
}

public Long getId() {
return this.id;
}

public void setId(Long id) {
this.id = id;
}

public String getNumber() {
return this.number;
}

public void setNumber(String number) {
this.number = number;
}

public String getVideoPath() {
return this.videoPath;
}

public void setVideoPath(String videoPath) {
this.videoPath = videoPath;
}
}

在build工程后,会在指定包下生成管理类DaoMaster,DaoSession,BeanDao。

初始化GreenDAO

初始化通常在Application下进行,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class CallApplication extends Application {

private static final String DB_NAME = "call.db";

private DaoSession mDaoSession;

@Override
public void onCreate() {
super.onCreate();
initDao();
}

private void initDao() {
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, DB_NAME);
SQLiteDatabase db = helper.getWritableDatabase();
DaoMaster daoMaster = new DaoMaster(db);
mDaoSession = daoMaster.newSession();
}

public DaoSession getDaoSession() {
return mDaoSession;
}
}

获取BeanDao

1
2
3
CallApplication myApp = (CallApplication) getApplication();
DaoSession daoSession = myApp.getDaoSession();
ContractVideoDao contractVideoDao = daoSession.getContractVideoDao();

增删查改

通过操作ContractVideoDao来进行记录的增删查改,其具体接口可参考ContractVideoDao类中的方法。

概述

IPC(IPC,InterProcess Communication):在Android系统中一个应用至少有一个进程,每个进程都有自己独立的资源和内存空间,其它进程不能任意访问当前进程的内存和资源。如果进程之间想要进行通信,则需要使用IPC手段。

每个Android的进程,只能运行在自己进程所拥有的虚拟地址空间。对于用户空间,不同进程之间彼此是不能共享的,而内核空间却是可共享的。Client进程向Server进程通信,恰恰是利用进程间可共享的内核内存空间来完成底层通信工作的,Client端与Server端进程往往采用ioctl等方法跟内核空间的驱动进行交互。

Parcelable

Android中实现序列化有两个选择:一是实现Serializable接口(是JavaSE本身就支持的),一是实现Parcelable接口(是Android特有功能,效率比实现Serializable接口高效,可用于Intent数据传递,也可以用于进程间通信(IPC))。实现Serializable接口非常简单,声明一下就可以了,而实现Parcelable接口稍微复杂一些,但效率更高,推荐用这种方法提高性能。

Android中Intent传递对象有两种方法:一是Bundle.putSerializable(Key,Object),另一种是Bundle.putParcelable(Key,Object)。当然这些Object是有一定的条件的,前者是实现了Serializable接口,而后者是实现了Parcelable接口。

  1. 在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。
  2. Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
  3. Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的持续性在外界有变化的情况下。尽管Serializable效率低点,但此时还是建议使用Serializable 。

步骤:

  1. implements Parcelable
  2. 重写writeToParcel方法,将你的对象序列化为一个Parcel对象,即:将类的数据写入外部提供的Parcel中,打包需要传递的数据到Parcel容器保存,以便从 Parcel容器获取数据
  3. 重写describeContents方法,内容接口描述,默认返回0就可以
  4. 实例化静态内部对象CREATOR实现接口Parcelable.Creator
1
public static final Parcelable.Creator<T> CREATOR

注:其中public static final一个都不能少,内部对象CREATOR的名称也不能改变,必须全部大写。需重写本接口中的两个方法:createFromParcel(Parcel in) 实现从Parcel容器中读取传递数据值,封装成Parcelable对象返回逻辑层,newArray(int size) 创建一个类型为T,长度为size的数组,仅一句话即可(return new T[size]),供外部类反序列化本类数组使用。

简而言之:通过writeToParcel将你的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射成你的对象。也可以将Parcel看成是一个流,通过writeToParcel把对象写到流里面,在通过createFromParcel从流里读取对象,只不过这个过程需要你来实现,因此写的顺序和读的顺序必须一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
public class Person implements Serializable {
private static final long serialVersionUID = -7060210544600464481L;
private String name;
private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

public class Book implements Parcelable {
private String bookName;
private String author;
private int publishDate;

public Book() {

}

public String getBookName() {
return bookName;
}

public void setBookName(String bookName) {
this.bookName = bookName;
}

public String getAuthor() {
return author;
}

public void setAuthor(String author) {
this.author = author;
}

public int getPublishDate() {
return publishDate;
}

public void setPublishDate(int publishDate) {
this.publishDate = publishDate;
}

@Override
public int describeContents() {
return 0;
}

@Override
public void writeToParcel(Parcel out, int flags) {
out.writeString(bookName);
out.writeString(author);
out.writeInt(publishDate);
}

public static final Parcelable.Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book[] newArray(int size) {
return new Book[size];
}

@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
};

public Book(Parcel in) {
bookName = in.readString();
author = in.readString();
publishDate = in.readInt();
}
}

Bundle

Bundle 实现了 parcelable 接口。基于这一点,当启动另一个进程时,可使用 Bundle 传输能够被序列化的数据。如 Activity,BroadcastReceiver。

文件共享

文件共享适合在对数据同步要求不高的进程间进行通信。从本质上说,SharedPreference 也是文件,但由于系统对它的读写有一定的缓存策略,因此不建议在多进程间使用 SharedPreference。

ContentProvider

BroadcastReceiver

Socket

Binder

Android-Binder实战

Messenger

概述

Messenger 是一种轻量级的 IPC 方案,它的底层实现是 AIDL。 因为它对 AIDL 做了封装,所以使用起来非常简单,就类似于我们绑定一个 Service 一样。同时,由于它一次处理一个请求,所以在服务端我们不用考虑线程同步的问题,因为在服务端中不存在并发执行的情况。Messenger 和 Message 都实现了 Parcelable 接口,因此可以跨进程传输。简单来说, Message 中所支持的数据类型就是 Messenger 所支持的传输类型。

Messenger 比较适用于低并发的一对多的通信。

构造函数:

1
2
3
4
5
6
7
8
9
private final IMessenger mTarget;

public Messenger(Handler target) {
mTarget = target.getIMessenger();
}

public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}

服务端进程:

  1. 创建一个 Service 来处理客户端的连接请求;
  2. 创建一个 Handler 并通过它来创建一个 Messenger 对象;
  3. 在 Service 的 onBind 函数中返回这个 Messenger 对象底层的 Binder ;

客户端进程:

  1. 绑定服务端的Service;
  2. 绑定成功后用服务端返回的 IBinder 对象创建一个 Messenger;
  3. 通过这个 Messenger 向服务端发送消息,消息类型为 Message 对象;
  4. 创建一个 Handler 并创建一个新的 Messenger 对象;
  5. 将这个新的 Messenger 对象通过 Message 的 replyTo 参数传递给服务端,服务端就可以通过这个 replyTo 参数回应客户端了;

实例

客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class MainActivity extends AppCompatActivity {

private Messenger messenger;

private static class MessengerHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.i("LLL", "client received: " + msg.getData().getString("reply"));
break;
}
}
}

private Messenger replyMessenger = new Messenger(new MessengerHandler());

private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Messenger messenger = new Messenger(iBinder);
//创建完成Messenger后就可以通过Messenger 来发送 Message 了
Message message = Message.obtain();
message.what = 0;
Bundle bundle = new Bundle();
bundle.putString("client", "this is message from client.");
message.setData(bundle);
message.replyTo = replyMessenger;

try {
messenger.send(message);
} catch (RemoteException e) {
e.printStackTrace();
}
}

@Override
public void onServiceDisconnected(ComponentName componentName) {

}
};

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Intent intent = new Intent(this, MessengerService.class);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}

@Override
protected void onDestroy() {
super.onDestroy();
unbindService(serviceConnection);
}
}

服务端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class MessengerService extends Service {

private static class MessengerHandle extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 0:
Log.i("LLL", "server received: " + msg.getData().getString("client"));

Messenger replyMessenger = msg.replyTo;
Message replyMessage = Message.obtain();
Bundle bundle = new Bundle();
bundle.putString("reply", "serve has received msg.");
replyMessage.what = 1;
replyMessage.setData(bundle);
try {
replyMessenger.send(replyMessage);
} catch (RemoteException e) {
e.printStackTrace();
}
break;
}
}
}

//此Messenger将客户端发送的消息传递给 MessengerHandler
private Messenger messenger = new Messenger(new MessengerHandle());

@Nullable
@Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
}

Service注册:

1
2
<service android:name=".MessengerService"
android:process=":remote"/>

源码

IMessenger.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
public interface IMessenger extends android.os.IInterface {
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements android.os.IMessenger {

private static final java.lang.String DESCRIPTOR = "android.os.IMessenger";

/** Construct the stub at attach it to the interface. */
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}

/**
* Cast an IBinder object into an android.os.IMessenger interface,
* generating a proxy if needed.
*/
public static android.os.IMessenger asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = (android.os.IInterface) obj
.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof android.os.IMessenger))) {
return ((android.os.IMessenger) iin);
}
return new android.os.IMessenger.Stub.Proxy(obj);
}

public android.os.IBinder asBinder() {
return this;
}

@Override
public boolean onTransact(int code, android.os.Parcel data,
android.os.Parcel reply, int flags)
throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_send: { // 接收的code和发送的code都是TRANSACTION_send
data.enforceInterface(DESCRIPTOR);
android.os.Message _arg0;
if ((0 != data.readInt())) { // data.readInt为1(当msg不为空时)
_arg0 = android.os.Message.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.send(_arg0);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}

private static class Proxy implements android.os.IMessenger {
private android.os.IBinder mRemote;

Proxy(android.os.IBinder remote) {
mRemote = remote;
}

public android.os.IBinder asBinder() {
return mRemote;
}

public java.lang.String getInterfaceDescriptor() {
return DESCRIPTOR;
}

public void send(android.os.Message msg)
throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((msg != null)) {
_data.writeInt(1);
msg.writeToParcel(_data, 0);// 将msg所有字段(what,obj)写到_data中去
} else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_send, _data, null, // // 接收的code和发送的code都是TRANSACTION_send
android.os.IBinder.FLAG_ONEWAY);
} finally {
_data.recycle();
}
}
}

static final int TRANSACTION_send = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}

public void send(android.os.Message msg) throws android.os.RemoteException;
}

Messenger.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
public final class Messenger implements Parcelable {
private final IMessenger mTarget;

/**
* Create a new Messenger pointing to the given Handler. Any Message
* objects sent through this Messenger will appear in the Handler as if
* {@link Handler#sendMessage(Message) Handler.sendMessage(Message)} had
* been called directly.
*
* @param target The Handler that will receive sent messages.
*/
public Messenger(Handler target) {
mTarget = target.getIMessenger();
}

/**
* Send a Message to this Messenger's Handler.
*
* @param message The Message to send. Usually retrieved through
* {@link Message#obtain() Message.obtain()}.
*
* @throws RemoteException Throws DeadObjectException if the target
* Handler no longer exists.
*/
public void send(Message message) throws RemoteException {
mTarget.send(message);
}

/**
* Retrieve the IBinder that this Messenger is using to communicate with
* its associated Handler.
*
* @return Returns the IBinder backing this Messenger.
*/
public IBinder getBinder() {
return mTarget.asBinder();
}

/**
* Comparison operator on two Messenger objects, such that true
* is returned then they both point to the same Handler.
*/
public boolean equals(Object otherObj) {
if (otherObj == null) {
return false;
}
try {
return mTarget.asBinder().equals(((Messenger)otherObj)
.mTarget.asBinder());
} catch (ClassCastException e) {
}
return false;
}

public int hashCode() {
return mTarget.asBinder().hashCode();
}

public int describeContents() {
return 0;
}

public void writeToParcel(Parcel out, int flags) {
out.writeStrongBinder(mTarget.asBinder());
}

public static final Parcelable.Creator<Messenger> CREATOR
= new Parcelable.Creator<Messenger>() {
public Messenger createFromParcel(Parcel in) {
IBinder target = in.readStrongBinder();
return target != null ? new Messenger(target) : null;
}

public Messenger[] newArray(int size) {
return new Messenger[size];
}
};

/**
* Convenience function for writing either a Messenger or null pointer to
* a Parcel. You must use this with {@link #readMessengerOrNullFromParcel}
* for later reading it.
*
* @param messenger The Messenger to write, or null.
* @param out Where to write the Messenger.
*/
public static void writeMessengerOrNullToParcel(Messenger messenger,
Parcel out) {
out.writeStrongBinder(messenger != null ? messenger.mTarget.asBinder()
: null);
}

/**
* Convenience function for reading either a Messenger or null pointer from
* a Parcel. You must have previously written the Messenger with
* {@link #writeMessengerOrNullToParcel}.
*
* @param in The Parcel containing the written Messenger.
*
* @return Returns the Messenger read from the Parcel, or null if null had
* been written.
*/
public static Messenger readMessengerOrNullFromParcel(Parcel in) {
IBinder b = in.readStrongBinder();
return b != null ? new Messenger(b) : null;
}

/**
* Create a Messenger from a raw IBinder, which had previously been
* retrieved with {@link #getBinder}.
*
* @param target The IBinder this Messenger should communicate with.
*/
public Messenger(IBinder target) {
mTarget = IMessenger.Stub.asInterface(target);
}
}

ContentProvider&Binder

可以使用ContentProvider结合AIDL的方式实现两个进程之间的通信,这种方式不需要通过bindService来建立两个进程之间的ServiceConnection。

MatrixCursor

在介绍上面的方式之前,可以先看一下 MatrixCursor 这个 Cursor 对象。

在一些使用 Cursor 的场景里,如果想得到一个 Cursor 对象,但又没有数据库返回一个 Cursor,此时可以通过 MatrixCursor 来返回一个伪造的 Cursor。比如一个程序在一般情况下用 context.getContentResolver().query() 从 ContentProvider 中查询数据,但是在一些特殊的场景里,需要返回的只有几条固定的已知记录,不需要从数据库查询,此时可以使用 MatrixCursor 来根据这些已知的记录构造一个 Cursor。

MatrixCursor 的用法如下:

  1. 首先创建一个字符数组,字符数组的值对应着表的字段:

    1
    val COLUMN_NAME = arrayOf("_id", "name", "age")
  2. 利用MatrixCursor的构造方法,构造一个MatrixCursor,传入的参数即是步骤1中创建的字段数组:

    1
    matrixCursor = MatrixCursor(COLUMN_NAME)
  3. 通过matrixCursor的addRow方法添加一行值,相当于向数据库中插入一条记录:

    1
    2
    3
    matrixCursor?.addRow(arrayOf<Any>(1, "hearing", 24))
    // 也可以通过构造一个MatrixCursor.RowBuilder来实现
    matrixCursor?.newRow()?.add(2)?.add("hhh")?.add(22)

通过上面三步即可完成 MatrixCursor 的构造,从 MatrixCursor 中取出数据的过程与 Cursor 相同。

接下来通过一个实例来看看怎么具体地借助 ContentProvider 和 Binder 来进行便捷的跨进程通信。

Server进程

服务端接口定义

在服务端进程或者服务端 App 中,定义 AIDL 接口文件:

1
2
3
interface IMyAidlInterface {
int getCount();
}

make 之后实现接口:

1
2
3
4
5
6
class CountServiceImpl : IMyAidlInterface.Stub() {
override fun getCount(): Int {
println("Server: getCount")
return 100
}
}

ContentProvider定义

接着注册并定义一个运行在服务端进程的 ContentProvider 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class BinderProvider : ContentProvider() {
override fun onCreate(): Boolean = true

override fun query(
uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?
): Cursor {
return BinderCursor.binderCursor
}

override fun getType(uri: Uri): String? = null

override fun insert(uri: Uri, values: ContentValues?): Uri? = null

override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0

override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int = 0
}

这里 query 方法中返回的 Cursor 对象是一个 BinderCursor.binderCursor, BinderCursor 继承自 MatrixCursor 类,其内保存有服务端 Binder 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class BinderCursor private constructor() : MatrixCursor(arrayOf("service")) {
companion object {
private const val KEY_BINDER_COUNT = "key_binder_count"
val binderCursor = BinderCursor()

fun getCountService(cursor: Cursor?): IBinder? {
return cursor?.extras?.getBinder(KEY_BINDER_COUNT)
}
}

private val mBinderExtra = Bundle()

init {
mBinderExtra.putBinder(KEY_BINDER_COUNT, CountServiceImpl())
}

override fun getExtras(): Bundle {
return mBinderExtra
}
}

然后在 Manifest 中注册:

1
2
3
4
5
6
<provider
android:name=".server.BinderProvider"
android:authorities="com.hearing.binder.demo"
android:exported="true"
android:grantUriPermissions="true"
android:process=":provider" />

Client进程

客户端进程通过以下方法可以获取到 Binder 代理对象,并远程调用服务端接口:

1
2
3
4
5
6
7
8
9
val cursor = contentResolver.query(Uri.parse("content://com.hearing.binder.demo"), null, null, null, null)
try {
val service = IMyAidlInterface.Stub.asInterface(BinderCursor.getCountService(cursor))
println("Client: ${service?.count}")
} catch (e: Exception) {
e.printStackTrace()
} finally {
cursor?.close()
}

总结

通过一个AIDL文件实现通用的跨进程接口调用

ThreadLocal

ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,而且只有在指定的线程里才能获取到存储的数据,对于其他线程来说是获取不到的。ThreadLocal适用于变量在线程间隔离而在方法或类间共享的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
static class ThreadLocalMap {
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;

Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}

ThreadLocalMap 维护了每个 ThreadLocal 与具体实例的映射,它的每个 Entry 都是对键的弱引用,防止内存泄漏。看下其工作流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Thread.java
ThreadLocal.ThreadLocalMap threadLocals = null;

// ThreadLocal.java
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}

ThreadLocalMap是 Thread 类的一个字段,即由 Thread 维护 ThreadLocal 对象与具体实例的映射。读取实例时,线程首先通过getMap(t)方法获取自身的 ThreadLocalMap。获取到 ThreadLocalMap 后,通过map.getEntry(this)方法获取该 ThreadLocal 在当前线程的 ThreadLocalMap 中对应的 Entry。

对于已经不再被使用且已被回收的 ThreadLocal 对象,它在每个线程内对应的实例由于被线程的 ThreadLocalMap 的 Entry 强引用,无法被回收,可能会造成内存泄漏

针对该问题,在 ThreadLocalMap 的 set 方法中会将所有键为 null 的 Entry 的值设置为 null,从而使得该值可被回收。接着会将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收。通过这种方式,ThreadLocal 可防止内存泄漏。

MessageQueue

概述

MessageQueue是消息队列,主要包含两个操作:插入和读取,而读取也包含了删除操作,即每读取一个消息就会从MessageQueue中删除掉这个已读的消息。MessageQueue的实现是通过单链表的数据结构来维护消息列表的(当前对象保留下一个对象的地址引用)。

Message

1
2
3
4
5
6
7
8
9
10
11
12
13
public final class Message implements Parcelable {
public int what;
public Object obj;
long when;
Handler target;
Message next;
Runnable callback;

// 设置在Barrier时是否仍可以不受Barrier的影响被正常处理,如果没有设置Barrier,异步消息就与同步消息没有区别
public void setAsynchronous(boolean async) {
// ...
}
}

Message分类:

  • 同步消息:正常情况下通过Handler发送的Message都属于同步消息,除非在发送的时候指定其是一个异步消息,同步消息会按顺序排列在队列中。
  • 异步消息:一般情况下与同步消息没有区别,只有在设置了同步屏障(barrier)时才有所不同。
  • 屏障消息(Barrier):屏障(Barrier)是一种特殊的Message,它的target为null(只有屏障的target可以为null),并且arg1属性被用作屏障的标识符来区别不同的屏障。屏障的作用是用于拦截队列中同步消息,放行异步消息。

同步异步消息类型由 isAsynchronous 方法得到,通过 MessageQueue.postSyncBarrier() 方法可以发送一个Barrier。当发现遇到barrier后,队列中后续的同步消息会被阻塞,而异步消息则不受barrier的影响,直到通过调用MessageQueue.removeSyncBarrier()释放了指定的barrier。屏障和普通消息一样可以根据时间来插入到消息队列中的适当位置,并且只会挡住它后面的同步消息的分发。插入普通消息会唤醒消息队列,但是插入屏障不会。

相关源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// MessageQueue.java
public int postSyncBarrier() {
return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
synchronized (this) {
final int token = mNextBarrierToken++;
// 没有为tartget赋值
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;

Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
mMessages = msg;
}
return token;
}
}

public void removeSyncBarrier(int token) {
synchronized (this) {
Message prev = null;
Message p = mMessages;
while (p != null && (p.target != null || p.arg1 != token)) {
prev = p;
p = p.next;
}
if (p == null) {
throw new IllegalStateException("The specified message queue synchronization "
+ " barrier token has not been posted or has already been removed.");
}
final boolean needWake;
if (prev != null) {
prev.next = p.next;
needWake = false;
} else {
mMessages = p.next;
needWake = mMessages == null || mMessages.target != null;
}
p.recycleUnchecked();

// If the loop is quitting then it is already awake.
// We can assume mPtr != 0 when mQuitting is false.
if (needWake && !mQuitting) {
nativeWake(mPtr);
}
}
}

一般情况下,app会频繁的大量使用Message对象,如果每次都去new一个新对象出来使用,用后再通过GC进行垃圾回收,那很可能会导致频繁的gc,影响程序的性能。因此,android提供了一个Message对象池,当开发者需要一个Message对象时,调用Message.obtain()从这个池子里取一个对象出来使用,这样做可以优化一定的性能,使用的是享元模式。

IdleHandler

IdleHandler可以用来提升性能,主要用在我们希望能够在当前线程消息队列空闲时做些事情(UI线程在显示完成后,如果线程空闲我们就可以提前准备其他内容)的情况下,不过最好不要做耗时操作。具体用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//getMainLooper().myQueue()或者Looper.myQueue()
Looper.myQueue().addIdleHandler(new IdleHandler() {
@Override
public boolean queueIdle() {
// 要处理的事情
// 返回false表示执行过一次后会移除
// 返回true表示一直keep不移除,每次消息队列空闲后都会执行
return false;
}
});

// MessageQueue.java
public void addIdleHandler(@NonNull IdleHandler handler) {
if (handler == null) {
throw new NullPointerException("Can't add a null IdleHandler");
}
synchronized (this) {
mIdleHandlers.add(handler);
}
}

public void removeIdleHandler(@NonNull IdleHandler handler) {
synchronized (this) {
mIdleHandlers.remove(handler);
}
}

enqueueMessage入队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}

synchronized (this) {
if (mQuitting) {
msg.recycle();
return false;
}

msg.markInUse(); // 标记为in use
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// 将队列中的Message按照处理时间先后插入
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}

// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

enqueueMessage方法会根据唤醒时间的先后顺序插入,在插入成功返回true,否则返回false。nativeWake 方法会唤醒CPU。

next取下条消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}

int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
// 本地方法, 释放CPU资源,-1表示一直等待
nativePollOnce(ptr, nextPollTimeoutMillis);

synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// msg.target == null就是同步屏障消息,那么只取异步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// 下一条消息尚未ready,设置timeout以在ready后唤醒
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}

// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}

// 处理IdleHandler的逻辑
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}

if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}

// 循环遍历所有IdleHandler
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler

boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
}
// 如果IdleHandler接口的queueIdle方法返回false说明只执行一次需要删除
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}

// Reset the idle handler count to 0 so we do not run them again.
pendingIdleHandlerCount = 0;
// 在处理IdleHandler的时候,一个新的Message可能已经下发了,因此不等待直接循环查找
nextPollTimeoutMillis = 0;
}
}

在next方法中会死循环查找满足条件的Message,找到则返回,若已经退出则返回null。总结一下next方法做的几件事情:

  • 根据delay来指定timeout,唤醒CPU;
  • 优先处理异步消息,然后处理达到timeout的同步消息;
  • 最后如果队列空闲则处理IdleHandler逻辑。

Looper

概述

Looper在消息机制里扮演着消息循环的角色,它不停的从消息队列里查看是否有新消息,如果有,则立即处理新消息,没有则一直阻塞在那里。当需要为一个线程创建一个Looper的时候,需要调用Looper的两个静态方法就可以给这个线程创建一个Looper对象了,这两个方法是Looper.prepare()Looper.loop()

prepare

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
new Thread(){
@override
public void run(){
Looper.prepare();
Handler handler = new Handler();
Looper.loop();
}
}.start();

// 把Looper对象保存到ThreadLocal里面
public static void prepare() {
prepare(true);
}

private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}

private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}

Looper.prepare()方法创建了一个Looper实例,并通过ThreadLocal将其保存到当前线程中,一个线程只允许创建一个Looper实例,即一个线程中Looper.prepare()方法只能被调用一次。

loop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public static void loop() {
final Looper me = myLooper();
// 要先调用Looper.prepare()方法
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// ...

for (;;) {
Message msg = queue.next(); // 可能阻塞
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}

try {
msg.target.dispatchMessage(msg);
} finally {
}
// ...
msg.recycleUnchecked();
}
}

Looper.loop()方法首先是获取到当前线程的Looper对象和绑定到Looper对象的这个消息队列,接着就是一个死循环了。当消息队列没有消息时,则等待消息;当有消息时,则把这个消息通过Handler(msg.target是一个Handler对象)的dispatchMessage方法来处理这个消息,这也就到了Handler里面去执行了,这样就成功地将代码逻辑切换到指定的线程中去了。

quit

1
2
3
4
5
6
7
public void quit() {
mQueue.quit(false);
}

public void quitSafely() {
mQueue.quit(true);
}

如果当这个loop执行到一半的时候要退出怎么办呢?Looper有一个quit方法,此方法被调用时,Looper会调用MessageQueue的quit或者quitSafely方法来通知消息队列退出,并标识为退出状态,当消息队列被标记为退出状态时,next方法就会返回null,这样loop里面的死循环就跳出去了。

总结

  1. 当Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send方法会返回false;
  2. 在子线程中,如果手动为其创建了Looper,那么在所有消息处理完成之后应该调用quit方法来终止消息循环,否则这个子线程就会一直处于等待状态,而如果退出Looper以后,这个线程就会立刻终止,因此建议不需要的时候终止Looper;
  3. Looper.loop()方法应该置于run方法的最后,不然之后的代码不会运行。除非调用了looper的quit方法;
  4. 主线程的Looper.loop()方法可能会引起主线程的阻塞,但只要它的消息循环没有被阻塞,能一直处理事件就不会产生ANR异常。因为Android是由事件驱动的,looper.loop()不断地接收事件、处理事件,每一个点击触摸或者说Activity的生命周期都是运行在Looper.loop()的控制之下,如果它停止了,应用也就停止了。只能是某一个消息或者说对消息的处理阻塞了Looper.loop(),而不是Looper.loop()阻塞它。

Handler

概述

Handler主要包括消息的发送和接收,其实是相当于Handler给自己发送了一条消息,只是消息经过了消息队列以及Looper,最后才到Handler的handleMessage方法里。

构造函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async; // mAsynchronous用来控制Barrier时是否仍可以不受Barrier的影响被正常处理,如果没有设置Barrier,异步消息就与同步消息没有区别
}

public Handler(Callback callback, boolean async) {
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException("Can't create handler inside thread " +
Thread.currentThread() + " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}

send

Handler的消息的发送主要由post系列方法以及send的系列方法来实现,post的方法最终都是通过send的方法来实现的。而send系列方法最后都是通过sendMessageAtTime方法来实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
// ...
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

发送消息其实就是往消息队列里面插入一个消息,返回的boolean值表示插入是否成功。

当Handler把消息插入到消息队列里后,MessageQueue就通过next方法把这个消息返回给Looper,而Looper则通过loop方法调用Handler的dispatchMessage方法。

dispatchMessage

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}

private static void handleCallback(Message message) {
message.callback.run();
}
  • 首先就是判断Message对象是否有callback对象,如果有,则调用callback的run方法,callback是通过Handler的post方法所传递的。
  • 如果Message对象没有callback对象,则跑到else代码块里,mCallback的handlerMessage方法和后面的那个handlerMessage方法其实是一样的,第一个是通过实现mCallback接口来得到的一个方法,而第二个是Handler的方法,两个方法都是空实现,当处理消息时都要重写这个两个方法中的一个,只是创建Handler时的构造方法不一样而已。

从上可以看出 Handler.Callback 有优先处理消息的权利,当某条消息被 Callback 拦截后(即返回true),则不会调用 Handler.handleMessage(msg) 方法。由此可以通过给 Handler 设置 mCallback 对象(可以通过反射等手段)来达到拦截该 Handler 对象里的消息的目的。如处理 SharedPreferences apply ANR 的时候便用到了这个方法。

扩展

Activity.runOnUiThread(Runnable)

1
2
3
4
5
6
7
8
9
final Handler mHandler = new Handler();

public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}

AsyncTask

AsyncTask是Android提供的一个处理异步任务的类,通过此类,可以实现UI线程和后台线程进行通讯,后台线程执行异步任务,并把结果返回给UI线程。

1
public abstract class AsyncTask<Params, Progress, Result> {}

泛型参数:

  • Params: 启动任务时输入的参数类型。
  • Progress: 后台任务执行中返回进度值的类型。
  • Result: 后台任务执行完成后返回结果的类型。

回调方法:

  • doInBackground: 必须重写,异步执行后台线程要完成的任务,耗时操作将在此方法中完成。(后台线程)
  • onPreExecute: 执行后台耗时操作前被调用,通常用于进行初始化操作。(UI线程)
  • onPostExecute: 当doInBackground方法完成后,系统将自动调用此方法,并将doInBackground方法返回的值传入此方法。通过此方法进行UI的更新。(UI线程)
  • onProgressUpdate: 当在doInBackground方法中调用publishProgress方法更新任务执行进度后,将调用此方法。通过此方法我们可以知晓任务的完成进度。(UI线程)

HandlerThread

更新UI时是子线程与UI主线程之间的通信,而子线程与子线程之间的通信需要用Handler+Thread来完成(需要自己操作Looper),Google官方提供了HandlerThread类,它是Thread的子类,用来在工作线程中执行任务,如耗时任务等,同时也有异步通信,消息传递的功能。

使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
HandlerThread mHandlerThread = new HandlerThread("HandlerThread");
handlerThread.start();

final Handler handler = new Handler(mHandlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
System.out.println("收到消息");
}
};

@Override
protected void onDestroy() {
super.onDestroy();
mHandlerThread.quit();
}

// 在另一个线程给HandlerThread发送消息
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000); // 模拟耗时操作
handler.sendEmptyMessage(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();

HandlerThread类源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;

public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}

public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}

protected void onLooperPrepared() {
}

@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}

public Looper getLooper() {
if (!isAlive()) {
return null;
}

// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}

@NonNull
public Handler getThreadHandler() {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}

public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}

public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}

public int getThreadId() {
return mTid;
}
}

Handler.runWithScissors

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 同步阻塞运行
public final boolean runWithScissors(final Runnable r, long timeout) {
if (r == null) {
throw new IllegalArgumentException("runnable must not be null");
}
if (timeout < 0) {
throw new IllegalArgumentException("timeout must be non-negative");
}
// 当前线程跟当前Handler都指向同一个Looper则直接运行
if (Looper.myLooper() == mLooper) {
r.run();
return true;
}

BlockingRunnable br = new BlockingRunnable(r);
return br.postAndWait(this, timeout);
}

private static final class BlockingRunnable implements Runnable {
private final Runnable mTask;
private boolean mDone;

public BlockingRunnable(Runnable task) {
mTask = task;
}

@Override
public void run() {
try {
mTask.run();
} finally {
synchronized (this) {
mDone = true;
notifyAll();
}
}
}

public boolean postAndWait(Handler handler, long timeout) {
if (!handler.post(this)) {
return false;
}

synchronized (this) {
if (timeout > 0) {
final long expirationTime = SystemClock.uptimeMillis() + timeout;
while (!mDone) {
long delay = expirationTime - SystemClock.uptimeMillis();
if (delay <= 0) {
return false; // timeout
}
try {
wait(delay);
} catch (InterruptedException ex) {
}
}
} else {
while (!mDone) {
try {
wait();
} catch (InterruptedException ex) {
}
}
}
}
return true;
}
}

内存泄漏

原理

可以作为 GcRoot 的对象:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象

发生内存泄漏的原因:非静态内部类或者匿名内部类默认持有外部类的引用,当通过 handler.sendMessage 方法发送消息时,Message 被发送到 Handler 中的 MessageQueue 里面,也就是 Looper 里面的 mQueue 队列,Message 如有延迟处理则会留在 MessageQueue 里面,而 Message 持有 mHandler 的引用,Handler 可能持有外部 Activity 的引用。主线程的 Looper 对象生命周期和应用程序生命周期一样长。因此不会回收已被销毁的 Activity 对象,而 GCRoot 是 Looper.sThreadLocal 对象。

解决方案

清空Message

1
2
3
4
5
6
@Override
protected void onDestroy() {
//将Handler里面消息清空了。
mHandler.removeCallbacksAndMessages(null);
super.onDestroy();
}

弱引用

为了避免非静态内部类&匿名内部类持有外部类引用可以采用静态内部类或者直接在外部编写该Handler的继承类。如果该类需要使用Activity相关变量,可以采用弱引用的方式将Activity的变量传过去。由于 gc 是无法控制的,如果发生 gc 导致 Acivity 被回收则无法正常工作了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class NoLeakHandler<T> extends Handler {

private WeakReference<SecondActivity> weakReference;

public NoLeakHandler(SecondActivity activity) {
this.weakReference = new WeakReference<>(activity);
}

@Override
public void handleMessage(Message msg) {
if (weakReference.get()!=null) {
Log.d("LLL", "handle...");
} else {
Log.d("LLL", "handle null...");
}
}
}

在onDestroy方法中尽量还是清空掉消息.

epoll

管道与eventfd

管道

  • 管道最常见的一个用法,就是将一个进程的输出导入到另一个进程的输入。
  • 数据以字节流的形式在管道中传输,也就是说,数据是没有消息边界。
  • 数据在管道中是单向流动的,管道的一端负责写数据,而另一端负责读数据。
  • 管道的缓冲区长度是 64 KB。

管道需要创建两个文件描述符,分别对应读写端,使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int pipFd[2];
pipe(pipFd); //打开管道

struct epoll_event event;
event.data.fd = pipFd[0]; //设置为监听管道读端
event.events = EPOLLIN | EPOLLET; //设置参数,接收可以 read() 的通知

int epfd = epoll_create(256); //创建 epoll 对象
int res = epoll_ctl(epfd, EPOLL_CTL_ADD, pipFd[0], &event); //添加管道读端为要监听的文件描述符

struct epoll_event allEvs[256];
int count = epoll_wait(epfd, allEvs, 256, 5000); //当前线程进入阻塞,等待被唤醒
for(int i = 0; i < count; i++){ //被唤醒,处理触发唤醒文件描述符
if(allEvs[i].data.fd == pipFd[0] && (allEvs[i].events & EPOLLIN)){
char buffer[256];
read(pipeFd, buffer, 100); //接收到管道可以进行读的信号,开始读取
}
}

// 其它线程
write(pipFd[1], str,strlen("hello"));

eventfd

  • eventfd 是 Linux 2.6.22后才开始支持的一种IPC通信方式,它的作用主要时用来做事件通知。
  • 与管道相比,eventfd的开销更小,因为eventfd只需要一个文件描述符。

管道和 eventfd 都可用于 epoll 等多路复用模型中,实现异步的信号通知功能。在 Android 6.0 之后,Looper 的唤醒从管道换成了 eventfd。

eventfd 是 Linux 系统中一个用来通知事件的文件描述符,基于内核向用户空间应用发送通知的机制,可以有效地被用来实现用户空间事件驱动的应用程序。

简而言之:eventfd 就是用来触发事件通知,它只有一个系统调用接口:

1
int eventfd(unsigned int initval, int flags);

表示打开一个 eventfd 文件并返回文件描述符,支持 epoll/poll/select 操作。

IO多路复用

IO 多路复用是一种同步 IO 模型,实现一个线程可以监视多个文件句柄。一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作,没有文件句柄就绪时会阻塞应用程序,交出 cpu。

与多进程和多线程技术相比,IO 多路复用技术的最大优势是系统开销小,系统不必为每个 IO 操作都创建进程或线程,也不必维护这些进程或线程,从而大大减小了系统的开销。

select、poll、epoll 就是 IO 多路复用三种实现方式。

  • select 最大连接数为进程文件描述符上限,一般为 1024;每次调用 select 拷贝 fd;轮询方式工作时间复杂度为 O(n)
  • poll 最大连接数无上限;每次调用 poll 拷贝 fd;轮询方式工作时间复杂度为 O(n)
  • epoll 最大连接数无上限;首次调用 epoll_ctl 拷贝 fd,调用 epoll_wait 时不拷贝;回调方式工作时间复杂度为 O(1)

epoll API:

1
2
3
4
5
6
7
8
9
10
11
12
// 返回一个 epfd,即 eventpoll 句柄。
int epoll_create(int size);

// 返回值:成功 0;失败 -1
// 对一个 epfd 进行操作。op 表示要执行的操作,包括 EPOLL_CTL_ADD (添加)、EPOLL_CTL_DEL (删除)、EPOLL_CTL_MOD (修改);
// fd 表示被监听的文件描述符;event 表示要被监听的事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

// 返回值:监听到的产生的事件数
// 等待 epfd 监听的 fd 所产生对应的事件。
// timeout:等待 IO 的超时时间,-1 表示一直阻塞直到来 IO 被唤醒,大于 0 表示阻塞指定的时间后被唤醒
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

Handler机制

主要分析 MessageQueue.java 中的三个 native 函数:

1
2
3
private native static long nativeInit(); //返回 ptr
private native void nativePollOnce(long ptr, int timeoutMillis); //阻塞
private native static void nativeWake(long ptr); //唤醒

nativeInit

首先来看 nativeInit 方法,nativeInit 在 MessageQueue 构造函数中被调用,其返回了一个底层对象的指针,对应实现在 android_os_MessageQueue.cpp 中:

1
2
3
4
5
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
// ...
return reinterpret_cast<jlong>(nativeMessageQueue);
}

可见 MessageQueue 对应的底层对象就是 NativeMessageQueue,而 NativeMessageQueue 初始化时会创建一个底层的 Looper 对象,Looper 的构造函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Looper::Looper(bool allowNonCallbacks) : mAllowNonCallbacks(allowNonCallbacks), ...{
// 通过 eventfd 系统调用返回一个文件描述符,专门用于事件通知
mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
...
rebuildEpollLocked();
}

void Looper::rebuildEpollLocked() {
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event));
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
...
}

通过 epoll_create 创建 epoll 对象,然后调用 epoll_ctl 添加 mWakeEventFd 为要监听的文件描述符

nativePollOnce

nativePollOnce 会使线程休眠,看下底层实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}

void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mLooper->pollOnce(timeoutMillis);
...
}

int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
for (;;) {
...
result = pollInner(timeoutMillis);
}
}

int Looper::pollInner(int timeoutMillis) {
...
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
...
}

通过调用 epoll_wait 方法,当前线程进入休眠,等待被唤醒

nativeWake

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
mLooper->wake();
}

void Looper::wake() {
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
if (errno != EAGAIN) {
LOG_ALWAYS_FATAL("Could not write wake signal to fd %d: %s", mWakeEventFd, strerror(errno));
}
}
}

关键逻辑是对 mWakeEventFd 发起写入操作,从而唤醒 nativePollOnce 中通过 epoll_wait 进入休眠的线程

扩展

在 Looper 中单独监控一个 mWakeEventFd 描述符为什么需要用到 epoll 呢,使用 recvform 这样的同步阻塞 IO 就行了呢?实际上 Looper 的功能是强大的,它提供了 addfd 的方法,外部可调用该方法动态添加需要监控的描述符与回调,Choreographer 中就是通过该方法监控 VSYNC 事件的。

所以 Native 层的 Looper 具有跨进程的能力?

总结

Android的消息机制的总体流程就是:Handler向MessageQueue发送一条消息(即插入一条消息),MessageQueue通过next方法(死循环查找)把消息传给Looper,Looper收到消息后开始处理,然后最终交给Handler自己去处理。换句话说就是:Handler给自己发送了一条消息,然后自己的handleMessage方法处理消息,只是中间过程经过了MessageQueue和Looper。这里的MessageQueue和Looper都有循环存在,调用的方法过程如下:

Handler消息机制

Handler导致内存泄漏的原因也就是因为在Looper的MessageQueue里面有还未处理的Message,而该Message又引用了Handler,如果该Handler采用的是非静态内部类或匿名内部类则该Handler又持有外部类Activity的引用,导致了Activity无法释放,因而产生了内存泄漏。

手写简易版的Handler机制

概述

来电秀是当来电的时候,将系统默认的来电UI替换成我们自定义的来电页面,然后实现接听和挂断的功能。目前可以使用如下两种方式:

  • 一种是参考大部分市面上来电秀APP的做法:监听手机的来电状态,然后以悬浮窗的方式弹出我们自定义的来电页面,覆盖系统来电页面,然后通过相关API实现接听和挂断功能。但是由于接听和挂断相关的API在Android的不同版本上都有修改,因此在某些Android版本上可能会有接听/挂断失败的情况。针对失效的情况,可以使用一些系统API以外的方式来接听/挂断电话。目前测试有效的方式有两种:
    • 一种是通过读取来电时系统的Notification信息,然后对通知上的“接听”和“挂断”进行调用来实现挂断/接听电话的需求。
    • 一种是模拟耳机线控的方式进行挂断/接听操作,通过操作MediaController,这种方式的成功率也比较高。
  • 一种是将我们的应用设置成系统默认的拨号APP,但是为了避免重写通话相关的功能,可以仅在监听到来电的时候使用APP自定义的页面,其余情况我们可以重定向到系统默认的拨号应用内,这种方式比较得不偿失,仅仅为了一个来电秀便修改系统默认的拨号应用,也可能会有一些兼容性问题。

本文将对这几种方式都进行尝试(由于市面上Android 7以下的手机已经很少见了,因此只考虑了Android 7到Android 9之间的设备,Android 10暂时不考虑)。

悬浮窗实现

概述

悬浮窗实现的方式是市面上大部分来电秀应用的做法,它主要通过监听手机的来电状态,然后以悬浮窗的方式弹出我们自定义的来电页面,覆盖掉系统页面,然后通过相关API实现接听和挂断功能。

权限

首先列举一下这种实现方式需要用到的相关权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 读取通话记录,用来显示来电人 -->
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<!-- 读取联系人,用来显示来电姓名 -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- 监听通话状态 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- 挂断电话需要 -->
<uses-permission android:name="android.permission.CALL_PHONE" />
<!-- 挂断/接听电话需要 -->
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
<!-- 悬浮窗 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- 前台服务 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
  • READ_CALL_LOG:Added in API level 16,权限级别:dangerous
  • READ_CONTACTS:Added in API level 1,权限级别:dangerous
  • READ_PHONE_STATE:Added in API level 1,权限级别:dangerous
  • CALL_PHONE:Added in API level 1,权限级别:dangerous
  • ANSWER_PHONE_CALLS:Added in API level 26,权限级别:dangerous
  • SYSTEM_ALERT_WINDOW:Added in API level 1,权限级别:signature|preinstalled|appop|pre23|development
  • FOREGROUND_SERVICE:Added in API level 28,权限级别:normal

其中动态申请权限代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
public class MainActivity extends AppCompatActivity {

private String[] mPermissions = new String[]{
Manifest.permission.READ_CALL_LOG,
Manifest.permission.READ_CONTACTS,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.CALL_PHONE,
"",
};

private static final int REQUEST_ID_POPUP = 0;
private static final int REQUEST_ID_PERMISSION = 1;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mPermissions[4] = Manifest.permission.ANSWER_PHONE_CALLS;
}

getPermissions();
}

private void getPermissions() {
if (!Settings.canDrawOverlays(this)) {
new AlertDialog.Builder(this)
.setTitle("获取悬浮窗权限")
.setMessage("点击跳转到悬浮窗权限页面")
.setPositiveButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivityForResult(intent, REQUEST_ID_POPUP);
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
MainActivity.this.finish();
}
})
.show();
} else {
if (checkPermission()) {
startService(new Intent(this, PhoneListenService.class));
} else {
ActivityCompat.requestPermissions(this, mPermissions, REQUEST_ID_PERMISSION);
}
}
}

private boolean checkPermission() {
boolean hasPermission = true;
for (String permission : mPermissions) {
if (!TextUtils.isEmpty(permission) && PermissionChecker.checkSelfPermission(this, permission)
!= PackageManager.PERMISSION_GRANTED) {
hasPermission = false;
}
}
return hasPermission;
}

@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == REQUEST_ID_POPUP) {
getPermissions();
}
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == REQUEST_ID_PERMISSION && grantResults.length > 0) {
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
finish();
}
}
startService(new Intent(this, PhoneListenService.class));
}
}
}

悬浮窗

悬浮窗页面控件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
public class FloatingView extends FrameLayout {
private View mView;
private FloatingManager mWindowManager;
private OnCallListener mListener;
private boolean mShown = false;

public FloatingView(Context context) {
super(context);
mView = LayoutInflater.from(context).inflate(R.layout.floating_view, null);
mView.findViewById(R.id.get_call).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
hide();
if (mListener != null) {
mListener.onGet();
}
}
});
mView.findViewById(R.id.end_call).setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
hide();
if (mListener != null) {
mListener.onEnd();
}
}
});
mWindowManager = FloatingManager.getInstance(context);
}

public void setPerson(String name, String number) {
if (!TextUtils.isEmpty(name)) {
((TextView) mView.findViewById(R.id.name_tv)).setText(name);
}
if (!TextUtils.isEmpty(number)) {
((TextView) mView.findViewById(R.id.number_tv)).setText(number);
}
}

public void setListener(OnCallListener listener) {
this.mListener = listener;
}

public void show() {
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
params.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
}
params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
params.width = LayoutParams.MATCH_PARENT;
params.height = LayoutParams.MATCH_PARENT;
mWindowManager.addView(mView, params);
mShown = true;
}

public void hide() {
if (mShown) {
mWindowManager.removeView(mView);
mShown = false;
}
}

public interface OnCallListener {
void onGet();

void onEnd();
}
}

xml文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg">

<TextView
android:id="@+id/name_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginTop="50dp"
android:gravity="center"
android:text="陌生人"
android:textColor="#AA99AA"
android:textSize="35sp"
android:textStyle="bold" />

<TextView
android:id="@+id/number_tv"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/name_tv"
android:layout_marginTop="20dp"
android:gravity="center"
android:textColor="#000000"
android:textSize="30sp"
android:textStyle="bold" />

<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="80dp">

<com.hearing.calltest.widget.CircleTextView
android:id="@+id/end_call"
android:layout_width="200px"
android:layout_height="200px"
android:layout_alignParentStart="true"
android:layout_marginStart="80dp"
app:color="#FF0000"
app:radius="100"
app:size="70"
app:text="挂断" />

<com.hearing.calltest.widget.CircleTextView
android:id="@+id/get_call"
android:layout_width="200px"
android:layout_height="200px"
android:layout_alignParentEnd="true"
android:layout_marginEnd="80dp"
app:color="#0000FF"
app:radius="100"
app:size="70"
app:text="接听" />

</RelativeLayout>

</RelativeLayout>

渐变背景xml如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="45"
android:centerColor="#ffffff"
android:endColor="#AAAAEE"
android:startColor="#1EE0FF" />
<stroke
android:width="2dp"
android:color="#AA82EE"
android:dashWidth="5dp"
android:dashGap="0dp" />
<corners
android:bottomLeftRadius="5dp"
android:bottomRightRadius="5dp"
android:radius="5dp"
android:topLeftRadius="5dp"
android:topRightRadius="5dp" />
</shape>

其中有一个圆形的接听/挂断按钮控件实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
public class CircleTextView extends View {

private String mCustomText;
private int mCustomColor;
private int mCustomRadius;
private int mFontSize;

private Paint mCirclePaint;
private TextPaint mTextPaint;

public CircleTextView(Context context) {
this(context, null);
}

public CircleTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public CircleTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initCustomAttrs(context, attrs);
}

private void initCustomAttrs(Context context, AttributeSet attrs) {
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CircleTextView);
mFontSize = ta.getInteger(R.styleable.CircleTextView_size, 16);
mCustomText = ta.getString(R.styleable.CircleTextView_text);
mCustomColor = ta.getColor(R.styleable.CircleTextView_color, Color.BLUE);
mCustomRadius = ta.getInteger(R.styleable.CircleTextView_radius, 30);
ta.recycle();

mCirclePaint = new Paint();
mCirclePaint.setColor(mCustomColor);
mCirclePaint.setStyle(Paint.Style.STROKE);
mCirclePaint.setStrokeWidth(5);
mTextPaint = new TextPaint();
mTextPaint.setColor(mCustomColor);
mTextPaint.setTextSize(mFontSize);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
// 在wrap_content的情况下默认长度为200
int minSize = 400;
// 当布局参数设置为wrap_content时,设置默认值
if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(minSize, minSize);
// 宽 / 高任意一个布局参数为= wrap_content时,都设置默认值
} else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(minSize, heightSpecSize);
} else if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(widthSpecSize, minSize);
}
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 处理padding属性
final int paddingLeft = getPaddingLeft();
final int paddingTop = getPaddingTop();
final int paddingRight = getPaddingRight();
final int paddingBottom = getPaddingBottom();
int width = 2 * mCustomRadius - paddingLeft - paddingRight;
int height = 2 * mCustomRadius - paddingTop - paddingBottom;
mCustomRadius = Math.min(width, height) / 2;
canvas.drawCircle(mCustomRadius, mCustomRadius, mCustomRadius, mCirclePaint);

// 将坐标原点移到控件中心
canvas.translate(width / 2f, height / 2f);
float textWidth = mTextPaint.measureText(mCustomText);
// 文字baseline在y轴方向的位置
float baseLineY = Math.abs(mTextPaint.ascent() + mTextPaint.descent()) / 2;
canvas.drawText(mCustomText, -textWidth / 2, baseLineY, mTextPaint);
}

public void setCustomText(String customText) {
this.mCustomText = customText;
invalidate();
}

public void setCustomColor(int customColor) {
this.mCustomColor = customColor;
mCirclePaint.setColor(customColor);
invalidate();
}

public void setFontSize(int fontSize) {
this.mFontSize = fontSize;
mTextPaint.setTextSize(fontSize);
invalidate();
}

public void setCustomRadius(int customRadius) {
this.mCustomRadius = customRadius;
invalidate();
}
}

控件的自定义属性文件如下:

1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="CircleTextView">
<attr name="size" format="integer" />
<attr name="color" format="color" />
<attr name="text" format="string" />
<attr name="radius" format="integer" />
</declare-styleable>
</resources>

悬浮窗的管理类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
public class FloatingManager {

private WindowManager mWindowManager;
private static FloatingManager mInstance;
private Context mContext;

public static FloatingManager getInstance(Context context) {
if (mInstance == null) {
mInstance = new FloatingManager(context);
}
return mInstance;
}

private FloatingManager(Context context) {
mContext = context;
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
}

/**
* 添加悬浮窗
* @param view
* @param params
* @return
*/
public boolean addView(View view, WindowManager.LayoutParams params) {
try {
mWindowManager.addView(view, params);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}

/**
* 移除悬浮窗
* @param view
* @return
*/
public boolean removeView(View view) {
try {
mWindowManager.removeView(view);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}

/**
* 更新悬浮窗参数
* @param view
* @param params
* @return
*/
public boolean updateView(View view, WindowManager.LayoutParams params) {
try {
mWindowManager.updateViewLayout(view, params);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}

前台Service

由于要让应用在后台时依旧监听来电状态,且不轻易被系统回收掉,因此使用前台Service的方式让我们的来电秀功能运行在前台Service中(可以选择是否需要在一个独立的进程里)。

Manifest配置如下:

1
2
3
4
5
6
7
<service
android:name=".service.PhoneListenService"
android:process=":PhoneListenService">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</service>

在前台Service中开启了一个Notification,相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class PhoneListenService extends Service {

public static final String TAG = "LLL";

private FloatingView mFloatingView;
private TelecomManager mTelManager;

@Override
public void onCreate() {
super.onCreate();

mTelManager = (TelecomManager) getSystemService(Context.TELECOM_SERVICE);

mFloatingView = new FloatingView(this);
mFloatingView.setListener(new FloatingView.OnCallListener() {
@Override
public void onGet() {
// 接听电话
acceptCall();
}

@Override
public void onEnd() {
// 挂断电话
endCall();
}
});
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Notification notification = buildNotification();
if (notification != null) {
startForeground(1, notification);
}

// 注册监听
registerPhoneStateListener();
return super.onStartCommand(intent, flags, startId);
}

private Notification buildNotification() {
Notification notification;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
return null;
}
String channelId = getString(R.string.app_name);
NotificationChannel notificationChannel = new NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_DEFAULT);
notificationChannel.setDescription(channelId);
notificationChannel.setSound(null, null);
notificationManager.createNotificationChannel(notificationChannel);

notification = new Notification.Builder(this, channelId)
.setContentTitle(getString(R.string.app_name))
.setContentText("来电秀")
.setSmallIcon(R.mipmap.ic_launcher)
.build();
} else {
notification = new Notification.Builder(this)
.setContentTitle(getString(R.string.app_name))
.setContentText("来电秀")
.setSmallIcon(R.mipmap.ic_launcher)
.build();
}
return notification;
}

@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
}

监听来电

PhoneStateListener

一种方式是通过PhoneStateListener来监听来电状态,可以通过TelephonyManager服务来监听系统通话的相关状态,共有三个状态:

  • CALL_STATE_IDLE:无任何状态时
  • CALL_STATE_OFFHOOK:接起电话时
  • CALL_STATE_RINGING:电话进来时

相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void registerPhoneStateListener() {
TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (tm != null) {
try {
MyPhoneCallListener listener = new MyPhoneCallListener();
listener.setListener(new MyPhoneCallListener.OnCallStateChanged() {
@Override
public void onCallStateChanged(int state, String number) {
switch (state) {
case TelephonyManager.CALL_STATE_IDLE:
Log.d(TAG, "无状态...");
mFloatingView.hide();
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
Log.d(TAG, "正在通话...");
mFloatingView.hide();
break;
case TelephonyManager.CALL_STATE_RINGING:
Log.d(TAG, "电话响铃...");
mFloatingView.show();
// 获取联系人信息
mFloatingView.setPerson(ContractsUtil.getContactName(PhoneListenService.this, number), number);
break;
}
}
});
tm.listen(listener, PhoneStateListener.LISTEN_CALL_STATE);
} catch (Exception e) {
e.printStackTrace();
}
}
}

监听类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class MyPhoneCallListener extends PhoneStateListener {

private OnCallStateChanged mListener;

/**
* CALL_STATE_IDLE 无任何状态时
* CALL_STATE_OFFHOOK 接起电话时
* CALL_STATE_RINGING 电话进来时
*/
@Override
public void onCallStateChanged(int state, String incomingNumber) {
Log.d("LLL", "state = " + state + ", incomingNumber = " + incomingNumber);
if (mListener != null) {
mListener.onCallStateChanged(state, incomingNumber);
}
super.onCallStateChanged(state, incomingNumber);
}

public void setListener(OnCallStateChanged listener) {
this.mListener = listener;
}

public interface OnCallStateChanged {
void onCallStateChanged(int state, String number);
}
}

BroadcastReceiver

通过PhoneStateListener的方式经过测试似乎需要让Service处于一个独立的进程中,而使用广播监听来电状态不需要如此。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private BroadcastReceiver mPhoneStateReceiver = new BroadcastReceiver() {

@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (TextUtils.equals(action, "android.intent.action.PHONE_STATE")) {
String state = intent.getStringExtra("state");
String number = intent.getStringExtra("incoming_number");

Log.d(TAG, "state = " + state + ", number = " + number);

if (TelephonyManager.EXTRA_STATE_RINGING.equalsIgnoreCase(state)) {
mFloatingView.show();
mFloatingView.setPerson(ContractsUtil.getContactName(PhoneListenService.this, number), number);
}
}
}
};

注册广播:

1
2
3
IntentFilter filter = new IntentFilter();
filter.addAction("android.intent.action.PHONE_STATE");
registerReceiver(mPhoneStateReceiver, filter);

获取联系人

根据来电号码查询联系人的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ContractsUtil {

public static String getContactName(Context context, String number) {
if (TextUtils.isEmpty(number)) {
return null;
}
final ContentResolver resolver = context.getContentResolver();

Uri lookupUri;
String[] projection = new String[]{ContactsContract.PhoneLookup._ID, ContactsContract.PhoneLookup.DISPLAY_NAME};
Cursor cursor = null;
try {
lookupUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
cursor = resolver.query(lookupUri, projection, null, null, null);
} catch (Exception ex) {
ex.printStackTrace();
try {
lookupUri = Uri.withAppendedPath(android.provider.Contacts.Phones.CONTENT_FILTER_URL,
Uri.encode(number));
cursor = resolver.query(lookupUri, projection, null, null, null);
} catch (Exception e) {
e.printStackTrace();
}
}
String ret = "未知来电";
if (cursor != null && cursor.getCount() > 0 && cursor.moveToFirst()) {
ret = cursor.getString(1);
cursor.close();
}
return ret;
}
}

挂断通话

在Android 9 平台,可以直接调用TelecomManager服务的endCall方法实现挂断电话,这个方法的相关描述如下:

  • 在API 28 中添加,在API 29 被标记为过期
  • 结束设备上的呼叫
  • 需要Manifest.permission.ANSWER_PHONE_CALLS权限

在Android 9以下的平台,可以通过反射获取到TELEPHONY_SERVICE服务,然后通过AIDL IPC的方式去调用endCall方法。

AIDL接口如下:

1
2
3
4
5
6
7
8
// ITelephony.aidl
package com.android.internal.telephony;

interface ITelephony {
boolean endCall();

void answerRingingCall();
}

在Android 8 及以下的设备中,有的手机调用endCall方法需要MODIFY_PHONE_STATE权限,然而这个权限现在只有系统应用才能申请,因此在某些手机上会挂不掉电话。

完整调用代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 在部分Android 8的设备上该方法会失效,补救措施可以参考下述两种方案(Notification和MediaController)
private void endCall() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (mTelManager != null) {
mTelManager.endCall();
}
} else {
try {
Method method = Class.forName("android.os.ServiceManager").getMethod("getService", String.class);
IBinder binder = (IBinder) method.invoke(null, new Object[]{Context.TELEPHONY_SERVICE});
ITelephony telephony = ITelephony.Stub.asInterface(binder);
telephony.endCall();
} catch (Exception e) {
// 如果挂断失败,则调起系统通话界面进行操作
if (mTelManager != null) {
mTelManager.showInCallScreen(false);
}
e.printStackTrace();
}
}
}

接听来电

在Android 4以下的设备上也可以跟挂断电话一样,IPC调用answerRingingCall方法即可接听来电,可惜在4.1以上的版本中,Google给这个方法的调用设置了权限,如果不是系统应用,会收到permissDeny的异常。在网上查到的很多解决办法都是模拟耳机线控的方式实现接听电话,然而在Android 7 上的几个设备上实践时,发现都失败了。

因此退而求其次,在Android 7 及以下的设备中,直接调起系统通话界面进行操作。

接听来电的完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 在Android 7上的设备接听会失败,补救措施可以参考下述两种方案(Notification和MediaController)
private void acceptCall() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
if (mTelManager != null) {
if (checkSelfPermission(Manifest.permission.ANSWER_PHONE_CALLS) != PackageManager.PERMISSION_GRANTED) {
return;
}
mTelManager.acceptRingingCall();
}
} else {
if (mTelManager != null) {
mTelManager.showInCallScreen(false);
}
}
}

Notification

可以通过读取来电时通知栏的Notification信息来区分是否是来电通知,通过Notification上的相关字段来区分接听/挂断操作,进而模拟其点击的Action实现接听和挂断操作。

权限

需要引导用户开启通知监听权限,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 判断是否打开了通知监听权限
public boolean isNotificationEnabled(Context context) {
Set<String> packageNames = NotificationManagerCompat.getEnabledListenerPackages(this);
if (packageNames.contains(context.getPackageName())) {
return true;
}
return false;
}

// 跳转开启通知读取权限
public void openNotificationListenSettings() {
try {
Intent intent;
intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
startActivityForResult(intent, REQUEST_ID_NOTIFICATION);
} catch (Exception e) {
e.printStackTrace();
}
}

接听/挂断电话

监听Notification需要继承NotificationListenerService类,通常实现以下方法:

  • onListenerConnected()
  • onNotificationPosted(StatusBarNotification sbn)
  • onNotificationRemoved(StatusBarNotification sbn)

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class NotificationListenService extends NotificationListenerService {

public static final String TAG = "LLL";

@Override
public void onNotificationPosted(StatusBarNotification sbn) {
Log.d(TAG, "onNotificationPosted");
super.onNotificationPosted(sbn);
try {
if (sbn.getNotification().actions != null) {

for (Notification.Action action : sbn.getNotification().actions) {

if ("Answer".equalsIgnoreCase(action.title.toString())
|| "接听".equalsIgnoreCase(action.title.toString())) {
Log.d(TAG, "answer is true");
PhoneHelper.getInstance().setAnswerIntent(action.actionIntent);
}

if ("Dismiss".equalsIgnoreCase(action.title.toString())
|| "忽略".equalsIgnoreCase(action.title.toString())) {
Log.d(TAG, "dismiss is true");
PhoneHelper.getInstance().setEndIntent(action.actionIntent);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

Manifest配置如下

1
2
3
4
5
6
7
<service
android:name=".service.NotificationListenService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>

MediaController

这种方式通过MediaController模拟耳机线控操作,可以在大多数设备上实现接听和挂断操作。

权限

这种方式也需要引导用户开启通知监听权限,代码如上节所示。

接听/挂断电话

需要实现一个空的NotificationListenerService类,Manifest配置如上节所示。

1
2
3
public class EmptyNotificationListenService extends NotificationListenerService {

}

一般来说挂断/接听电话使用的是如下两种线控方式:

  • 单击:接听
  • 长按:挂断

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
* 通过模拟耳机接听/挂断电话
*
* @param isAnswer: true, 接听; false: 挂断
*/
private void sendHeadsetHook(boolean isAnswer) {
MediaSessionManager sessionManager = (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE);

if (sessionManager == null) {
return;
}

try {
List<MediaController> controllers = sessionManager.getActiveSessions(new ComponentName(this, EmptyNotificationListenService.class));

for (MediaController m : controllers) {
if ("com.android.server.telecom".equals(m.getPackageName())) {

if (isAnswer) {
m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK));
} else {
long now = SystemClock.uptimeMillis();
m.dispatchMediaButtonEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_HEADSETHOOK,
1, 0, KeyCharacterMap.VIRTUAL_KEYBOARD,
0, KeyEvent.FLAG_LONG_PRESS, InputDevice.SOURCE_KEYBOARD));
}

m.dispatchMediaButtonEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_HEADSETHOOK));
Log.d(TAG, "headset sent to tel");
break;
}
}
} catch (SecurityException e) {
Log.d(TAG, "Permission error, Access to notification not granted to the app.");
}
}

锁屏显示

锁屏权限需要引导用户手动开启(某些机型没有这个权限?),代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (!PermissionUtil.getInstance().isLockOpen(this)) {
new AlertDialog.Builder(this)
.setMessage("请开启锁屏显示权限")
.setPositiveButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.setNegativeButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
PermissionUtil.getInstance().setLockOpen(MainActivity.this);
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.fromParts("package", getPackageName(), null));
startActivity(intent);
}
})
.create()
.show();
}

自启动

为了防止用户在退出应用后,来电秀失效,可以将上述的PhoneListenService和EmptyNotificationListenService放在一个单独的进程中,在某些机型(VIVO)上即使退出了应用,该Service依旧存活。而在更多的机型(小米,华为等),service所在进程也会被杀死。在这些机型上,可以引导用户开启自启动权限,在开启了自启动后,即使用户杀掉了应用主进程,service也会不定时重启,可能是立刻,也可能是几秒。

可以在Service中的回调函数中打印日志,针对不同的机型,应用死亡后会回调的方法也不一样,需要适配一下。在会被回调的方法中,可以重新启动MainActivity,也可以做其它的事情。

将PhoneListenService和EmptyNotificationListenService放在一个单独的进程中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<service android:name=".service.PhoneListenService"
android:enabled="true"
android:exported="false"
android:process=":phone">
<intent-filter>
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</service>

<service
android:name=".service.EmptyNotificationListenService"
android:enabled="true"
android:exported="false"
android:process=":phone"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>

引导用户开启自启动的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if (!PermissionUtil.getInstance().isLaunchOpen(this)) {
new AlertDialog.Builder(this)
.setMessage("请开启自启动权限")
.setPositiveButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.setNegativeButton("确认", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
PermissionUtil.getInstance().setLaunchOpen(MainActivity.this);
try {
startActivity(getAutostartSettingIntent(MainActivity.this));
} catch (Exception e) {
e.printStackTrace();
}
}
})
.create()
.show();
}

默认拨号应用实现

概述

这种方式是通过引导用户将我们的应用设置成系统默认拨号应用,这种方式基本上不会存在挂断和接听失效的情况,所需的权限也比较少,然而可能存在一些兼容性问题,如果出现,用户体验会比较差。

最理想的方式是将系统拨号应用的功能全部实现,可惜工作量比较大,因此可以只在监听到来电的时候才调用我们自定义的功能,其余情况分状态跳转到系统自带的页面。

在我自己的测试中,实现上还有一些问题,由于觉得这种方式依旧太重量级了,因此没有花太多时间去研究…

权限

1
2
3
4
<uses-permission android:name="android.permission.CALL_PHONE" />

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

动态申请权限的代码如上节所示。

悬浮窗

这一块的功能基本不变,实现上也是一样的。

申请默认应用

1
2
3
4
5
6
7
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!TextUtils.equals(((TelecomManager) getSystemService(Context.TELECOM_SERVICE)).getDefaultDialerPackage(), getPackageName())) {
startActivity(new Intent(ACTION_CHANGE_DEFAULT_DIALER).putExtra(EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, getPackageName()));
}
}
}

InCallService

这个功能的核心类就是InCallService类,其相关用法直接参考官网说明,不过在RoleManager这部分的说明中,由于RoleManager是在API 29(Android 10)才添加的,因此在Android Q以下的设备中不能使用这种方式申请成为默认应用,具体申请方法如上节所述。

参考官网的做法,首先在Manifest文件中声明MainActivity具有处理通话的能力,然后声明InCallService自定义的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.DIAL" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="tel" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.DIAL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

<service
android:name=".service.CallService"
android:permission="android.permission.BIND_INCALL_SERVICE">
<meta-data
android:name="android.telecom.IN_CALL_SERVICE_UI"
android:value="true" />
<intent-filter>
<action android:name="android.telecom.InCallService" />
</intent-filter>
</service>

其中android.telecom.IN_CALL_SERVICE_UI的表示此InCallService将替换内置的通话中UI。

自定义的InCallService代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class CallService extends InCallService {

public static final String TAG = "LLL";

private Call mCall;
private FloatingView mFloatingView;


@Override
public void onCreate() {
super.onCreate();

mFloatingView = new FloatingView(this);
mFloatingView.setListener(new FloatingView.OnCallListener() {
@Override
public void onGet() {
mFloatingView.hide();
if (mCall != null) {
mCall.answer(VideoProfile.STATE_AUDIO_ONLY);
gotoDialog();
}
}

@Override
public void onEnd() {
mFloatingView.hide();
if (mCall != null) {
mCall.disconnect();
}
}
});
}

public CallService() {
super();
}

@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return super.onBind(intent);
}

@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind");
return super.onUnbind(intent);
}

@Override
public void onCallAudioStateChanged(CallAudioState audioState) {
Log.d(TAG, "onCallAudioStateChanged");
super.onCallAudioStateChanged(audioState);
}

@Override
public void onCallRemoved(Call call) {
Log.d(TAG, "onCallRemoved");
super.onCallRemoved(call);
}

@Override
public void onCallAdded(Call call) {
Log.d(TAG, "state = " + call.getState());
mCall = call;
switch (call.getState()) {
case Call.STATE_RINGING:
mFloatingView.show();
break;
default:
// 此处处理还需要进一步调研
break;
}
}

private void gotoDialog() {
Intent intent = new Intent(Intent.ACTION_CALL, null);
if (checkSelfPermission(Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
return;
}
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}

CallScreeningService

这个类是用来做来电筛选的,在APP成为默认拨号应用后,可以监听来电状态,然后执行拦截与否的操作,可以使用这个类进行挂断电话的操作,但是不能接听电话。

在Manifest文件配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.DIAL" />

<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />

<data android:scheme="tel" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.DIAL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

<service
android:name=".service.CustomCallScreeningService"
android:permission="android.permission.BIND_SCREENING_SERVICE">
<intent-filter>
<action android:name="android.telecom.CallScreeningService" />
</intent-filter>
</service>
</application>

自定义CallScreeningService服务如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class CustomCallScreeningService extends CallScreeningService {

private static final String TAG = "CallScreeningService";

private FloatingView mFloatingView;
private Call.Details mCallDetails;


@Override
public void onCreate() {
super.onCreate();

mFloatingView = new FloatingView(this);
mFloatingView.setListener(new FloatingView.OnCallListener() {
@Override
public void onGet() {
acceptCall();
}

@Override
public void onEnd() {
endCall();
}
});
}

private void acceptCall() {
if (mCallDetails == null) {
return;
}
Log.d(TAG, "acceptCall");
CallResponse response = new CallResponse
.Builder()
.setDisallowCall(false)
.setRejectCall(false)
.setSkipCallLog(false)
.setSkipNotification(false)
.build();
respondToCall(mCallDetails, response);
mCallDetails = null;
}

private void endCall() {
if (mCallDetails == null) {
return;
}
Log.d(TAG, "endCall");
CallResponse response = new CallResponse
.Builder()
.setDisallowCall(true)
.setRejectCall(true)
.setSkipCallLog(true)
.setSkipNotification(true)
.build();
respondToCall(mCallDetails, response);
mCallDetails = null;
}

@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return super.onBind(intent);
}

@Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind");
return super.onUnbind(intent);
}

@Override
public void onScreenCall(@NonNull Call.Details callDetails) {
Log.d(TAG, "onScreenCall");
mFloatingView.setPerson(callDetails.getCallerDisplayName(), callDetails.getCallerDisplayName());
mFloatingView.show();
mCallDetails = callDetails;
}
}

结论

悬浮窗实现

这种方式是将我们的应用作为一个普通的app,当监听到来电时通过悬浮窗的形式弹出我们自定义的来电页面,然后通过我们提供的挂断/接听按钮进行挂断/接听操作。

所需权限如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!-- 读取通话记录,用来显示来电号码(读取广播中的号码) -->
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<!-- 读取联系人,根据来电号码查询联系人 -->
<uses-permission android:name="android.permission.READ_CONTACTS" />
<!-- 监听通话状态 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<!-- 挂断电话需要 -->
<uses-permission android:name="android.permission.CALL_PHONE" />
<!-- 挂断/接听电话需要 -->
<uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
<!-- 悬浮窗 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- 前台服务 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

存在的问题:

  • 系统提供的API中,在Android 9 上的设备接听/挂断电话都可实现
  • 小部分Android 8 上不能通过系统API挂断电话(在Redmi 6A,Android 8.1.0 011019设备上测试失败,在其它三款Android 8的设备上测试可以成功)
  • Android 7 上不能通过系统API接听电话(只有一台Android 7 的测试设备,测试结果为接听失败)

解决办法:尝试过应用商店下载量比较高的几款来电秀应用,都存在这个问题,可以使用如下几个补救方案:

  • 当尝试挂断/接听电话失败时,跳转到系统通话的页面,让用户再一次手动挂断/接听电话。
  • 监听来电Notification挂断/接听电话
  • 模拟耳机线控挂断/接听电话

后两种方式如下所述。

Notification

通过读取来电时通知栏的Notification信息来区分是否是来电通知,通过Notification上的相关字段来区分接听/挂断操作,进而模拟其点击的Action实现接听和挂断操作。

这种方式需要解决的问题是如何准确识别出当前通知是否是来电通知,以及对应的接听/挂断是Notification中的哪一个Action:

  • 可以通过筛选通知的包名来识别是否为来电通知(不能型号手机包名可能不一样)
  • 可以通过title来识别是否为接听/挂断,这个也取决于不同的手机型号,因为不同的开发商开发的拨号应用其使用的title可能不一样。更有甚者,系统在切换了语言后,title也会随之变化,想想就头大。

所需的权限:

  • 需要引导用户手动开启通知监听权限

存在的问题:

  • 在不同的Android版本上都可以挂断/接听
  • 准确匹配当前的Notification是否是来电通知比较麻烦
  • 通过title的方式区分挂断/接听操作有局限性,不同手机型号上title不一致,而且也会受到语言切换的影响

MediaController

这种方式的成功率经测试发现效果比较好,更准确的成功率有待进一步测试。

所需的权限:

  • 需要引导用户手动开启通知监听权限

暂时没有发现其他问题。

自启动/锁屏

都需要引导用户手动开启这两个权限,而且需要适配不同的机型,不同的机型这些权限所在的应用包名也不一样。

默认拨号应用

引导用户手动选择我们的应用作为系统默认拨号应用(很多用户应该不会允许),如果要达到理想的效果,需要我们手动实现一个拨号应用的全部功能。如果不想实现拨号应用的全部功能,只实现来电时自定义一个接听页面,则会影响到其他部分的功能,比如说拨号等,尝试了一下,发现结果不理想。

如果不介意实现一个通话应用的全部功能,那么InCallService无疑是一个理想的解决方案,因为接听和挂断电话在Call这个类中都有实现。

所需的权限:

1
2
3
4
5
6
<!-- 挂断电话需要 -->
<uses-permission android:name="android.permission.CALL_PHONE" />
<!-- 悬浮窗 -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<!-- 前台服务 -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

存在的问题:

  • 需要用户同意选择默认拨号应用,很多用户可能不会允许
  • 接听/挂断功能正常,但可能会影响到拨号等功能
  • 通过成为系统默认拨号应用来仅仅实现一个来电秀功能,比较重量级

总结

综上所述,使用悬浮窗实现(系统API+MediaController)的方式可以实现来电秀的功能,通过调用系统API,在失效的设备上接着使用模拟耳机线控的方式来挂断/接听电话,经过测试暂时还没有发现失效的设备(仅限于Android 7,8,9)。

另外,在android.telecomandroid.telephony这两个包中还有许多系统提供的api,说不定可以从中找到新的有效方案,这个也有待后续的调研。

本文的项目完整代码链接:CallTest

概述

android系统会为每个程序进程的运行时创建一个Application类的对象且仅创建一个,且application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。因为它是全局的单例的,所以在不同的Activity,Service中获得的对象都是同一个对象。所以通过Application来进行一些,数据传递,数据共享等,数据缓存等操作。

注:本文基于Android 9.0源码,为了文章的简洁性,引用源码的地方可能有所删减。

多进程

不同的进程会有自己的Application实例,如果应用中采用多进程方式,onCreate方法会执行多次,可以根据不同的进程名字进行不同的初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class MyApplication extends Application {
private final static String PROCESS_NAME = "com.hearing";
private static MyApplication myApplication = null;

public static MyApplication getApplication() {
return myApplication;
}

/**
* 判断是不是UI主进程,因为有些东西只能在UI主进程初始化
*/
public static boolean isAppMainProcess() {
try {
int pid = android.os.Process.myPid();
String process = getAppNameByPID(MyApplication.getApplication(), pid);
if (TextUtils.isEmpty(process)) {
return true;
} else if (PROCESS_NAME.equalsIgnoreCase(process)) {
return true;
} else {
return false;
}
} catch (Exception e) {
e.printStackTrace();
return true;
}
}

/**
* 根据Pid得到进程名
*/
public static String getAppNameByPID(Context context, int pid) {
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
for (android.app.ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {
if (processInfo.pid == pid) {
return processInfo.processName;
}
}
return "";
}

@Override
public void onCreate() {
super.onCreate();

myApplication = this;

if (isAppMainProcess()) {
//do something for init
}
}
}

生命周期

onCreate

在创建应用程序时创建

onTerminate

当终止应用程序对象时调用,不保证一定被调用,当程序是被内核终止以便为其他应用程序释放资源,那么将不会提醒,并且不调用应用程序的对象的onTerminate方法而直接终止进程

onLowMemory

当后台程序已经终止资源还匮乏时会调用这个方法。好的应用程序一般会在这个方法里面释放一些不必要的资源来应付当后台程序已经终止,前台应用程序内存还不够时的情况。

onTrimMemory

当系统回调确认该进程是时候回收不必要的内存了,这将例如发生在后台时,没有足够的内存保持尽可能多的后台进程运行一样。一般发生在点击Home键、Task任务菜单键时被执行。

onConfigurationChanged

配置改变时触发这个方法

ActivityLifecycleCallbacks

概述

ActivityLifecycleCallbacks 是用来监听所有 Activity 的生命周期回调。接口定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface ActivityLifecycleCallbacks {
// 当Activity调用Activity#onCreate时调用
void onActivityCreated(Activity activity, Bundle savedInstanceState);
// 当Activity调用Activity#onStart时调用
void onActivityStarted(Activity activity);
// 当Activity调用Activity#onResume时调用
void onActivityResumed(Activity activity);
// 当Activity调用Activity#onPause时调用
void onActivityPaused(Activity activity);
// 当Activity调用Activity#onStop时调用
void onActivityStopped(Activity activity);
// 当Activity调用Activity#onSaveInstanceState时调用
void onActivitySaveInstanceState(Activity activity, Bundle outState);
// 当Activity调用Activity#onDestroy时调用
void onActivityDestroyed(Activity activity);
}

使用:

1
2
3
4
5
6
7
8
public class MyApplication extends Application {

@Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(mAppLifecycleCallbacks);
}
}

管理Activity栈

Activity 页面栈,最常用的实现就是用来完全退出应用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private static HashMap<Integer, WeakReference<Activity>> sActivities = new HashMap<>();

public void finishAllActivity() {
for (int i : sActivities.keySet()) {
if (sActivities.get(i) != null && sActivities.get(i).get() != null) {
Activity activity = sActivities.get(i).get();
if (!Util.isFinishingOrDestroyed(activity)) {
activity.finish();
}
}
}
sActivities.clear();
}

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
WeakReference<Activity> wa = new WeakReference<>(activity);
sActivities.put(activity.hashCode(), wa);
}

@Override
public void onActivityDestroyed(Activity activity) {
sActivities.remove(activity.hashCode());
}

获取当前Activity

1
2
3
4
5
6
7
8
9
10
11
public Activity getCurrentActivity() {
if (sCurrentActivity == null) {
return null;
}
return sCurrentActivity.get();
}

@Override
public void onActivityResumed(Activity activity) {
sCurrentActivity = new WeakReference<>(activity);
}

判断应用前后台

方法一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ActivityLifecycleCallbacks.java
private volatile int appCount = 0;
private volatile boolean isForground = true;

@Override
public void onActivityStarted(Activity activity) {
synchronized (this) {
appCount++;
if (!isForground) {
isForground = true;
Log.e("AppLifecycle", "app into forground ");
}
}
}

@Override
public void onActivityStopped(Activity activity) {
synchronized (this) {
appCount--;
if (!isForgroundAppValue()) {
isForground = false;
Log.e("AppLifecycle", "app into background ");
}
}
}

private boolean isForgroundAppValue() {
synchronized (this) {
return appCount > 0;
}
}

方法二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
public class AppStatusObserver {
private static AppStatusObserver sInstance;

static final int EVENT_ON_ACTIVITY_RESUMED = 0;
static final int EVENT_ON_ACTIVITY_STOPPED = 1;

@AppStatus
private int mAppStatus = STATUS.STATUS_NONE;

public interface STATUS {
int STATUS_NONE = 0;
int STATUS_BACKGROUND = 1;
int STATUS_FOREGROUND = 2;
}

@Retention(RetentionPolicy.SOURCE)
@IntDef({STATUS.STATUS_BACKGROUND, STATUS.STATUS_FOREGROUND})
public @interface AppStatus {
}

private int mCurResumeActivityHC;
private Set<WeakReference> mWRListener;

private AppStatusObserver() {
mWRListener = new HashSet<>();
}

public boolean isAppInBackground() {
return mAppStatus == STATUS.STATUS_BACKGROUND;
}

public boolean isAppInForeground() {
return mAppStatus == STATUS.STATUS_FOREGROUND;
}

public void registerListener(IStatusChgListener listener) {
for (WeakReference w : mWRListener) {
IStatusChgListener l = (IStatusChgListener) w.get();
if (l == listener) {
return;
}
}
WeakReference<IStatusChgListener> w = new WeakReference<>(listener);
mWRListener.add(w);
}

public void unregisterListener(IStatusChgListener listener) {
for (WeakReference w : mWRListener) {
IStatusChgListener l = (IStatusChgListener) w.get();
if (l == listener) {
w.clear();
break;
}
}
}

void onActivityLifecycleEvent(@NonNull Activity activity, int event) {
synchronized (AppStatusObserver.class) {
switch (event) {
case EVENT_ON_ACTIVITY_RESUMED:
if (mAppStatus != STATUS.STATUS_FOREGROUND) {
if ((activity.hashCode() == mCurResumeActivityHC || mCurResumeActivityHC == 0)) {
notifyChange(STATUS.STATUS_FOREGROUND);
mAppStatus = STATUS.STATUS_FOREGROUND;
} else if (mAppStatus == STATUS.STATUS_BACKGROUND) {
notifyChange(STATUS.STATUS_FOREGROUND);
mAppStatus = STATUS.STATUS_FOREGROUND;
}
}
this.mCurResumeActivityHC = activity.hashCode();
break;
case EVENT_ON_ACTIVITY_STOPPED:
if (activity.hashCode() == mCurResumeActivityHC) {
notifyChange(STATUS.STATUS_BACKGROUND);
mAppStatus = STATUS.STATUS_BACKGROUND;
}
break;
}
}
}

private void notifyChange(@AppStatus int status) {
for (WeakReference w : mWRListener) {
IStatusChgListener listener = (IStatusChgListener) w.get();
if (listener != null) {
listener.onStatusChange(status);
}
}
}

public synchronized static AppStatusObserver getInstance() {
if (sInstance == null) {
sInstance = new AppStatusObserver();
}
return sInstance;
}

public interface IStatusChgListener {
void onStatusChange(@AppStatus int status);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// ActivityLifecycleCallbacks.java
private AppStatusObserver mAppStatusChgObserver = AppStatusObserver.getInstance();

@Override
public void onActivityResumed(Activity activity) {
mAppStatusChgObserver.onActivityLifecycleEvent(activity, AppStatusObserver.EVENT_ON_ACTIVITY_RESUMED);
}

@Override
public void onActivityStopped(Activity activity) {
mAppStatusChgObserver.onActivityLifecycleEvent(activity, AppStatusObserver.EVENT_ON_ACTIVITY_STOPPED);
}

原理

system进程和app进程都运行着一个或多个app,每个app都会有一个对应的Application对象。本文将解析以下两种进程创建Application的过程:

  • system_server进程;
  • app进程;

system_server进程

SystemServer.main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// SystemServer.java
public final class SystemServer {
...
public static void main(String[] args) {
//先初始化SystemServer对象,再调用对象的run()方法
new SystemServer().run();
}
}

private void run() {
Looper.prepareMainLooper();// 准备主线程looper

//加载android_servers.so库,该库包含的源码在frameworks/base/services/目录下
System.loadLibrary("android_servers");

createSystemContext(); //初始化系统上下文

//创建系统服务管理
mSystemServiceManager = new SystemServiceManager(mSystemContext);
LocalServices.addService(SystemServiceManager.class, mSystemServiceManager);

//启动各种系统服务
try {
startBootstrapServices(); // 启动引导服务
startCoreServices(); // 启动核心服务
startOtherServices(); // 启动其他服务
} catch (Throwable ex) {
throw ex;
}

//一直循环执行
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}

createSystemContext

1
2
3
4
5
6
7
8
private void createSystemContext() {
ActivityThread activityThread = ActivityThread.systemMain();
mSystemContext = activityThread.getSystemContext();
mSystemContext.setTheme(DEFAULT_SYSTEM_THEME);

final Context systemUiContext = activityThread.getSystemUiContext();
systemUiContext.setTheme(DEFAULT_SYSTEM_THEME);
}

ActivityThread

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public final class ActivityThread extends ClientTransactionHandler {
// 创建ApplicationThread对象
final ApplicationThread mAppThread = new ApplicationThread();
final Looper mLooper = Looper.myLooper();
// class H extends Handler
final H mH = new H();
// 当前进程中首次初始化的app对象
Application mInitialApplication;
final ArrayList<Application> mAllApplications = new ArrayList<Application>();
// 标记当前进程是否为system进程
boolean mSystemThread = false;
// 记录system进程的ContextImpl对象
private ContextImpl mSystemContext;
private ContextImpl mSystemUiContext;

final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages = new ArrayMap<>();
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
static volatile Handler sMainThreadHandler; // set once in main()
private static volatile ActivityThread sCurrentActivityThread;

ActivityThread() {
mResourcesManager = ResourcesManager.getInstance();
}
}

systemMain

1
2
3
4
5
6
7
8
9
10
11
12
13
public static ActivityThread systemMain() {
// The system process on low-memory devices do not get to use hardware
// accelerated drawing, since this can add too much overhead to the
// process.
if (!ActivityManager.isHighEndGfx()) {
ThreadedRenderer.disable(true);
} else {
ThreadedRenderer.enableForegroundTrimming();
}
ActivityThread thread = new ActivityThread();
thread.attach(true, 0);
return thread;
}

创建完ActivityThread对象,接下来执行attach()操作:

attach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
private void attach(boolean system, long startSeq) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
// ...
} else {
// Don't set application object here -- if the system crashes,
// we can't display an alert, we just want to die die die.
android.ddm.DdmHandleAppName.setAppName("system_process", UserHandle.myUserId());
try {
mInstrumentation = new Instrumentation();
mInstrumentation.basicInit(this);
// mPackageInfo是LoadedApk对象
ContextImpl context = ContextImpl.createAppContext(this, getSystemContext().mPackageInfo);
mInitialApplication = context.mPackageInfo.makeApplication(true, null);
mInitialApplication.onCreate();
} catch (Exception e) {
throw new RuntimeException(
"Unable to instantiate Application():" + e.toString(), e);
}
}
// ...
}

public ContextImpl getSystemContext() {
synchronized (this) {
if (mSystemContext == null) {
mSystemContext = ContextImpl.createSystemContext(this);
}
return mSystemContext;
}
}

ContextImpl

构造函数

部分成员变量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final @NonNull ActivityThread mMainThread;
final @NonNull LoadedApk mPackageInfo;
private @Nullable ClassLoader mClassLoader;

private final @Nullable IBinder mActivityToken;

private final String mBasePackageName;
private final String mOpPackageName;

private final @NonNull ResourcesManager mResourcesManager;
private @NonNull Resources mResources;
private @Nullable Display mDisplay; // may be null if default display

private PackageManager mPackageManager;

构造函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private ContextImpl(@Nullable ContextImpl container, @NonNull ActivityThread mainThread,
@NonNull LoadedApk packageInfo, @Nullable String splitName,
@Nullable IBinder activityToken, @Nullable UserHandle user, int flags,
@Nullable ClassLoader classLoader) {
// ...
mMainThread = mainThread;
mActivityToken = activityToken;
mFlags = flags;

// 将传入的LoadedApk赋值给了mPackageInfo
mPackageInfo = packageInfo;
mSplitName = splitName;
mClassLoader = classLoader;
mResourcesManager = ResourcesManager.getInstance();
// ...
}

createSystemContext

1
2
3
4
5
6
7
8
static ContextImpl createSystemContext(ActivityThread mainThread) {
LoadedApk packageInfo = new LoadedApk(mainThread);
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null, null, null, 0, null);
context.setResources(packageInfo.getResources());
context.mResources.updateConfiguration(context.mResourcesManager.getConfiguration(),
context.mResourcesManager.getDisplayMetrics());
return context;
}

createAppContext

1
2
3
4
5
6
7
static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
ContextImpl context = new ContextImpl(null, mainThread, packageInfo, null,
null, null, 0, null);
context.setResources(packageInfo.getResources());
return context;
}

LoadedApk

构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
private final ActivityThread mActivityThread;
final String mPackageName;
private ApplicationInfo mApplicationInfo;
private String mAppDir;
private String mResDir;
private String[] mOverlayDirs;
private String mDataDir;
private String mLibDir;
private File mDataDirFile;
private File mDeviceProtectedDataDirFile;
private File mCredentialProtectedDataDirFile;
private final ClassLoader mBaseClassLoader;
private final boolean mSecurityViolation;
private final boolean mIncludeCode;
private final boolean mRegisterPackage;
private final DisplayAdjustments mDisplayAdjustments = new DisplayAdjustments();
/** WARNING: This may change. Don't hold external references to it. */
Resources mResources;
private ClassLoader mClassLoader;
private Application mApplication;

private String[] mSplitNames;
private String[] mSplitAppDirs;
private String[] mSplitResDirs;
private String[] mSplitClassLoaderNames;

private final ArrayMap<Context, ArrayMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers = new ArrayMap<>();
private final ArrayMap<Context, ArrayMap<BroadcastReceiver, LoadedApk.ReceiverDispatcher>> mUnregisteredReceivers = new ArrayMap<>();
private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mServices = new ArrayMap<>();
private final ArrayMap<Context, ArrayMap<ServiceConnection, LoadedApk.ServiceDispatcher>> mUnboundServices = new ArrayMap<>();

LoadedApk(ActivityThread activityThread) {
mActivityThread = activityThread;
mApplicationInfo = new ApplicationInfo();
mApplicationInfo.packageName = "android";
mPackageName = "android";
mAppDir = null;
mResDir = null;
mSplitAppDirs = null;
mSplitResDirs = null;
mSplitClassLoaderNames = null;
mOverlayDirs = null;
mDataDir = null;
mDataDirFile = null;
mDeviceProtectedDataDirFile = null;
mCredentialProtectedDataDirFile = null;
mLibDir = null;
mBaseClassLoader = null;
mSecurityViolation = false;
mIncludeCode = true;
mRegisterPackage = false;
mClassLoader = ClassLoader.getSystemClassLoader();
mResources = Resources.getSystem();
mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);
}

makeApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
if (mApplication != null) {
return mApplication;
}

Application app = null;

String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}

try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
initializeJavaContextClassLoader();
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
}
mActivityThread.mAllApplications.add(app);
mApplication = app;

if (instrumentation != null) {
try {
instrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
}
}

// Rewrite the R 'constants' for all library apks.
// ...
return app;
}

getClassLoader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public ClassLoader getClassLoader() {
synchronized (this) {
if (mClassLoader == null) {
createOrUpdateClassLoaderLocked(null /*addedPaths*/);
}
return mClassLoader;
}
}

private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
if (mPackageName.equals("android")) {
// Note: This branch is taken for system server and we don't need to setup
// jit profiling support.
if (mClassLoader != null) {
// nothing to update
return;
}

if (mBaseClassLoader != null) {
mClassLoader = mBaseClassLoader;
} else {
mClassLoader = ClassLoader.getSystemClassLoader();
}
mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);

return;
}
// ...
}

// ClassLoader.java
public static ClassLoader getSystemClassLoader() {
return SystemClassLoader.loader;
}

static private class SystemClassLoader {
public static ClassLoader loader = ClassLoader.createSystemClassLoader();
}

private static ClassLoader createSystemClassLoader() {
String classPath = System.getProperty("java.class.path", ".");
String librarySearchPath = System.getProperty("java.library.path", "");
return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
}

Instrumentation.newApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Instrumentation.java
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
// private AppComponentFactory getFactory(String pkg) {}
Application app = getFactory(context.getPackageName())
.instantiateApplication(cl, className);
app.attach(context);
return app;
}

// frameworks/base/core/java/android/app/AppComponentFactory.java
public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
@NonNull String className)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Application) cl.loadClass(className).newInstance();
}

接着是Application.attach()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Application.java
final void attach(Context context) {
attachBaseContext(context); // Application的mBase
// ContextImpl.getImpl(context)返回的就是context自身
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

// class ContextImpl extends Context {}
// ContextImpl.java
static ContextImpl getImpl(Context context) {
Context nextContext;
while ((context instanceof ContextWrapper) &&
(nextContext=((ContextWrapper)context).getBaseContext()) != null) {
context = nextContext;
}
return (ContextImpl)context;
}

attch方法的主要功能:

  • 将新创建的ContextImpl对象保存到Application的父类成员变量mBase;
  • 将新创建的LoadedApk对象保存到Application的成员变量mLoadedApk;

如果想通过反射获取应用的ClassLoader,需要从LoadedApk对象中获取,LoadedApk对象也同样可以通过反射方式获取,它的实例在许多对象中都有引用,比如说:ContextImpl和Application中都有引用:

  • ContextImpl:如上代码中可以看到,在Application#attachBaseContext(Context base)回调中可以拿到ContextImpl实例,进一步可以获得LoadedApk对象;
  • Application:应注意到,在调用了attachBaseContext之后,Application中的mLoadedApk字段才被赋值,因此在attachBaseContext中反射获取到的LoadedApk会是null,可以在onCreate回调时再尝试反射调用。

App进程

ActivityThread

main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
// ...
Looper.prepareMainLooper();

// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
// ...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);

if (sMainThreadHandler == null) {
// static volatile Handler sMainThreadHandler;
sMainThreadHandler = thread.getHandler();
}

Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");
}

这是运行在app进程,当进程由zygote fork后执行ActivityThread的main方法。

attach

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private void attach(boolean system, long startSeq) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
ViewRootImpl.addFirstDrawHandler(new Runnable() {
@Override
public void run() {
ensureJitEnabled();
}
});
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManager.getService();
try {
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
// ...
} else {
// ...
}
}

mgr.attachApplication是通过Binder方式调用AMS中的方法。

H

在H内部类中定义了许多种message类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class H extends Handler {
public static final int BIND_APPLICATION = 110;
public static final int EXIT_APPLICATION = 111;
public static final int RECEIVER = 113;
public static final int CREATE_SERVICE = 114;
public static final int SERVICE_ARGS = 115;
public static final int STOP_SERVICE = 116;

public static final int CONFIGURATION_CHANGED = 118;
public static final int CLEAN_UP_CONTEXT = 119;
public static final int GC_WHEN_IDLE = 120;
public static final int BIND_SERVICE = 121;
public static final int UNBIND_SERVICE = 122;
public static final int DUMP_SERVICE = 123;
public static final int LOW_MEMORY = 124;
public static final int PROFILER_CONTROL = 127;
public static final int CREATE_BACKUP_AGENT = 128;
public static final int DESTROY_BACKUP_AGENT = 129;
public static final int SUICIDE = 130;
public static final int REMOVE_PROVIDER = 131;
public static final int ENABLE_JIT = 132;
public static final int DISPATCH_PACKAGE_BROADCAST = 133;
public static final int SCHEDULE_CRASH = 134;
public static final int DUMP_HEAP = 135;
public static final int DUMP_ACTIVITY = 136;
public static final int SLEEPING = 137;
public static final int SET_CORE_SETTINGS = 138;
public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;
public static final int DUMP_PROVIDER = 141;
public static final int UNSTABLE_PROVIDER_DIED = 142;
public static final int REQUEST_ASSIST_CONTEXT_EXTRAS = 143;
public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144;
public static final int INSTALL_PROVIDER = 145;
public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
public static final int ENTER_ANIMATION_COMPLETE = 149;
public static final int START_BINDER_TRACKING = 150;
public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
public static final int ATTACH_AGENT = 155;
public static final int APPLICATION_INFO_CHANGED = 156;
public static final int RUN_ISOLATED_ENTRY_POINT = 158;
public static final int EXECUTE_TRANSACTION = 159;
public static final int RELAUNCH_ACTIVITY = 160;
}

AMS.attachApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public final void attachApplication(IApplicationThread thread, long startSeq) {
synchronized (this) {
int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
attachApplicationLocked(thread, callingPid, callingUid, startSeq);
Binder.restoreCallingIdentity(origId);
}
}

private final boolean attachApplicationLocked(IApplicationThread thread,
int pid, int callingUid, long startSeq) {
ProcessRecord app;
long startTime = SystemClock.uptimeMillis();
if (pid != MY_PID && pid >= 0) {
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid); // 根据pid获取ProcessRecord
}
} else {
app = null;
}

thread.bindApplication(processName, appInfo, providers,
app.instr.mClass,
profilerInfo, app.instr.mArguments,
app.instr.mWatcher,
app.instr.mUiAutomationConnection, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.persistent,
new Configuration(getGlobalConfiguration()), app.compat,
getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, isAutofillCompatEnabled);
return true;
}

ApplicationThread.bindApplication

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public final void bindApplication(String processName, ApplicationInfo appInfo,
List<ProviderInfo> providers, ComponentName instrumentationName,
ProfilerInfo profilerInfo, Bundle instrumentationArgs,
IInstrumentationWatcher instrumentationWatcher,
IUiAutomationConnection instrumentationUiConnection, int debugMode,
boolean enableBinderTracking, boolean trackAllocation,
boolean isRestrictedBackupMode, boolean persistent, Configuration config,
CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
String buildSerial, boolean autofillCompatibilityEnabled) {
AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
data.providers = providers;
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.instrumentationUiAutomationConnection = instrumentationUiConnection;
data.debugMode = debugMode;
data.enableBinderTracking = enableBinderTracking;
data.trackAllocation = trackAllocation;
data.restrictedBackupMode = isRestrictedBackupMode;
data.persistent = persistent;
data.config = config;
data.compatInfo = compatInfo;
data.initProfilerInfo = profilerInfo;
data.buildSerial = buildSerial;
data.autofillCompatibilityEnabled = autofillCompatibilityEnabled;
sendMessage(H.BIND_APPLICATION, data);
}

sendMessage方法最终走的是mH.sendMessage(msg),mH是H类型:

ActivityThread.handleBindApplication

当主线程收到H.BIND_APPLICATION,则调用handleBindApplication:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// ActivityThread.java
private void handleBindApplication(AppBindData data) {
mBoundApplication = data;
Process.setArgV0(data.processName); // 设置进程名
// ...
// 创建LoadedApk对象,并将其加入到mPackages,也就是说每个app都会创建唯一的LoadedApk对象。
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
// ...

// 创建ContextImpl上下文
final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
// ...
Application app;
try {
// 此处data.info是指LoadedApk, 通过反射创建目标应用Application对象
app = data.info.makeApplication(data.restrictedBackupMode, null);
mInitialApplication = app;
// ...
mInstrumentation.onCreate(data.instrumentationArgs);
//回调onCreate
mInstrumentation.callApplicationOnCreate(app);

} finally {
}
}

public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}

private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
boolean registerPackage) {
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
WeakReference<LoadedApk> ref;
if (differentUser) {
// Caching not supported across users
ref = null;
} else if (includeCode) {
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}

LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
// 创建LoadedApk对象
packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);

if (mSystemThread && "android".equals(aInfo.packageName)) {
packageInfo.installSystemApplicationInfo(aInfo,
getSystemContext().mPackageInfo.getClassLoader());
}

if (differentUser) {
// Caching not supported across users
} else if (includeCode) {
mPackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
}
}
return packageInfo;
}
}

创建LoadedApk对象,并将将新创建的LoadedApk加入到mPackages,也就是说每个app都会创建唯一的LoadedApk对象。此处aInfo来源于ProcessRecord.info变量,也就是进程中的第一个app。

LoadedApk.makeApplication

getClassLoader

该方法跟system_server一样,其内调用的getClassLoader也调用了createOrUpdateClassLoaderLocked方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
private void createOrUpdateClassLoaderLocked(List<String> addedPaths) {
if (mPackageName.equals("android")) {
// ...
}

if (mRegisterPackage) {
try {
ActivityManager.getService().addPackageDependency(mPackageName);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

final List<String> zipPaths = new ArrayList<>(10);
final List<String> libPaths = new ArrayList<>(10);

final String defaultSearchPaths = System.getProperty("java.library.path");
final boolean treatVendorApkAsUnbundled = !defaultSearchPaths.contains("/vendor/lib");

makePaths(mActivityThread, isBundledApp, mApplicationInfo, zipPaths, libPaths);

String libraryPermittedPath = mDataDir;

final String librarySearchPath = TextUtils.join(File.pathSeparator, libPaths);

boolean needToSetupJitProfiles = false;
if (mClassLoader == null) {
mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip,
mApplicationInfo.targetSdkVersion, isBundledApp, librarySearchPath,
libraryPermittedPath, mBaseClassLoader,
mApplicationInfo.classLoaderName);
mAppComponentFactory = createAppFactory(mApplicationInfo, mClassLoader);
needToSetupJitProfiles = true;
}

List<String> extraLibPaths = new ArrayList<>(3);
String abiSuffix = VMRuntime.getRuntime().is64Bit() ? "64" : "";
if (!defaultSearchPaths.contains("/vendor/lib")) {
extraLibPaths.add("/vendor/lib" + abiSuffix);
}
if (!defaultSearchPaths.contains("/odm/lib")) {
extraLibPaths.add("/odm/lib" + abiSuffix);
}
if (!defaultSearchPaths.contains("/product/lib")) {
extraLibPaths.add("/product/lib" + abiSuffix);
}
if (!extraLibPaths.isEmpty()) {
StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskReads();
try {
ApplicationLoaders.getDefault().addNative(mClassLoader, extraLibPaths);
} finally {
StrictMode.setThreadPolicy(oldPolicy);
}
}

if (addedPaths != null && addedPaths.size() > 0) {
final String add = TextUtils.join(File.pathSeparator, addedPaths);
ApplicationLoaders.getDefault().addPath(mClassLoader, add);
// Setup the new code paths for profiling.
needToSetupJitProfiles = true;
}

if (needToSetupJitProfiles && !ActivityThread.isSystem()) {
setupJitProfileSupport();
}
}

// ApplicationLoaders.java
ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
String librarySearchPath, String libraryPermittedPath,
ClassLoader parent, String classLoaderName) {
return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
libraryPermittedPath, parent, zip, classLoaderName);
}

private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
String librarySearchPath, String libraryPermittedPath,
ClassLoader parent, String cacheKey, String classLoaderName) {
ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();

synchronized (mLoaders) {
if (parent == null) {
parent = baseParent;
}

if (parent == baseParent) {
ClassLoader loader = mLoaders.get(cacheKey);
if (loader != null) {
return loader;
}

ClassLoader classloader = ClassLoaderFactory.createClassLoader(
zip, librarySearchPath, libraryPermittedPath, parent,
targetSdkVersion, isBundled, classLoaderName);
GraphicsEnvironment.getInstance().setLayerPaths(
classloader, librarySearchPath, libraryPermittedPath);

mLoaders.put(cacheKey, classloader);
return classloader;
}

ClassLoader loader = ClassLoaderFactory.createClassLoader(
zip, null, parent, classLoaderName);
return loader;
}
}

// ClassLoaderFactory.java
public static ClassLoader createClassLoader(String dexPath,
String librarySearchPath, String libraryPermittedPath, ClassLoader parent,
int targetSdkVersion, boolean isNamespaceShared, String classloaderName) {

final ClassLoader classLoader = createClassLoader(dexPath, librarySearchPath, parent, classloaderName);
// ...
return classLoader;
}

public static ClassLoader createClassLoader(String dexPath,
String librarySearchPath, ClassLoader parent, String classloaderName) {
if (isPathClassLoaderName(classloaderName)) {
return new PathClassLoader(dexPath, librarySearchPath, parent);
} else if (isDelegateLastClassLoaderName(classloaderName)) {
return new DelegateLastClassLoader(dexPath, librarySearchPath, parent);
}

throw new AssertionError("Invalid classLoaderName: " + classloaderName);
}

Instrumentation.newApplication

逻辑与system_server类似。

总结

App进程的Application创建过程,跟system进程的核心逻辑都差不多。只是app进程多了两次binder调用。其中newApplication方法中通过类加载器加载了Application Class对象,并通过其newInstance方法创建了Application实例。

Application

概述

Android基于Linux内核,它将开源代码和闭源第三方应用程序结合在一起。Android的开源部分称为Android开源项目(Android Open Source Project,AOSP),它是完全开放的,任何人都可以免费使用和修改。

如同传统的Linux系统一样,Android的第一个用户空间进程是init,它是所有其他进程的根。与Android相关的内容可以看这里

Linux扩展

唤醒锁

移动设备上的电源管理不同于传统的计算机系统,所以,为了管理系统如何进入睡眠,Android为Linux添加了一个新的功能,称为唤醒锁(wake lock),也称为悬停阻止器(suspend blocker)。

在传统的计算机系统上,系统可以处于两种电源状态之一:运行并且准备好处理用户输人:或者深度睡眠,并且如果没有诸如按下电源键一类的外部中断就不能继续执行。在运行的时候,次要的硬件设备可以按需要通电或者断电,但是CPU本身以及核心硬件部件必须保持通电状态以处理到来的网络通信以及其他类似的事件。进人低能耗睡眠状态是发生得比较少的事情:或者通过用户明确地让系统睡眠,或者由于比较长的时间间隔没有用户活动,从而系统自身进入睡眠。从这样的睡眠状态醒来需要来自外部源的硬件中断,例如按下键盘上的一个按键,在此刻设备将醒来并且点亮屏幕。

移动设备的用户具有不同的期望。尽管用户可以关闭屏幕,在这样的情况下看起来好像是让设备睡眠了,但是传统的睡眠状态实际上并不是用户想得到的。当设备的屏幕关闭之时,设备仍然需要工作:它需要能够接听电话呼叫,接收并处理到来的聊天消息数据,以及许多其他事情。对于移动设备,关干打开和关闭设备屏幕的期望同样比传统的计算机具有更髙的要求。在这类典型的移动应用中,恢复设备直到它能够使用的任何延迟都会对用户体验造成严重的负面影响。

给定了这样的需求,一种解决方案或许仅仅是当设备的屏幕关闭之时不让CPU睡眠,这样它就总是准备好再次重新打开。归根到底,内核了解什么时候线程无需工作调度,并且Linux(以及大多数操作系统)将会自动地让CPU空闲,在这样的情况下使用较低的电能。

然而,空闲的CPU与真正的睡眠是不同的。

1)在许多芯片组上,空闲状态使用的电能比真正的睡眠状态要多得多。
2)空闲的CPU可以在任何时刻唤醒,只要某些工作赶巧变得可用,即使该工作是不重要的。
3)只是让CPU空闲并不意味着可以关闭其他硬件,而这样的硬件在真正的睡眠中是不需要的。

Android上的唤醒锁允许系统进入深度睡眠模式,而不必与一个明确的用户活动(例如关闭屏幕)绑在一起。具有唤醒锁的系统的默认状态是睡眠状态。当设备在运行时,为了保持它不回到睡眠,则需要持有一个唤醒锁。

当屏幕打开时,系统总是持有一个唤醒锁,这样就阻止了设备进入睡眠,所以它将保持运行。

然而,在屏幕关闭时,系统本身一般并不持有唤醒锁,所以只有在某些其他实体持有唤醒锁的条件下才能保持系统不进入睡眠。当没有唤醒锁被持有时,系统进入睡眠,并且只能由干硬件中断才能将其从睡眠中唤醒。

一旦系统已经进入睡眠,硬件中断可以将其再次唤醒,如同在传统操作系统中那样。这样的中断源有基于时间的鳖报、来自蜂窝无线电的事件(例如呼入的呼叫)、到来的网络通信以及按下特定的硬件按钮(例如电源按钮)。针对这些事件的中断处理程序要求对标准Linux做出一个改变:在处理完中断之后,它们需要获得一个初始的唤醒锁从而使系统保持运行。

中断处理程序获得的唤醒锁必须持有足够长的时间,以便能够沿着栈向上将控制传递给内核中的驱动程序,由其继续对事件进行处理。然后,内核驱动程序负责获得自己的唤醒锁,在此之后,中断唤醒锁可以安全地得到释放而不存在系统进入睡眠的风险。

如果在这之后驱动程序将该事件向上传送到用户空间,则需要类似的握手。驱动程序必须确保继续持有唤醒锁直到它将事件传递给等待的用户进程,并且要确保存在使用户进程获得自己的唤醒锁的条件。这一流程可能还会在用户空间的子系统之间继续,只要某个实体持有唤醒锁,我们就继续执行想要的处理以便响应事件。然而,一旦没有唤醒锁被持有,整个系统将返回睡眠并且所有进程停止。

内存不足杀手

Linux中的“内存不足杀手”(out-of-memory Killer)试图在内存极低时进行恢复,在现代操作系统上内存不足的情况是模糊的事情。由于有分页和交换,应用程序本身很难看到内存不足的错误。然而,内核仍然可能进入这样一种情形,当需要的时候找不到可用的RAM页面,不但对新的分配会这样,而且在换入或者分页入某些正在使用的地址范围时也可能如此。

在这样的低内存情形中,标准的Linux内存不足杀手是最后的应急手段,它试图找到RAM,使得内核能够继续处理它正在做的事情。做法是为每个进程分配一个“坏度” (badness)水平,并且简单地杀死最坏的进程。进程的坏度基于进程正在使用的RAM数量、它已经运行了多长时间以及其他因素,目标是杀死大量但愿不太重要的进程。

Android为内存不足杀手施加了特别的压力。它没有交换空间,所以它处于内存不足情形会更为常见:除非通过放弃从最近使用的存储器映射的干净的RAM页面,否则没有办法缓解内存压力。即便如此,Android还是使用标准Linux的配置,过度提交(over-commit)内存,也就是说,允许在RAM中分配地址空间而无需保证有可用的RAM对其提供后备。过度提交对于优化内存使用是一个极其重要的工具,这是因为mmap大文件(例如可执行文件)是很常见的,此处你只需要将该文件中全部数据的一小部分装入RAM。

考虑到这样的情形,常备的Linux内存不足杀手工作得不太好,因为它更多地被预定为最后的应急手段,并且很难正确地识别合理的进程来杀死。事实上,正如我们在后面要讨论的,Android广泛地依赖定期运行内存不足杀手以收割(reap)进程,并且对于选择哪个进程的问题做出好的选择。

为解决这一问题,Android为内核引入了自己的内存不足杀手,具有不同的语义和设计目标。Android的内存不足杀手运行得更加积极进取:只要RAM变“低”则运行。低的RAM是由一个可调整的参数标识的,该参数指示在内核中有多少空闲的和缓存的RAM是可接受的。当系统变得低于这个极限时,内存不足杀手便运行以便从别处释放RAM。目标是确保系统绝不会进入坏的分页状态,当前台应用程序竞争RAM时坏的分页状态会对用户体验造成负面影响,因为页面不断地换入换出会导致应用程序的执行变得非常缓慢。

与试图猜测哪个进程应该被杀死不同,Android的内存不足杀手非常严格地依赖由用户空间提供给它的信息。传统的Linux内存不足杀手具有每个进程的oom_adj参数,通过修改进程的总体坏度得分,该参数可用来指导选择最佳的进程并将其杀死。Android的内存不足杀手使用这个相同的参数,但是具有严格的顺序:具有较髙oom.adj的进程总是在那些具有较低oom_adj的进程之前被杀死。

Binder

概述

Binder机制如下图:

Binder机制

在栈的最底层是一个内核模块,实现了实际的跨进程交互,并且通过内核的ioctl函数将其展露(ioctl是一个通用的内核调用,用来发送定制的命令给内核驱动程序和模块。)在内核模块之上,是一个基本的面向对象的用户空间API,允许应用程序通过IBinder和Binder类创建并且与IPC端点进行交互。在顶部是一个基于接口的编程模型,应用程序在其中声明它们的IPC接口,并且不再需要关心IPC在底层是如何发生的细节问题。

Binder内核模块

Binder没有使用像管道这样的现有Linux IPC设施,Binder IPC模型与传统的Linux机制差别之大,使得它无法纯粹在用户空间及Linux机制之上来实现。此外,Android不支持大部分System V原语(信号量、共享内存段、消息队列)进行跨进程的交互,因为它们不能提供健壮的语义以清除来自有问题的或恶意的应用程序的资源。

Binder使用的基本IPC模型是远程过程调用(Remote Procedure Call, RPC)。也就是说,发送的进程向内核提交一个完整的IPC操作,该操作在接收的进程中被执行,当接收者执行时,发送者可以有选择地设定它们不阻塞,从而继续执行,与接收者并行。因此,Binder IPC是基于消息的,类似System V消息队列,而不是基于流的(如Linux管道)。Binder中的消息称为事务(transaction),在更高层可以被看作跨进程的函数调用。

用户空间提交给内核的每个事务是一个完整的操作:它标识操作的目标和发送者的标识符,以及交付的完整数据。内核决定适当的进程来接收该事务,将其交付给进程中等待的线程。

下图显示了事务的基本流程。发送的进程中任何线程都可能创建标识其目标的事务,并且将该事务提交给内核。内核制作事务的副本,将发送者的标识符添加到其中。内核确定由哪个进程负责事务的目标,并且唤醒接收事务的进程中的一个线程。一旦接收的进程执行起来,它要确定适当的事务目标并且交付。

基本的Binder-IPC事务

发送给内核的事务标识了一个目标对象,然而,内核必须确定接收进程。为实现这一点,内核跟踪每个进程中可用的对象,并将它们映射到其他进程,如下图所示。我们在这里看到的对象只是该进程地址空间中的地址。内核只是跟踪这些对象地址,并没有附着在它们之上的意义;它们可以是C数据结构的地址或者位于该进程地址空间中的任何其他东西。

Binder跨进程对象映射

远程进程中对干对象的引用由一个整数句柄(handle)来标识,这很像是Linux的文件描述符。

在进程中传输Binder对象

上图中的主要步骤如下:

1)进程1创建一个初始的事务结构,其中包含对象lb的本地地址。
2)进程1提交事务到内核。
3)内核査看事务中的数据,找到地址对象lb,并且创建一个针对它的新条目,因为它以前并不知道该地址。
4)内核利用事务的目标句柄2来确定它意在进程2中的对象2。
5)内核现在将事务头重写,使其适合进程2,改变其目标为地址对象2a。
6)内核同样为目标进程重写事务数据;此处它发现对象lb还不被进程2所知,所以为它创建一个新的句柄3。
7)重写的事务被交付给进程2来执行。
8)—旦接收到事务,进程会发现新的句柄3,并将其添加到可用句柄表中。

如果事务内部的一个对象已经由接收进程知晓,则流程是类似的,差别在于现在内核只需要重写事务,使得事务包含此前已分配的句柄或者接收进程的本地对象指针。这意味着,发送相同的对象到一个进程很多次,总是会得到相同的标识,这与Linux文件描述符不同,在Linux中打开相同的文件多次,每次会分配不同的文件描述符。当对象在进程之间传递时,Binder IPC系统将维护唯一的对象标识。

Binder体系结构本质上为Linux引入了一个基于能力的安全模型。每一个Binder对象是一个能力。发送一个对象到另一个进程就是将能力授予该进程。于是,接收进程可以使用对象提供的一切功能。进程可以送出一个对象到另一个进程,然后从任何进程接收一个对象,并且识别接收到的对象是否正是它最初送出的那个对象。

Binder用户空间API

大多数用户空间代码不直接与Binder内核模块交互,存在一个用户空间的面向对象的库,它提供了更加简单的API。这些用户空间API的第一层相当直接地映射到之前讨论过的内核概念,采用如下三个类的形式。

1)IBinder是Binder对象的抽象接口。其关键方法是transact,它将一个事务提交给对象。接收事务的实现可能是本地进程中的一个对象,或者是另一个进程中的对象;如果它在另一个进程中,则将会通过如前面讨论的Binder内核模块交付给它。
2)Binder是一个具体的Binder对象。实现一个Binder子类将给你一个可以从其他进程调用的类。其关键方法是onTransact,它接收发送给它的一个事务。Binder子类的主要责任是查看它接收的事务数据,并且执行适当的操作。
3)Parcel是一个容器,用于读和写Binder事务中的数据。

Binder用户空间API

从上图可以看到Binderlb和Binder2a是具体Binder子类的实例。为了执行一个IPC,进程现在要创建一个包含期望数据的Parcel,并且通过BinderProxy将其发送。只要一个新的句柄出现在进程之中,此类就将被创建,因此提供了IBinder的实现,它的transact方法将为调用创建适当的事务并将其提交到内核。因此,我们在前面讨论过的内核事务结构在用户空间API中拆开了:目标由BinderProxy代表,并且其数据保存在一个Parcel之中:事务如我们前面看过的那样流过内核,一旦出现在接收进程的用户空间中,它的目标将用来确定适当的接收Binder对象,而一个Parcel将从其数据构造出来并且交付给对象的onTransact方法。

Binder接口和AIDL

这一层主要的部分是一个命令行工具,称为AIDL(Android Interface Definition Language, Android接口定义语言)。该工具是一个接口编译器,它以接口的抽象描述为输入,生成定义接口所必需的源代码,并且实现适当的编组和解组代码,这样的代码是进行远程调用所需要的。

安全性

Android的应用程序安全性围绕着UID展开。在Linux中,每个进程在运行时拥有一个独特的UID,Android使用UID来识别与保护安全屏障。进程进行交互的唯一手段是利用跨进程通信(IPC)机制,携带足以使它识别调用者的信息。binder IPC在毎个跨进程的事务中明确包含了这些信息,确保IPC的接收者能简单地请求调用者的UID。

Android为系统底层预先定义了一系列标准UID,但大多数应用程序是在其第一次运行或安装时,从“应用程序UID”范围中获得动态分配的UID的。下表是一些常用的UID:

UID值 用途
0
1000 核心系统(system server进程)
1001 电话服务
1013 底层媒体进程
2000 命令行界面访问
10000-19999 动态分配应用程序U1D
100000 多用户由此开始

当一个应用程序首次被分配一个UID时,随之将创造一个新的存储目录,用来存储这个UID拥有的文件。应用程序可以自由访问该目录中它的私有文件,但不能访问其他应用程序的文件。反过来,其他应用程序也不能访问它的文件。

Linux简介

关于POSIX的介绍在这里。Linux系统的层次结构如下:

Linux层次结构

内核坐落在硬件之上,负责实现与I/O设备和存储管理单元的交互,并控制CPU对前述设备的访问。如图所示,在最底层,内核包含中断处理程序,它们是与设备交互的主要方式,以及底层的分派机制。这种分派在中断时发生。底层的代码中止正在运行的进程,将其状态存储在内核进程结构中,然后启动相应的驱动程序。进程分派也在内核完成某些操作,并且需要再次启动一个用户进程时发生。进程分派的代码是汇编代码,并且和进程调度代码有很大不同。

Linux内核结构

Linux系统进程

Linux系统内存管理

Linux I/O

Linux文件系统

Linux系统启动

当计算机启动时,BIOS加电自检,并对硬件进行检测和初始化,这是因为操作系统的启动过程可能会依赖于磁盘访问、屏幕、键盘等。接下来,启动磁盘的第一个扇区,即主引导记录(MBR),被读入到一个固定的内存区域并且执行。这个分区中含有一个很小的程序(只有512字节),这个程序从启动设备中,比如SATA磁盘或SCSI磁盘,调入一个名为boot的独立程序。boot程序将自身复制到髙地址的内存当中从而为操作系统释放低地址的内存。

复制完成后,boot程序读取启动设备的根目录。为了达到这个目的,boot程序必须能够理解文件系统和目录格式,这个工作通常由引导程序,如GRUB(多系统启动管理器)来完成。

然后,boot程序读入操作系统内核,并把控制交给内核。从这里开始,boot程序完成了它的任务,系统内核开始运行。

内核的启动代码是用汇编语言写成的,具有较髙的机器依赖性。主要的工作包括创建内核堆栈、识别CPU类型、计算可用内存、禁用中断、启用内存管理单元,最后调用C语言写成的main函数开始执行操作系统的主要部分。

C语言代码也有相当多的初始化工作要做,但是这些工作更逻辑化(而不是物理化)。

接下来,内核数据结构得到分配。大部分内核数据结构的大小是固定的,但是一少部分,如页面缓存和特殊的页表结构,依赖于可用内存的大小。

从这里开始,系统进行自动配置。使用描述何种设备可能存在配置文件,系统开始探测哪些设备是确实存在的。如果一个被探测的设备给出了响应,这个设备就会被加入到已连接设备表中。如果它没有响应,就假设它未连接或直接忽略掉它。不同于传统的UNIX版本,Linux系统的设备驱动程序不需要被静态链接至内核中,它们可以被动态加载。

一旦所有的硬件都配置好了,接下来要做的事情就是细心地手动运行进程0,建立它的堆栈,运行它。进程0继续进行初始化,做如下的工作:配置实时时钟,挂载根文件系统,创建init进程(进程1)和页面守护进程(进程2)。

init进程检测它的标志以确定它应该为单用户还是多用户服务。

  • 单用户:调用fork函数创建一个shell进程,并且等待这个进程结束。
  • 多用户:调用fork函数创建一个运行系统初始化shell脚本(即/etc/rc)的进程,这个进程可以进行文件系统一致性检测、挂载附加文件系统、开启守护进程等。然后这个进程从/etc/ttys中读取数据,其中/etc/ttys列出了所有的终端和它们的属性。

在多用户服务中,对于每一个启用的终端,/etc/rc进程调用fork函数创建一个自身的副本,进行内部处理并运行一个名为getty的程序。getty程序为每条连线设置传输速率和其他属性(比如,有一些可能是调制解调器),然后在终端的屏幕上输出:

1
login:

等待用户从键盘键入用户名。当有人坐在终端前,提供了一个用户名后,getty程序就结束了,登录程序/bin/login开始运行。login程序要求输入密码,给密码加密,并与保存在密码文件/etc/passwd中的加密密码进行对比。如果是正确的,login程序以用户shell程序替换自身,等待第一个命令。如果是不正确的,login程序要求输入另一个用户名。

Linux的安全性

基本概念一个Linux系统的用户群体由一定数量的注册用户组成,其中毎个用户拥有一个唯一的UID(用户ID)。UID是介干0到65535之间的一个整数。文件(进程及其他资源)都标记了它的所有者的UID。尽管可以改变文件所有权,但是默认情况下,文件的所有者是创建该文件的用户。

用户可以被分组,其中每组同样由一个16位的整数标记,叫作GID(组ID)。给用户分组通过在系统数据库中添加一条记录指明哪个用户属于哪个组的方法手工(由系统管理员)完成。一个用户可以同时属于多个组。

Linux中的基本安全机制很简单。每个进程记录它的所有者的UID和GID。当一个文件被创建时,它的UID和GID被标记为创建它的进程的UID和GID。该文件同时获得由该进程决定的一些权限。这些权限指定所有者、所有者所在组的其他用户及其他用户对文件具有什么样的访问权限。对干这三类用户而言,潜在的访问权限为读、写和执行,分别由r、w和x标记。当然,执行文件的权限仅当文件是可执行二进制程序时才有意义。试图执行一个拥有执行权限的非可执行文件(即,并非由一个合法的文件头开始的文件)会导致错误。因为有三类用户,每类用户的权限由3个比特位标记,那么9个比特位就足够标记访问权限。下图给出了一些9位数字及其含义的例子:

二进制 标记 准许的文件访问权限
111000000 rwx—— 所有者可以读、写和执行
111111000 rwxrwx— 所有者和组可以读、写和执行
110100100 rw-r—– 所有者可以读和写;组可以读
110100100 rw-r–r– 所有者可以读和写;其他人可以读
111101101 rwxr-xr-x 所有者拥有所有权限,其他人可以读和执行
000000000 ——— 所有人都不拥有任何权限
000000111 ——rwx 只有组以外的其他用户拥有所有权限(奇怪但是合法)

UID为0的用户是一个特殊用户,称为超级用户(或者根用户)。超级用户能够读和写系统中的任何文件,不论这个文件为谁所有,也不论这个文件的保护模式如何。UID为0的进程拥有调用一小部分受保护的系统调用的权限,而普通用户是不能调用这些系统调用的。

目录也是一种文件,并且具有普通文件一样的保护模式。不同的是,目录的x比特位表示査找权限而不是执行权限。因此,如果一个目录具有保护模式rwxr-xr-x,那么它允许所有者读、写和查找目录,但是其他人只可以读和查找,而不允许从中添加或者删除目录里的文件。

与I/O相关的特殊文件拥有与普通文件一样的保护位。这种机制可以用来限制对I/O设备的访问权限。例如,假设打印机是特殊文件——/dev/lp,可以被根用户或者一个叫守护进程的特殊用户拥有,具有保护模式rw--------,从而阻止其他所有人对打印机的访问权限。当然,这意味着其他任何人都不可以使用打印机,这种做法限制了很多合法的打印要求。

事实上,允许对I/O设备及其他系统资源进行受控访问的做法具有一个更普遍的问题。这个问题通过增加一个保护位SETUID到之前的9个比特位来解决。当一个进程的SETUID位打开,它的有效UID将变成相应可执行文件的所有者的UID,而不是当前使用该进程的用户的UID。当一个进程试图打开一个文件时,系统检査的将是它的有效UID,而不是真正的UID。将访问打印机的程序设置为被守护进程所有,同时打开SETUID位,这样任何用户都可以执行该程序,并拥有守护进程的权限(例如访问/dep/lp),但是这仅限于运行该程序(例如给打印任务排序)。

许多敏感的Linux程序通过打开SETUID位被根用户所有。例如,允许用户改变密码的程序需要写password文件。允许password文件公开可写显然不是个好主意。解决的方法是,提供一个被根用户所有同时SETUID位打开的程序。虽然该程序拥有对password文件的全部权限,但是它仅仅改变调用该程序的用户的密码,而不允许其他任何的访问权限。

除了SETUID位,还有一个SETGID位,工作原理同SETUID类似。它暂时性地给用户该程序的有效GID。然而在实践中,这个位很少用到。

当用户登录时,登录程序login(为根用户所有且SETUID打开)要求输入登录名和密码。它首先计算密码的散列值,然后在/etc/passwd文件中査找,看是否有相匹配的项(网络系统工作得稍有不同)。使用散列的原因是防止密码在系统中以非加密的方式存在。如果密码正确,登录程序在/etc/passwd中读取该用户选择的shell程序的名称,例如可能是bash,但是也有可能是其他的shell。然后登录程序使用setuid和setgid来使自己的UID和GID变成用户的UID和GID(注意,它一开始的时候是根用户所有且SETUID打开)。然后它打开键盘作为标准输入(文件描述符0),屏幕为标准输出(文件描述符1),屏幕为标准错误输出(文件描述符2)。最后,执行用户选择的shell程序,因此终止自己。

到这里,用户选择的shell已经在运行,并且被设置了正确的UID和GID,标准输人、标准输出和标准错误输出都被设置成了默认值。它创建任何子进程(也就是用户输入的命令)都将自动继承shell的UID和GID,所以它们将拥有正确的UID和GID,这些进程创建的任何文件也具有这些值。

当任何进程想要打开一个文件,系统首先将文件的i节点所记录的保护位与用户的有效UID和有效GID对比,来检査访问是否被允许。如果允许访问,就打开文件并且返回文件描述符:否则不打开文件,返回-1。在接下来的read和write中不再检査权限。因此,当一个文件的保护模式在它被打开后修改,新模式将无法影响已经打开该文件的进程。

Linux安全模型及其实现在本质上跟其他大多数传统的UNIX系统相同。

机械硬盘

盘片/盘面/磁头

硬盘中一般会有多个盘片组成,每个盘片包含两个面,每个盘面都对应地有一个读/写磁头。受到硬盘整体体积和生产成本的限制,盘片数量都受到限制,一般都在5片以内。盘片的编号自下向上从0开始,如最下边的盘片有0面和1面,再上一个盘片就编号为2面和3面。如下图:

盘片-盘面-磁头

扇区/磁道

下图显示的是一个盘面,盘面中一圈圈灰色同心圆为一条条磁道,从圆心向外画直线,可以将磁道划分为若干个弧段,每个磁道上一个弧段被称之为一个扇区(图践绿色部分)。扇区是磁盘的最小组成单元,通常是512字节。(由于不断提高磁盘的大小,部分厂商设定每个扇区的大小是4096字节)

扇区-磁道

磁头/柱面

硬盘通常由重叠的一组盘片构成,每个盘面都被划分为数目相等的磁道,并从外缘的“0”开始编号,具有相同编号的磁道形成一个圆柱,称之为磁盘的柱面。磁盘的柱面数与一个盘面上的磁道数是相等的。由于每个盘面都有自己的磁头,因此,盘面数等于总的磁头数。如下图:

磁头-柱面

磁盘容量计算

存储容量 = 磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数

上图中磁盘是一个3个圆盘6个磁头,7个柱面(每个盘片7个磁道)的磁盘,图3中每条磁道有12个扇区,所以此磁盘的容量为:

1
6 * 7 * 12 * 512 = 258048

每个磁道的扇区数一样是说的老的硬盘,外圈的密度小,内圈的密度大,每圈可存储的数据量是一样的。新的硬盘数据的密度都一致,这样磁道的周长越长,扇区就越多,存储的数据量就越大。

磁盘读取响应时间

  • 寻道时间:磁头从开始移动到数据所在磁道所需要的时间,寻道时间越短,I/O操作越快,目前磁盘的平均寻道时间一般在3-15ms,一般都在10ms左右。
  • 旋转延迟:盘片旋转将请求数据所在扇区移至读写磁头下方所需要的时间,旋转延迟取决于磁盘转速。普通硬盘一般都是7200rpm,慢的5400rpm。
  • 数据传输时间:完成传输所请求的数据所需要的时间。

小结一下:从上面的指标来看、其实最重要的或者说最关心的应该只有两个:寻道时间;旋转延迟。

读写一次磁盘信息所需的时间可分解为:寻道时间、延迟时间、传输时间。为提高磁盘传输效率,软件应着重考虑减少寻道时间和延迟时间。

块/簇

磁盘块/簇是虚拟出来的,块是操作系统中最小的逻辑存储单位。操作系统与磁盘打交道的最小单位是磁盘块。

通俗的来讲,在Windows下如NTFS等文件系统中叫做簇;在Linux下如Ext4等文件系统中叫做块(block)。每个簇或者块可以包括2、4、8、16、32、64等的n次方个扇区。

优点:

  • 读取方便:由于扇区的数量比较小,数目众多在寻址时比较困难,所以操作系统就将相邻的扇区组合在一起,形成一个块,再对块进行整体的操作。
  • 分离对底层的依赖:操作系统忽略对底层物理存储结构的设计。通过虚拟出来磁盘块的概念,在系统中认为块是最小的单位。

操作系统经常与内存和硬盘这两种存储设备进行通信,类似于“块”的概念,都需要一种虚拟的基本单位。所以,与内存操作,是虚拟一个页的概念来作为最小单位。与硬盘打交道,就是以块为最小单位。

扇区、块/簇、page的关系:

  • 扇区:硬盘的最小读写单元
  • 块/簇:是操作系统针对硬盘读写的最小单元
  • 页:是内存与操作系统之间操作的最小单元。
  • 扇区 <= 块/簇 <= page

固态硬盘

固态硬盘和机械硬盘分别采用两种当下流行的持久储存方式(区别于内存断电即失去数据的储存)。

固态硬盘跟机械硬盘不一样。固态硬盘:是由电子芯片及电路板,没有磁头,没有磁盘(机械硬盘靠磁盘转动读取数据),固态硬盘不是机械操作的。

机械硬盘使用磁为介质保存数据,利用磁头读写圆盘面微小磁体的磁极来实现数据读写。

固态硬盘采用闪存为储存介质,和U盘的原理几乎一样,闪存外观很像CPU、内存那种芯片,也和内存一样内部没有任何活动的机械部件。

机械硬盘在不同位置读写时,磁头要来回运动,在同一个地方连续写入一段数据时效率更高,随机乱序读写时效率很低。而闪存不用,随机读写和顺序读写几乎没有速度区别。

普通U盘单个闪存芯片的读写速度无法及得上机械硬盘,而固态硬盘实际是闪存整列,利用多个闪存并行读写来达到甚至超过机械硬盘的读写速度,并行的闪存数无上限,读写速度、容量与闪存芯片数成正比,但价格也成正比。

固态硬盘除了速度高,还有功耗低、无噪音、低故障率的优点。理论上闪存有写次数限制,以目前技术来说同一个位置写超过3000次就会失效并故障,但固态硬盘采用不同于机械硬盘的写入策略,将写入操作平均分摊在整个空间内,所以在普通家用中几乎不会有些写坏的那一天,这也是固态硬盘与普通闪存的本质区别。

文件系统的实现

文件系统布局

文件系统存放在磁盘上。多数磁盘划分为一个或多个分区,每个分区中有一个独立的文件系统。磁盘的0号扇区称为主引导记录(Master Boot Record,MBR)**,用来引导计算机。在MBR的结尾是分区表。该表给出了毎个分区的起始和结束地址。表中的一个分区被标记为活动分区。在计算机被引导时,BIOS读入并执行MBR。MBR做的第一件事是确定活动分区,读入它的第一个块,称为引导块**(boot block),并执行之。引导块中的程序将装载该分区中的操作系统。为统一起见,毎个分区都从一个引导块开始,即使它不含有一个可启动的操作系统。不过,未来这个分区也许会有一个操作系统的。

一个可能的文件系统布局如下:

文件系统布局

  • 超级块:超级块包含文件系统的所有关键参数,在计算机启动时,或者在该文件系统首次使用时,超级块会被读入内存。超级块中的典型信息包括:确定文件系统类型用的魔数、文件系统中块的数量以及其他重要的管理信息。
  • 空闲块:可以用位图或指针列表的形式给出。
  • i节点:一个数据结构数组,每个文件一个,i节点说明了文件的方方面面。
  • 根目录:存放文件系统目录树的根部。
  • 目录和文件。

文件的实现

文件存储实现的关键问题是记录各个文件分别用到哪些磁盘块,不同操作系统采用不同的方法。

连续分配

最简单的分配方案是把每个文件作为一连串连续数据块存储在磁盘上。所以,在块大小为1KB的磁盘上,50KB的文件要分配50个连续的块。对于块大小为2KB的磁盘,将分配25个连续的块。

每个文件都从一个新的块开始,因此可能会在结尾浪费一些空间。

优点:

  • 实现简单
  • 读操作性能好,因为在单个操作中就可以从磁盘上读出整个文件。只需要一次寻找(对第一个块)。之后不再需要寻道和旋转延迟,所以,数据以磁盘全带宽的速率输入。

缺点:

  • 随着时间的推移,磁盘会变得零碎。因为删除文件时,会造成空洞,而挤掉这些空洞可能需要复制大量的文件,代价太高。
  • 可以维护一个空洞列表,但是为了录入一个文档,用户启动了文本编辑器或字处理软件,程序首先询问最终文件的大小会是多少。如果用户为了避免这个问题而给出不实际的较大的数字作为最后文件的大小,则可能由于不存在这么大的空洞而使得创建失败。
  • 空洞列表这种方式在CD-ROM上被广泛使用。在这里所有文件的大小都事先知道,并且在CD-ROM文件系统的后续使用中,这些文件的大小也不再改变。

链表分配

存储文件的第二种方法是为每个文件构造磁盘块链表,每个块的第一个字作为指向下一块的指针,块的其他部分存放数据。与连续分配方案不同,这一方法可以充分利用每个磁盘块。同样,在目录项中,只需要存放第一块的磁盘地址,文件的其他块就可以从这个首块地址査找到。

另一方面,在链表分配方案中,尽管顺序读文件非常方便,但是随机访问却相当缓慢。要获得块n,操作系统每一次都必须从头开始,并且要先读前面的块。显然,进行如此多的读操作太慢了。

而且,由于指针占去了一些字节,每个磁盘块存储数据的字节数不再是2的整数次幂。虽然这个问题并不是非常严重,但是也会降低系统的运行效率,因为许多程序都是以长度为2的整数次幂来读写磁盘块的。由于每个块的前几个字节被指向下一个块的指针所占据,所以要读出完整的一个块大小的信息,就需要从两个磁盘块中获得和拼接信息,这就因复制引发了额外的开销。

文件分配表(内存)

如果取出每个磁盘块的指针字,把它们放在内存的一个表中,就可以解决上述链表的两个不足。

假设文件A依次使用了磁盘块4、7、2、10和12,文件B依次使用了磁盘块6、3、11和14。可以使用内存中的文件分配表(File Allocation Table, FAT)如下:

文件分配表

按这类方式组织,整个块都可以存放数据。进而,随机访问也容易得多。虽然仍要顺着链在文件中査找给定的偏移量,但是整个链都存放在内存中,所以不需要任何磁盘引用。与前面的方法相同,不管文件有多大,在目录项中只需记录一个整数(起始块号),按照它就可以找到文件的全部块。

这种方法的主要缺点是必须把整个表都存放在内存中,可能并不实用。

i节点

最后一个记录各个文件分别包含哪些磁盘块的方法是给每个文件赋予一个称为i节点的数据结构,其中列出了文件属性和文件块的磁盘地址。如下图:

i节点

给定i节点,就能找到文件的所有块。相对于在内存中采用表的方式而言,这种机制具有很大的优势,即只有在对应文件打开时,其i节点才在内存中。

如果每个i节点只能存储固定数量的磁盘地址,那么当一个文件所含的磁盘块的数目超出了i节点所能容纳的数目时,一个解决方案是最后一个“磁盘地址”不指向数据块,而是指向一个包含额外磁盘块地址的块的地址,如上图所示。

更髙级的解决方案是:可以有两个或更多个包含磁盘地址的块,或者指向其他存放地址的磁盘块的磁盘块。

目录的实现

目录系统的主要功能是把ASCII文件名映射成定位文件数据所需的信息。与此密切相关的问题是在何处存放文件属性。每个文件系统维护诸如文件所有者以及创建时间等文件属性,它们必须存储在某个地方。

一种显而易见的方法是把文件属性直接存放在目录项中,很多系统确实是这样实现的。在这个简单设计中,目录中有一个固定大小的目录项列表,每个文件对应一项,其中包含一个(固定长度)文件名、一个文件属性的结构体以及用以说明磁盘块位置的一个或多个磁盘地址(至某个最大值)。

对于采用i节点的系统,还存在另一种更好的方法,即把文件属性存放在i节点中而不是目录项中。在这种情形下,目录项会更短:只有文件名和i节点号。

几乎所有的现代操作系统都支持可变长度的长文件名,有多种实现方式。

最简单的方法是给予文件名一个长度限制,典型值为255个字符,但是会浪费大量的空间。

一种替代方案是放弃“所有目录项大小一样”的想法。这种方法中,每个目录项有一个固定部分,这个固定部分通常以目录项的长度开始,后面是固定格式的数据,通常包括所有者、创建时间、保护信息以及其他属性。这个固定长度的头的后面是一个任意长度的实际文件名,为了使每个目录项从字的边界开始,每个文件名被填充成整数个字。

这个方法的缺点是,当移走文件后,就引入了一个长度可变的空隙,而下一个进来的文件不一定正好适合这个空隙。这个问题与我们已经看到的连续磁盘文件的问题是一样的,由于整个目录在内存中,所以只有对目录进行紧凑操作才可节省空间。另一个问题是,一个目录项可能会分布在多个页面上,在读取文件名时可能发生缺页中断。

处理可变长度文件名字的另一种方法是,使目录项自身都有固定长度,而将文件名放置在目录后面的堆中,这一方法的优点是,当一个文件目录项被移走后,另一个文件的目录项总是可以适合这个空隙。当然,必须要对堆进行管理,而在处理文件名时缺页中断仍旧会发生。另一个小优点是文件名不再需要从字的边界开始。

到目前为止,在需要査找文件名时,所有的方案都是线性地从头到尾对目录进行捜索。对于非常长的目录,线性査找就太慢了。加快査找速度的一个方法是在每个目录中使用散列表。使用散列表的优点是査找非常迅速。其缺点是需要复杂的管理。只有在预计系统中的目录经常会有成百上千个文件时,才把散列方案真正作为备用方案考虑。

一种完全不同的加快大型目录査找速度的方法是,将査找结果存入髙速缓存。在开始査找之前,先査看文件名是否在髙速缓存中。如果是,该文件可以立即定位。当然,只有在査询目标集中在相对小范围的文件集合的时候,髙速缓存的方案才有效果。

共享文件

存在共享文件的文件系统如下图:

共享文件

C的一个文件现在也出现在B的目录下。B的目录与该共享文件的联系称为一个链接(link)。这样,文件系统本身是一个有向无环图(Directed Acyclic Graph,DAG)而不是一棵树。将文件系统组织成有向无环图使得维护复杂化,但也是必须付出的代价。

如果目录中包含磁盘地址,则当链接文件时,必须把C目录中的磁盘地址复制到B目录中。如果B或C随后又往该文件中添加内容,则新的数据块将只列入进行添加工作的用户的目录中。其他的用户对此改变是不知道的。所以违背了共享的目的。

第一种解决方案是,磁盘块不列入目录,而是列入一个与文件本身关联的小型数据结构中。目录将指向这个小型数据结构。这是UNIX系统中所采用的方法(小型数据结构即是i节点)。

第二种解决方案是,让系统建立一个类型为LINK的新文件,并把该文件放在B的目录下,使得B与C的一个文件存在链接。新的文件中只包含了它所链接的文件的路径名。当B读该链接文件时,操作系统査看到要读的文件是LINK类型,则找到该文件所链接的文件的名字,并且去读那个文件。与传统(硬)链接相对比起来,这一方法称为符号链接(symbolic linking)。

第一种方法中,当B链接到共享文件时,i节点记录文件的所有者是C。建立一个链接并不改变所有关系,但它将i节点的链接计数加1,所以系统知道目前有多少目录项指向这个文件。如果以后C试图删除这个文件,系统将面临问题。如果系统删除文件并清除i节点,B则有一个目录项指向一个无效的i节点。如果该i节点以后分配给另一个文件,则B的链接指向一个错误的文件。系统通过i节点中的计数可知该文件仍然被引用,但是没有办法找到指向该文件的全部目录项以刪除它们。指向目录的指针不能存储在i节点中,原因是有可能有无数个这样的目录。唯一能做的就是只删除C的目录项,但是将i节点保留下来,并将计数置为1,而现在的状况是,只有B有指向该文件的目录项,而该文件的所有者是C。如果系统进行记账或有配额,那么C将继续为该文件付账直到B决定删除它,如果真是这样,只有到计数变为0的时刻,才会刪除该文件。

对于符号链接,以上问题不会发生,因为只有真正的文件所有者才有一个指向i节点的指针。链接到该文件上的用户只有路径名,没有指向i节点的指针。当文件所有者删除文件时,该文件被销毁。以后若试图通过符号链接访问该文件将导致失败,因为系统不能找到该文件。删除符号链接根本不影响该文件。

符号链接的问题是需要额外的开销。必须读取包含路径的文件,然后要一个部分一个部分地扫描路径,直到找到i节点。这些操作也许需要很多次额外的磁盘访问。此外,每个符号链接都需要额外的i节点,以及额外的一个磁盘块用于存储路径,虽然如果路径名很短,作为一种优化,系统可以将它存储在i节点中。符号链接有一个优势,即只要简单地提供一个机器的网络地址以及文件在该机器上驻留的路径,就可以链接全球任何地方的机器上的文件。

还有另一个由链接带来的问题,在符号链接和其他方式中都存在。如果允许链接,文件有两个或多个路径。査找一指定目录及其子目录下的全部文件的程序将多次定位到被链接的文件。例如,一个将某一目录及其子目录下的文件转储到磁带上的程序有可能多次复制一个被链接的文件。进而,如果接着把磁带读进另一台机器,除非转储程序具有智能,否则被链接的文件将被两次复制到磁盘上,而不是只是被链接起来。

文件系统管理和优化

磁盘空间管理

几乎所有的文件系统都把文件分割成固定大小的块来存储,各块之间不一定相邻。

块大小

记录空闲块

  • 磁盘块链表,链表的每个块中包含尽可能多的空闲磁盘块号。
  • 位图,n个块的磁盘需要n位位图,空闲与否用二进制表示。

磁盘配额

为了防止人们贪心而占有太多的磁盘空间,多用户操作系统常常提供一种强制性磁盘配额机制。其思想是系统管理员分给每个用户拥有文件和块的最大数量,操作系统确保每个用户不超过分给他们的配额。

文件系统备份

磁盘转储到磁带上有两种方案:物理转储和逻辑转储。

  • 物理转储是从磁盘的第0块开始,将全部的磁盘块按序输出到磁带上,直到最后一块复制完毕。
  • 逻辑转储从一个或几个指定的目录开始,递归地转储其自给定基准日期。

文件系统的一致性

影响文件系统可靠性的另一个问题是文件系统的一致性。很多文件系统读取磁盘块,进行修改后,再写回磁盘。如果在修改过的磁盘块全部写回之前系统崩溃,则文件系统有可能处于不一致状态。如果一些未被写回的块是i节点块、目录块或者是包含有空闲表的块时,这个问题尤为严重。

为了解决文件系统的不一致问题,很多计算机都带有一个实用程序以检验文件系统的一致性。例如,UNIX有fsck,而Windows用scandisk。系统启动时,特别是崩溃之后的重新启动,可以运行该实用程序。

下面介绍一下UNIX下fsck的用法。

一致性检査分为两种:块的一致性检査和文件的一致性检査。在检査块的一致性时,程序构造两张表,每张表中为每个块设立一个计数器,都初始化为0。第一个表中的计数器跟踪该块在文件中的出现次数,第二个表中的计数器跟踪该块在空闲表或空闲位图中的出现次数。

接着检验程序使用原始设备读取全部的i节点,忽略文件的结构,只返回从零开始的所有磁盘块。由i节点开始,可以建立相应文件中用到的全部块的块号表。每当读到一个块号时,该块在第一个表中的计数器加1。然后,该程序检查空闲表或位图,査找全部未使用的块。每当在空闲表中找到一个块时,就会使它在第二个表中的相应计数器加1。

文件系统一致性

如果文件系统一致,则每一块或者在第一个表计数器中为1,或者在第二个表计数器中为1,如图a所示。

但是当系统崩溃后,这两张表可能如图b所示,其中,磁盘块2没有出现在任何一张表中,这称为块丢失。尽管块丢失不会造成实际的损害,但它的确浪费了磁盘空间,减少了磁盘容量。块丢失问题的解决很容易:文件系统检验程序把它们加到空闲表中即可。

有可能出现的另一种情况如图c所示。其中,块4在空闲表中出现了2次(只在空闲表是真正意义上的一张表时,才会出现重复,在位图中,不会发生这类情况)。解决方法也很简单:只要重新建立空闲表即可。

最糟的情况是,在两个或多个文件中出现同一个数据块,如图d中的块5。如果其中一个文件被删除,块5会添加到空闲表中,导致一个块同时处于使用和空闲两种状态。若删除这两个文件,那么在空闲表中这个磁盘块会出现两次。文件系统检验程序可以采取相应的处理方法是,先分配一空闲块,把块5中的内容复制到空闲块中,然后把它插到其中一个文件之中。这样文件的内容未改变(虽然这些内容几乎可以肯定是不对的),但至少保持了文件系统的一致性。这一错误应该报告,由用户检査文件受损情况。

除检査每个磁盘块计数的正确性之外,文件系统检验程序还检査目录系统。此时也要用到一张计数器表,但这时是一个文件(而不是一个块)对应于一个计数器。程序从根目录开始检验,沿着目录树递归下降,检查文件系统中的每个目录。对每个目录中的每个文件,将文件使用计数器加1。要注意,由于存在硬链接,一个文件可能出现在两个或多个目录中。而遇到符号链接是不计数的,不会对目标文件的计数器加1。

在检验程序全部完成后,得到一张由i节点号索引的表,说明每个文件被多少个目录包含。然后,检验程序将这些数字与存储在文件i节点中的链接数目相比较。当文件创建时,这些计数器从1开始,随着每次对文件的一个(硬)链接的产生,对应计数器加1。如果文件系统一致,这两个计数应相等。但是,有可能出现两种错误,即i节点中的链接计数太大或者太小。

如果i节点的链接计数大于目录项个数,这时即使所有的文件都从目录中删除,这个计数仍是非0,i节点不会被删除。该错误并不严重,却因为存在不属于任何目录的文件而浪费了磁盘空间。为改正这一错误,可以把i节点中的链接计数设成正确值。

另一种错误则是潜在的灾难。如果同一个文件链接两个目录项,但其i节点链接计数只为1,如果删除了任何一个目录项,对应i节点链接计数变为0。当i节点计数为0时,文件系统标志该i节点为“未使用”,并释放其全部块。这会导致其中一个目录指向一未使用的i节点,而很有可能其块马上就被分配给其他文件。解决方法同样是把i节点中链接计数设为目录项的实际个数值。

文件系统性能

访问磁盘比访问内存慢得多,考虑到访问时间的这个差异,许多文件系统采用了各种优化措施以改善性能。

高速缓存

管理髙速缓存有不同的算法,常用的算法是:检査全部的读请求,査看在高速缓存中是否有所需要的块。如果存在,可执行读操作而无须访问磁盘。如果该块不在髙速缓存中,首先要把它读到高速缓存,再复制到所需地方。之后,对同一个块的请求都通过髙速缓存完成。

由于在高速缓存中有许多块,通常通过散列表查找,相同散列值的块在一个链表中连接在一起。置换高速缓存的方法与页表置换的算法类似。

块提前读

在需要用到块之前,试图提前将其写入高速缓存,从而提髙命中率。块提前读策略只适用于实际顺序读取的文件。对随机访问文件,提前读丝毫不起作用。相反,它还会帮倒忙,因为读取无用的块以及从高速缓存中删除潜在有用的块将会占用固定的磁盘带宽(如果有“脏”块的话,还需要将它们写回磁盘,这就占用了更多的磁盘带宽)。

文件系统通过跟踪每一个打开文件的访问方式来确定这一点。例如,可以使用与文件相关联的某个位协助跟踪该文件到底是“顺序访问方式”还是“随机访问方式”。在最初不能确定文件属于哪种存取方式时,先将该位设置成顺序访问方式。但是,査找一完成,就将该位清除。如果再次发生顺序读取,就再次设置该位。这样,文件系统可以通过合理的猜测,确定是否应该采取提前读的策略。即便弄错了一次也不会产生严重后果,不过是浪费一小段磁盘的带宽罢了。

减少磁盘臂运动

把有可能顺序访问的块放在一起,当然最好是在同一个柱面上,从而减少磁盘臂的移动次数。当写一个输出文件时,文件系统就必须按照要求一次一次地分配磁盘块。如果用位图来记录空闲块,并且整个位图在内存中,那么选择与前一块最近的空闲块是很容易的。如果用空闲表,并且链表的一部分存在磁盘上,要分配紧邻着的空闲块就困难得多。

不过,即使采用空闲表,也可以采用块簇技术。这里用到一个小技巧,即不用块而用连续块簇来跟踪磁盘存储区。如果一个扇区有512个字节,有可能系统采用1KB的块(2个扇区),但却按每2块(4个扇区)一个单位来分配磁盘存储区。这和2KB的磁盘块并不相同,因为在高速缓存中它依然使用1KB的块,磁盘与内存数据之间传送也是以1KB为单位进行,但在一个空闲的系统上顺序读取文件,寻道的次数可以减少一半,从而使文件系统的性能大大改善。

在使用i节点或任何类似i节点的系统中,另一个性能瓶颈是,读取一个很短的文件也需要两次磁盘访问:一次是访问i节点,另一次是访问块。

一个简单的改进方法是,在磁盘中部而不是开始处存放i节点,此时,在i节点和第一块之间的平均寻道时间减为原来的一半。另一种做法是:将磁盘分成多个柱面组,每个柱面组有自己的i节点、数据块和空闲表。在文件创建时,可选取任一i节点,但选定之后,首先在该i节点所在的柱面组上査找块。如果在该柱面组中没有空闲的块,就选用与之相邻的柱面组的一个块。

当然,仅当磁盘中装有磁盘臂的时候,讨论寻道时间和旋转时间才是有意义的。越来越多的电脑开始装配不带移动部件的固态硬盘(SSD)。对于这些硬盘,由于采用了和闪存同样的制造技术,使得随机访问与顺序访问在传输速度上已经较为相近,传统硬盘的许多问题就消失了。不幸的是,新的问题又随之出现。例如,固态硬盘在读取、写入和删除时表现出一些特性,尤其是每一块只可写入有限次数的特征,导致使用时需要十分小心以达到均匀分散磨损的目的。

磁盘碎片整理

磁盘性能可以通过如下方式恢复:移动文件使它们相邻,并把所有的(至少是大部分的)空闲空间放在一个或多个大的连续的区域内。

Windows有一个程序defrag就是从事这个工作的。Windows的用户应该定期使用它,当然,SSD盘除外。

Linux文件系统(特别是ext2和ext3)由于其选择磁盘块的方式,在磁盘碎片整理上一般不会遭受像Windows那样的困难,因此很少需要手动的磁盘碎片整理。

固态硬盘并不受磁盘碎片的影响,事实上,在固态硬盘上做磁盘碎片整理反倒是多此一举,不仅没有提髙性能,反而磨损了固态硬盘。所以碎片整理只会缩短固态硬盘的寿命。

I/O硬件原理

I/O设备

I/O设备大致可以分为两类:块设备(block device)和字符设备(character device)。

  • 块设备把信息存储在固定大小的块中,每个块有自己的地址。通常块的大小在512字节至65536字节之间。所有传输以一个或多个完整的(连续的〉块为单位。块设备的基本特征是每个块都能独立于其他块而读写。硬盘、蓝光光盘和USB盘是最常见的块设备。
  • 字符设备以字符为单位发送或接收一个字符流,而不考虑任何块结构。字符设备是不可寻址的,也没有任何寻道操作。打印机、网络接口、鼠标(用作指点设备)以及大多数与磁盘不同的设备都可看作字符设备。

I/O设备一般由机械部件和电子部件两部分组成,机械部件是设备本身,电子部件称作设备控制器(device controller)或适配器(adapter)。通常可以将这两部分分开处理,以提供更加模块化和更加通用的设计。

设备控制器

在个人计算机上,设备控制器经常以主板上的芯片的形式出现,或者以插入(PCI)扩展槽中的印刷电路板的形式出现。如果控制器和设备之间采用的是标准接口,各个公司都可以制造各种适合这个接口的控制器或设备。

内存映射I/O

每个控制器有几个寄存器用来与CPU进行通信。通过写入这些寄存器,操作系统可以命令设备发送数据、接收数据、开启或关闭,或者执行某些其他操作。通过读取这些寄存器,操作系统可以了解设备的状态,是否准备好接收一个新的命令等。

除了这些控制寄存器以外,许多设备还有一个操作系统可以读写的数据缓冲区。例如,在屏幕上显示像素的常规方法是使用一个视频RAM,这一RAM基本上只是一个数据缓冲区,可供程序或操作系统写入数据。

CPU如何与设备的控制寄存器和数据缓冲区进行通信?

  1. 每个控制寄存器被分配一个I/O端口 (I/O port)号,这是一个8位或16位的整数。所有I/O端口形成I/O端口空间(I/O port space),并且受到保护使得普通的用户程序不能对其进行访问(只有操作系统可以访问)。
  2. 将所有控制寄存器映射到内存空间中,每个控制寄存器被分配唯一的一个内存地址,并且不会有内存被分配这一地址。这样的系统称为内存映射I/O(memory-mapped I/O)。

直接存储器存取

无论一个CPU是否具有内存映射1/0,它都需要寻址设备控制器以便与它们交换数据。CPU可以从I/O控制器每次请求一个字节的数据,但是这样做浪费CPU的时间,所以经常用到一种称为直接存储器存取(Direct Memory Access, DMA)的不同方案。

为简化解释,假设CPU通过单一的系统总线访问所有的设备和内存,该总线连接CPU、内存和I/O设备,如图所示。只有硬件具有DMA控制器时操作系统才能使用DMA,而大多数系统都有DMA控制器,更加普遍的是,只有一个DMA控制器可利用(例如,在主板上),由它调控到多个设备的数据传送,而这些数据传送经常是同时发生的。

无论DMA控制器在物理上处于什么地方,它都能够独立于CPU而访问系统总线。它包含若干个可以被CPU读写的寄存器,其中包括一个内存地址寄存器、一个字节计数寄存器和一个或多个控制寄存器。控制寄存器指定要使用的I/O端口、传送方向(从I/O设备读或写到I/O设备)、传送单位(毎次一个字节或每次一个字)以及在一次突发传送中要传送的字节数。

为了解释DMA的工作原理,首先看一下没有使用DMA时磁盘如何读。首先,控制器从磁盘驱动器串行地、一位一位地读一个块(一个或多个扇区),直到将整块信息放入控制器的内部缓冲区中。接着,它计算校验和,以保证没有读错误发生。然后控制器产生一个中断。当操作系统开始运行时,它重复地从控制器的缓冲区中一次一个字节或一个字地读取该块的信息,并将其存人内存中。

DMA

使用DMA时,过程是不同的。首先,CPU通过设置DMA控制器的寄存器对它进行编程,所以DMA控制器知道将什么数据传送到什么地方(图中的第1步)。DMA控制器还要向磁盘控制器发出一个命令,通知它从磁盘读数据到其内部的缓冲区中,并且对校验和进行检验。如果磁盘控制器的缓冲区中的数据是有效的,那么DMA就可以开始了。

DMA控制器通过在总线上发出一个读请求到磁盘控制器而发起DMA传送(第2步)。这一读请求看起来与任何其他读请求是一样的,并且磁盘控制器并不知道或者并不关心它是来自CPU还是来自DMA控制器。一般情况下,要写的内存地址在总线的地址线上,所以当磁盘控制器从其内部缓冲区中读取下一个字的时候,它知道将该字写到什么地方。写到内存是另一个标准总线周期(第3步)。当写操作完成时,磁盘控制器在总线上发出一个应答信号到DMA控制器(第4步)。于是,DMA控制器步增要使用的内存地址,并且步减字节计数。如果字节计数仍然大于0,则重复第2步到第4步,直到字节计数到达0。此时,DMA控制器将中断CPU以便让CPU知道传送现在已经完成了。当操作系统幵始工作时,用不着将磁盘块复制到内存中,因为它已经在内存中了。

I/O软件原理

I/O软件的目标

在设计I/O软件时一个关键的概念是设备独立性(device independence)。即程序可以访问任意I/O设备而无需事先指定设备。与设备独立性密切相关的是统一命名,一个文件或一个设备的名字不应依赖于设备。在UNIX系统中,所有存储盘都能以任意方式集成到(mount)文件系统层次结构中,因此,用户不必知道哪个名字对应于哪台设备。

I/O软件的另一个重要问题是错误处理(error handling)。一般来说,错误应该尽可能地在接近硬件的层面得到处理。

另一个关键问题是同步(synchronous:即阻塞)和异步(asynchronous:即中断驱动)传输。大多数物理I/O是异步的——CPU启动传输后便转去做其他工作,直到中断发生。

I/O软件的另一个问题是缓冲(buffering),数据离开一个设备之后通常并不能直接存放到其最终的目的地。

最后一个是共享设备和独占设备的问题。

程序控制I/O

I/O的最简单形式是让CPU做全部工作,这一方法称为程序控制I/O(programmed I/O)。考虑一个用户进程,该进程想通过串行接口在打印机上打印8个字符的字符串“ABCDEFGH”。

软件首先要在用户空间的一个缓冲区中组装字符串,然后,用户进程通过发出打开打印机一类的系统调用来获得打印机以便进行写操作。如果打印机当前被另一个进程占用,该系统调用将失败并返回一个错误代码,或者将阻塞直到打印机可用,一旦拥有打印机,用户进程就发出一个系统调用通知操作系统在打印机上打印字符串。

然后,操作系统(通常)将字符串缓冲区复制到内核空间中的一个数组(在这里访问更容易,因为内核可能必须修改内存映射才能到达用户空间)。一旦打印机可用,操作系统就复制第一个字符到打印机的数据寄存器中,在这个例子中使用了内存映射I/O。

在第一个字符打印出来后,系统已经将“B”标记为下一个待打印的字符。一旦将第一个字符复制到打印机,操作系统就要査看打印机是否就绪准备接收另一个字符。一般而言,打印机都有第二个寄存器,用于表明其状态。将字符写到数据寄存器的操作将导致状态变为非就绪。当打印机控制器处理完当前字符时,它就通过在其状态寄存器中设置某一位或者将某个值放到状态寄存器中来表示其可用性。这时,操作系统将等待打印机状态再次变为就绪。打印机就绪事件发生时,操作系统就打印下一个字符。这一循环继续进行,直到整个字符串打印完。然后,控制返回到用户进程。

程序控制I/O十分简单但是有缺点,即直到全部I/O完成之前要占用CPU的全部时间。

中断驱动I/O

现在我们考虑在不缓冲字符而是在每个字符到来时便打印的打印机上进行打印的情形,允许CPU在等待打印机变为就绪的同时做某些其他事情的方式就是使用中断。

当打印字符串的系统调用被发出时,字符串缓冲区被复制到内核空间,并且一旦打印机准备好接收一个字符时就将第一个字符复制到打印机中。这时,CPU要调用调度程序,并且某个其他进程将运行。请求打印字符串的进程将被阻塞,直到整个字符串打印完。

当打印机将字符打印完并且准备好接收下一个字符时,它将产生一个中断。这一中断将停止当前进程并且保存其状态。然后,打印机中断服务过程将运行。

使用DMA的I/O

中断驱动I/O的一个明显缺点是中断发生在每个字符上。中断要花费时间,所以这一方法将浪费一定数量的CPU时间。这一问题的一种解决方法是使用DMA。

此处的思路是让DMA控制器一次给打印机提供一个字符,而不必打扰CPU。本质上,DMA是程序控制I/O,只是由DMA控制器而不是主CPU做全部工作。这一策略需要特殊的硬件(DMA控制器),但是使CPU获得自由从而可以在I/O期间做其他工作。

DMA重大的成功是将中断的次数从打印每个字符一次减少到打印每个缓冲区一次。如果有许多字符并且中断十分缓慢,那么采用DMA可能是重要的改进。另一方面,DMA控制器通常比主CPU要慢很多。如果DMA控制器不能以全速驱动设备,或者CPU在等待DMA中断的同时没有其他事情要做,那么采用中断驱动I/O甚至采用程序控制I/O也许更好。

I/O软件层次

  • 硬件
  • 中断处理程序
  • 设备驱动程序
  • 与设备无关的操作系统软件
  • 用户级I/O软件

ioctl

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数,即对设备的一些特性进行控制,例如串口的传输波特率等。其函数原型如下:

1
int ioctl(int fd, ind cmd, ...);
  • fd:用户程序打开设备时使用open函数返回的文件描述符
  • cmd:用户程序对设备的控制命令
  • data:一些补充参数,和cmd的意义相关

ioctl函数是文件结构中的一个属性分量,也就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。

如果不用ioctl的话,也可以实现对设备I/O通道的控制,但很麻烦。例如,我们可以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,那么后面就跟着控制命令。如果这样做的话,会导致代码分工不明,程序结构混乱。所以才使用ioctl来实现控制的功能。用户程序所做的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。