普通视图

发现新文章,点击刷新页面。
昨天以前HansChen 的博客

Android 12 - WMS 层级结构 && DisplayAreaGroup 引入

作者 chenhang
2021年10月26日 00:00

1. 简介

在 Android 窗口管理中,所有的窗口都是以树形数据结构进行组织管理的,认知这棵 WMS 的树有助于我们理解窗口的管理和显示,同时,WMS 的层级也决定了其在 SurfaceFlinger 的层级结构,这恰恰决定了它的显示规则。

2. WMS 顶层层级构建

在 Android 12 中,所有窗口树形管理都继基于 WindowContainer, 每个 WindowContainer 都有一个父节点和若干个子节点,我们先看看框架中 WindowContainer 都有哪些类型:

2021-11-01-10-50-30

  • RootWindowContainer: 最顶层的管理者,直接管理 DisplayContent
  • DisplayContent: 代表着一个真实或者虚拟的显示设备,在普遍场景中,系统中只存在一个 DisplayContent
  • TaskDisplayArea: 是系统中所有应用任务的父节点,用于管理 Task
  • Task: 代表着一个任务
  • ActivityRecord: 代表一个 Activity 节点
  • WallpaperWindowToken: 代表壁纸节点

在开始之前大概整理了一下系统中各个节点之间的关系:

2021-11-01-11-35-41

从上图可以看到,节点之间的嵌套关系还是比较复杂的( 而且这还是不包括下面章节中提到的引入 Feature 之后的层级关系),层级的最顶端就是 RootWindowContainer, 而它的子节点只能是: DisplayContent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// RootWindowContainer.java
void setWindowManager(WindowManagerService wm) {
...

final Display[] displays = mDisplayManager.getDisplays();
for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) {
final Display display = displays[displayNdx];
// 为每一个 Display 挂载一个 DisplayContent 节点
final DisplayContent displayContent = new DisplayContent(display, this);
addChild(displayContent, POSITION_BOTTOM);
if (displayContent.mDisplayId == DEFAULT_DISPLAY) {
mDefaultDisplay = displayContent;
}
}
...
}

再来看看 DisplayContent 的构造方法,核心逻辑就只有一句,依靠 DisplayAreaPolicy 进行层级初始化

1
2
3
4
5
6
7
8
9
10
11
// DisplayContent.java
DisplayContent(Display display, RootWindowContainer root) {
super(root.mWindowManager, "DisplayContent", FEATURE_ROOT);
...

// 构造子节点层级,默认策略是使用 DisplayAreaPolicy.DefaultProvider
mDisplayAreaPolicy = mWmService.getDisplayAreaPolicyProvider().instantiate(
mWmService, this /* content */, this /* root */, mImeWindowsContainer);

...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// DisplayAreaPolicy.java
static final class DefaultProvider implements DisplayAreaPolicy.Provider {
@Override
public DisplayAreaPolicy instantiate(WindowManagerService wmService,
DisplayContent content, RootDisplayArea root,
DisplayArea.Tokens imeContainer) {
// 创建 TaskDisplayArea 节点,注意,这里是允许创建多个 TaskDisplayArea 并添加的
final TaskDisplayArea defaultTaskDisplayArea = new TaskDisplayArea(content, wmService,
"DefaultTaskDisplayArea", FEATURE_DEFAULT_TASK_CONTAINER);
final List<TaskDisplayArea> tdaList = new ArrayList<>();
tdaList.add(defaultTaskDisplayArea);

final HierarchyBuilder rootHierarchy = new HierarchyBuilder(root);
rootHierarchy.setImeContainer(imeContainer).setTaskDisplayAreas(tdaList);
if (content.isTrusted()) {
// 配置 Feature 及它所能影响的层级
configureTrustedHierarchyBuilder(rootHierarchy, wmService, content);
}

// 根据配置的 Feature 生成并挂载各个节点,建造层级
return new DisplayAreaPolicyBuilder().setRootHierarchy(rootHierarchy).build(wmService);
}
}

在 Android 12 上,Feature 正式派上用场了,原生添加了以下 Feature:

  • WindowedMagnification: 屏幕放大功能,通過 SystemUI mirrorSurface 该节点实现内容拷贝,详见 WindowMagnificationGestureHandler#toggleMagnification
  • HideDisplayCutout: 隐藏刘海屏功能,开启后,该节点将不会延伸到刘海屏区域(除了状态栏导航栏等窗口,因为不在节点控制范围之内)
  • OneHandedBackgroundPanel: 单手模式下此节点会挂载一个纯色图层(在壁纸图层之下),防止深色模式下分辨不出单手模式
  • OneHanded: 单手模式下相关节点都会做一个向下的位移
  • FullscreenMagnification: 屏幕放大功能,通过无障碍服务 FullScreenMagnificationController.SpecAnimationBridge#setMagnificationSpecLocked 最后调用 DisplayContent#applyMagnificationSpec 方法实现节点放大。不过源码中并不是通过这个 Feature 来实现相关层级放大的,改造得还不彻底
  • ImePlaceholder: 特殊情况下用来放置输入法的节点

我们知道,Android 系统是有 Z 轴概念的,不同的窗口有不同的高度,所有的窗口类型对应到 WMS 都会有一个 layer 值,layer 越大,显示在越上面,WMS 规定 1~36 层级,每一个 Feature 都指定了它所能影响到的 layer 层。这里用颜色对不同 Feature 能影响 layer 图层进行颜色标记:

2021-11-01-14-35-46

标记完之后,就需要根据图表生成窗口层级了,首先对标记好的图表进行上移,上移规则: 如果色块上方是空白的,则可以上移,直至上方是颜色块(不知道大家有没有玩过 2048 这款游戏,上移逻辑是一样的~)

2021-11-01-14-45-24

上移之后,我们得到了最终的图表,接下来用以下规则进行层级构建:

  • 同一行相邻的同色块变成一个 Feature 节点,从左到右根据颜色不断生成节点,同一行所有节点挂在同一个父节点下
  • 父节点就是垂直正上方一行的色块对应的节点
  • 为最末端所有 Feature 节点再添加一个节点,根据子节点代表的 layer 不一样,最后添加的节点也不一样
    • 除了 layer 是 2、15 和 16 外,挂载 DisplayArea.Tokens(这类节点后续只能添加 WindowToken 节点)
    • layer = 2 (也就是 APPLICATION_LAYER)的节点,挂载 TaskDisplayArea
    • layer 等于 15 和 16 的节点,挂载 ImeContainer

通过上述构建规则后,我们可以获得一个树形的层级,并且这棵树有以下特点:

  • 树的最末端节点对应一个 layer 范围,同一个 layer 值只有一个末端节点与之对应
  • 为所有 Feature 都生成了对应的父节点,用以控制其所能影响的 layer

生成了这棵树后,我们会保存两样东西:

  • 所有 layer 值对应的最末端节点,方便我们后续根据窗口类型添加节点
  • Map<Feature, List<DisplayArea>> 形式保存的所有 Feature 节点,方便我们后取出某 Feature 对应的所有节点

现在,虽说我们的 WMS 层级是构建好了,但对于这些 Feature 有何作用还完全没有涉及,这块打算放在 WM Shell 专题里进行说明~~

3. DisplayAreaGroup

通过上面 Feature 的说明可以知道,不同的 Feature 是父子节点的关系,那如果我想划分一个逻辑显示区域,对这块区域配置不同的 Feature 该如何呢? 这时候就可以使用 DisplayAreaGroup 了,框架允许我们添加多个 DisplayAreaGroup, 并为其配置不同的 Feature

就像原生提供的 demo 一样,我们可以创建两个 DisplayAreaGroup 并将屏幕一分为二分别放置这两个,这两个区域都是可以作为应用容器的,和分屏不一样的是,这两块区域可以有不同的 Feature 规则以及其他特性,比如设置不同的 DisplayArea#setIgnoreOrientationRequest

2021-11-01-15-35-28

DisplayAreaGroupDisplayContent 都是 RootDisplayArea 的直接子类,DisplayAreaGroup 可以认为是一个 Display 划分出的多个逻辑 Display 吧。当然,AOSP 虽然引入了这个概念和代码,但其实并未使用,我们只能从测试代码 DualDisplayAreaGroupPolicyTest 中略窥一二了~

4. 小结

WMS 相关的内容体系实在太多,本文也仅仅是分析 WMS 窗口层级最顶层的结构,对于具体的窗口添加移除管理这些尚未涉及,同样,原生新增的 Feature 节点使用也没有涉及(这大部分都被打包进 WM Shell 中去了)

如何顺滑地查看 Android Native 代码

作者 chenhang
2019年10月11日 00:00

1. 简介

使用 Android Studio 查看 Android Framework 代码体验非常好,无论是索引还是界面都让人很满意,但是当你跟踪代码,发现进入 native 逻辑时,就会发现 Android Studio 对 native 代码的支持非常不好,不能索引不支持符号搜索不能跳转等,这些让人非常抓狂。那么如何能在 IDE 愉快地查看 native 代码呢?在 Windows 上,Source Insight 的表现也很好,但苦于只有 Windows 平台支持且界面不好,经过一番折腾,还真是找到了方法,下面我们将一步一步打造丝滑的 native 代码阅读环境。

先看一下效果:

2019-10-11-15-02-40.gif

2. CMake

能让 IDE 正确地建立索引,我们需要让 IDE 能正确地知道源文件、头文件、宏定义等各种数据,庆幸的是,我们发现 AOSP 在编译过程中,可以帮我们生成这些数据,详见:http://androidxref.com/9.0.0_r3/xref/build/soong/docs/clion.md

通过文档我们可知,只需要按照以下步骤完成一次编译,即可自动生成各模块对应的 CMake 文件。至于 Cmake 文件是什么,这里就不做赘述了,大家可以自行了解。

  1. 打开以下两个开关,CMakeLists.txt 就会根据编译环境自动生成
1
2
export SOONG_GEN_CMAKEFILES=1
export SOONG_GEN_CMAKEFILES_DEBUG=1
  1. 启动编译
1
make -j16

或者只编译你需要的模块

1
make frameworks/native/service/libs/ui

生成的文件存放在 out 目录,比如刚刚编译的 libui 模块对应的路径为:

1
out/development/ide/clion/frameworks/native/libs/ui/libui-arm64-android/CMakeLists.txt
  1. 合并多个模块

生成了 CMake 后,我们发现,CMake 文件是按模块生成的。这样的话,会导致 IDE 只能单独导入一个模块,而我们平时不可能只看一个模块的代码,如果把多个模块都 include 进来呢?
我们可以在 out/development/ide/clion 路径新建一个 CMakeLists.txt 文件,并添加一下内容:

1
2
3
4
5
6
7
# 指定 CMake 最低版本
cmake_minimum_required(VERSION 3.6)
# 指定工程名,随意
project(aosp)
# 把你需要的模块通过 add_subdirectory 添加进来,注意子目录必须也包含 CMakeLists.txt 文件
add_subdirectory(frameworks/native)
#add_subdirectory(frameworks/base/core/jni/libandroid_runtime-arm64-android)

这样,我们就把多个模块合并在一起了,用 IDE 去打开这个总的 CMake 文件即可

3. 导入 IDE

只要生成 CMake 文件后,剩下的事情就好办了,现在能识别 CMake 工程的 IDE 非常多,大家可以根据个人喜好选择,如:

  • CLion
  • Eclipse
  • Visual Studio

这里以 CLion 为例讲一下如何导入

  1. 打开 CLion
  2. 选择「New CMake Project from Sources」
  3. 指定包含 CMakeLists.txt 的目录,如我们在上一个步骤中说的 out/development/ide/clion(这个目录的 CMakeLists.txt 包含了多个模块,还记得吗?)
  4. 选择「Open Existing Project」
  5. Enjoy your journey …

当然,CLion 也有一个缺点,收费!!如何能免费使用就看大家各显神通了

4. 遇到的一些问题

  • 生成的 CMakeLists.txt 里指定路径可能会使用绝对路径,如: set(ANDROID_ROOT /Volumes/AndroidSource/M1882_QOF7_base),这里大家要注意,如果把 CMakeLists.txt 拷贝到别的工程使用,记得修正一下路径
  • Mac 用户留意,如果你的 CMakeLists.txt 是从 linux 平台生成拷贝过来的,生成的 CMakeLists.txt 里指定的 c++ 编译器 set(CMAKE_CXX_COMPILER "${ANDROID_ROOT}/prebuilts/clang/host/linux-x86/clang-3977809/bin/clang++") 这里指定的是 linux-x86 的编译器,记得替换成 darwin-x86,如果对应目录下没有 clang++,那就从 AOSP 源码拷一个吧
  • 如果 CMake 中列出的源文件在工程中找不到,会导致 CLion 停止索引,如果出现不一致的时候,移除 CMake 中源文件的声明即可

如果使用遇到其他问题,欢迎联系告知,谢谢

5. 总结

所谓工欲善其事,必先利其器。通过这种方法建立的索引包含了 AOSP 所有模块,最重要是它还会根据编译环境,把相关 FLAGS 和宏都设置好。

设计模式之装饰模式

作者 chenhang
2017年3月9日 00:00

概述

装饰模式(Decorator)也叫包装器模式(Wrapper),是指动态地给一个对象添加一些额外的职责,就增加功能来说装饰模式比生成子类更为灵活。它通过创建一个包装对象,也就是装饰来包裹真实的对象

情景举例

我们先来分析这样一个画图形的需求:

  1. 它能绘制各种背景,如红色、蓝色、绿色
  2. 它能绘制形状,如三角形,正方形,圆形
  3. 它能给形状加上阴影

就先列这三个简单的需求吧,下面让我们比较下各种实现的优缺点

丑陋的实现

来看看我们用继承是如何实现的,首先,抽象出一个Shape接口我想大家都不会有意见的是不是?

1
2
3
4
5
6
7
8
9
10
/**
* @author HansChen
*/
public interface Shape {

/**
* 绘制图形
*/
void draw();
}

然后我们定义各种情况下的子类,结构如下,看到这么多的子类,是不是有点要爆炸的感觉?真是想想都可怕
2019-9-2-12-35-9.png

而且如果再新增一种需求,比如现在要画椭圆,那么维护的人员估计就要爆粗了吧?

为了避免写出上面的代码,聪明的童鞋们可能会提出第二种方案:

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
/**
* @author HansChen
*/
public class ShapeImpl implements Shape {

enum Type {
Circle,
Square,
Trilatera
}

enum Color {
Red,
Green,
Blue
}

private Type type;
private Color color;
private boolean shadow;

public ShapeImpl() {
}

public Type getType() {
return type;
}

public void setType(Type type) {
this.type = type;
}

public Color getColor() {
return color;
}

public void setColor(Color color) {
this.color = color;
}

public boolean isShadow() {
return shadow;
}

public void setShadow(boolean shadow) {
this.shadow = shadow;
}

@Override
public void draw() {
// TODO: 2017/3/9 根据属性情况画出不同的图
}
}

这样,根据不同的画图需求,只需要设置不同的属性就可以了,这样确实避免了类爆炸增长的问题,但这种方式违反了开放封闭原则,比如画正方形的方式变了,需要对ShapeImpl进行修改,或者如果新增需求,如画椭圆,也需要对ShapeImpl进行修改。而且这个类不方便扩展,子类将继承一些对自身并不合适的方法。

装饰模式

概念介绍

装饰模式(Decorator)也叫包装器模式(Wrapper),是指动态地给一个对象添加一些额外的职责

以下情况使用Decorator模式:

  • 需要扩展一个类的功能,或给一个类添加附加职责。
  • 需要动态的给一个对象添加功能,这些功能可以再动态的撤销。
  • 需要增加由一些基本功能的排列组合而产生的非常大量的功能,从而使继承关系变的不现实。
  • 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类

但这种灵活也会带来一些缺点,这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。装饰模式会导致设计中出现许多小类,如果过度使用,会使程序变得很复杂

下面来看看装饰模式的结构:
2019-9-2-12-35-32.png

  1. Component抽象组件,是一个接口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对象。(注:在装饰模式中,必然有一个最基本、最核心、最原始的接口或者抽象类充当Component抽象组件)
  2. ConcreteComponent具体组件,是最核心、最原始、最基本的接口或抽象类的实现,我们需要装饰的就是它
  3. Decorator装饰角色, 一般是一个抽象类,实现接口或者抽象方法,它的属性里必然有一个private变量指向Component抽象组件。
  4. 具体装饰角色,如上图中的ConcreteDecoratorA和ConcreteDecoratorB,我们要把我们最核心的、最原始的、最基本的东西装饰成其它东西。

代码示例如下:

1
2
3
4
5
6
7
 /**
* @author HansChen
*/
public interface Component {

void operation();
}
1
2
3
4
5
6
7
public class ConcreteComponent implements Component {

@Override
public void operation() {
System.out.print("do something");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Decorator implements Component {

private Component component;

public Decorator(Component component) {
this.component = component;
}

@Override
public void operation() {
component.operation();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class ConcreteDecoratorA extends Decorator {

public ConcreteDecoratorA(Component component) {
super(component);
}

@Override
public void operation() {
super.operation();
System.out.println("do something");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class ConcreteDecoratorB extends Decorator {

public ConcreteDecoratorB(Component component) {
super(component);
}

@Override
public void operation() {
super.operation();
System.out.println("do something");
}
}

上面说了一堆结构和示例代码,但大家可能还是不太好理解,下面用装饰模式来重新实现画图的功能

用装饰模式实现需求

先上结构图
2019-9-2-12-35-51.png

首先定义可动态扩展对象的抽象

1
2
3
4
5
6
7
public interface Shape {

/**
* 绘制图形
*/
void draw();
}

定义具体的组件,每一个组件代表一个形状

1
2
3
4
5
6
7
public class Square implements Shape {

@Override
public void draw() {
System.out.print("正方形");
}
}
1
2
3
4
5
6
7
public class Trilateral implements Shape {

@Override
public void draw() {
System.out.print("三角形");
}
}
1
2
3
4
5
6
7
public class Circle implements Shape {

@Override
public void draw() {
System.out.print("圆形");
}
}

定义可装饰者的抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ShapeDecorator implements Shape {

private Shape shape;

public ShapeDecorator(Shape shape) {
this.shape = shape;
}

@Override
public void draw() {
shape.draw();
}
}

定义具体的装饰者

1
2
3
4
5
6
7
8
9
10
11
12
public class Blue extends ShapeDecorator {

public Blue(Shape shape) {
super(shape);
}

@Override
public void draw() {
super.draw();
System.out.print(" 蓝色");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Green extends ShapeDecorator {

public Green(Shape shape) {
super(shape);
}

@Override
public void draw() {
super.draw();
System.out.print(" 绿色");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Red extends ShapeDecorator {

public Red(Shape shape) {
super(shape);
}

@Override
public void draw() {
super.draw();
System.out.print(" 红色");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
public class Shadow extends ShapeDecorator {

public Shadow(Shape shape) {
super(shape);
}

@Override
public void draw() {
super.draw();
System.out.print(" 有阴影");
}
}

好了,现在让我们看看具体怎么使用:

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

public static void main(String[] args) {
//正方形 红色 有阴影
Shape shape = new Square();
shape = new Red(shape);
shape = new Shadow(shape);
shape.draw();

//圆形 绿色
shape = new Circle();
shape = new Green(shape);
shape.draw();

//三角形 蓝色 有阴影
shape = new Trilateral();
shape = new Blue(shape);
shape = new Shadow(shape);
shape.draw();
}
}

可以看到,装饰模式是非常灵活的,通过不同的装饰,实现不同的效果

装饰模式的应用举例

这里再列举一些用到了装饰模式的情景,童鞋们可以根据这些场景加深对装饰模式的理解

  • Java中IO设计
  • Android中ContextContextWrapper的设计

总结

装饰模式是为已有功能动态地添加功能的一种方式,它把每个要装饰的功能放在单独的类中,并让这个类包括要装饰的对象,有效地把核心职能和装饰功能区分开了。但它带来灵活的同时,也容易导致别人不了解自己的设计方式,不知如何使用。就像Java中I/O库,人们第一次接触的时候,往往无法轻易理解它。这其中的平衡取舍,就看自己咯

设计模式之代理模式

作者 chenhang
2016年12月27日 00:00

概述

我们执行一个功能的函数时,经常需要在其中写入与功能不是直接相关但很有必要的代码,如日志记录、信息发送、安全和事务支持等,这些枝节性代码虽然是必要的,但它会带来以下麻烦:

  • 枝节性代码游离在功能性代码之外,它下是函数的目的
  • 枝节性代码会造成功能性代码对其它类的依赖,加深类之间的耦合
  • 枝节性代码带来的耦合度会造成功能性代码移植困难,可重用性降低

毫无疑问,枝节性代码和功能性代码需要分开来才能降低耦合程度,我们可以使用代理模式(委托模式)完成这个要求。代理模式的作用是:为其它对象提供一种代理以控制对这个对象的访问。在某些情况下,一 个客户不想直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介作用。

代理模式一般涉及到三个角色:

  • 抽象角色:声明真实对象和代理对象的共同接口
  • 代理角色:代理对象内部包含有真实角色的引用,从而可以操作真实角色,同时代理对象 与真实对象有相同的接口,能在任何时候代替真实对象,代理对象可以在执行真实对 象前后加入特定的逻辑以实现功能的扩展。
  • 真实角色:代理角色所代表的真实对象,是我们最终要引用的对象

常见的代理应用场景有:

  • 远程代理:对一个位于不同的地址空间对象提供一个局域代表对象,如RMI中的stub
  • 虚拟代理:根据需要将一个资源消耗很大或者比较复杂的对象,延迟加载,在真正需要的时候才创建
  • 保护代理:控制对一个对象的访问权限
  • 智能引用:提供比目标对象额外的服务和功能

接下来,我们用代码来说明什么是代理模式

代理模式

UML图

先看看代理模式的结构图:
2019-9-2-12-29-26.png

代码

下面给出一个小栗子说明代理模式,先定义一个抽象角色,也就是一个公共接口,声明一些需要代理的方法,本文定义一个Subject接口,为了简单说明,只是在里面定义一个request方法:

1
2
3
4
public interface Subject {

void request();
}

定义Subject的实现类RealSubject,它是一个真实角色:

1
2
3
4
5
6
7
public class RealSubject implements Subject {

@Override
public void request() {
System.out.print("do real request");
}
}

定义一个代理角色ProxySubject,跟RealSubject一样,它也继承了Subject接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ProxySubject implements Subject {

private RealSubject mSubject;

public ProxySubject() {
mSubject = new RealSubject();
}

@Override
public void request() {
System.out.print("before");
mSubject.request();
System.out.print("after");
}
}

客户端调用代码

1
2
3
4
5
6
7
8
public class Client {

public static void main(String[] args) {

Subject subject = new ProxySubject();
subject.request();
}
}

这样,一个简易的代理模式模型就建立了,客户端在使用过程中,无需关注RealSubject,只需要关注ProxySubject就行了,并且可以在ProxySubject中插入一些非功能信的代码,比如输出Log,统计执行时间等等

远程代理

远程代理,对一个位于不同的地址空间对象提供一个局域代表对象。这样说大家可能比较抽象,不太能理解,但其实童鞋们可能在就接触过了,在Android中,Binder的使用就是典型的远程代理。比如ActivityManager:
2019-9-2-12-29-57.png

在启动Activity的时,会调用ActivityManager的startActivity方法,我们看看Activity是怎么获取的:

1
2
3
4
5
6
7
8
9
10
11
12
static public IActivityManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IActivityManager in =
(IActivityManager)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
// 返回代理类
return new ActivityManagerProxy(obj);
}

可以看到,最终是返回了一个ActivityManager的代理类,因为真正的ActivityManager是运行在内核空间的,Android应用无法直接访问得到,那么就可以借助这个ActivityManagerProxy,通过Binder与真正的ActivityManager,也就是ActivityManagerService交互。其中ActivityManagerService和ActivityManagerProxy都实现了同一个接口:IActivityManager。这个就是Android中典型的代理模式的栗子了。至于ActivityManagerService和ActivityManagerProxy是如何通过Binder实现远程调用,这个就是另一个话题Binder的内容了,这里不再做阐述

延迟加载

根据需要将一个资源消耗很大或者比较复杂的对象,延迟加载,在真正需要的时候才创建。假设我们创建RealSubject需要耗费一定的资源,那么,我们可以把创建它延迟到实际调用的时候,优化Client初始化速度,比如,这样修改ProxySubject以达到延迟加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ProxySubject implements Subject {

private RealSubject mSubject;

public ProxySubject() {
}

@Override
public void request() {
// 延时加载
if (mSubject == null) {
mSubject = new RealSubject();
}
mSubject.request();
}
}

Client在实例化ProxySubject的时候,不需消耗资源,而是等到真正调用request的时候,才会加载RealSubject,达到延时加载的效果

保护代理

可以在Proxy类中加入进行权限,验证是否具有执行真实代码的权限,只有权限验证通过了才进行真实对象的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ProxySubject implements Subject {

private RealSubject mSubject;
private User mUser;

public ProxySubject(User user) {
this.mUser = user;
}

@Override
public void request() {
// 验证权限
if (mUser.isLogin()) {
mSubject.request();
}
}
}

额外功能

通过引入代理类,可以方便地在功能性代码前后插入扩展,如Log输出,调用统计等,实现对原代码的无侵入式代码扩展,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ProxySubject implements Subject {

private RealSubject mSubject;

public ProxySubject() {
mSubject = new RealSubject();
}

@Override
public void request() {
System.out.print("Log: before");
mSubject.request();
System.out.print("Log: after");
}
}

静态代理和动态代理

静态代理和动态代理的概念和使用可以参考我另一篇文章:Java动态代理:http://blog.csdn.net/shensky711/article/details/52872249

依赖注入利器 - Dagger ‡

作者 chenhang
2016年12月18日 00:00

概述

在开发过程中,为了实现解耦,我们经常使用依赖注入,常见的依赖注入方式有:

  • 构造方法注入:在构造方法中把依赖作为参数传递进去
  • setter方法注入:添加setter方法,把依赖传递进去
  • 接口注入:把注入方法抽到一个接口中,然后实现该接口,把依赖传递进去

下面用一个小栗子来说明三种方式的用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class PersonService implements DependencyInjecter {

private PersonDao personDao;

// 构造方法注入
public PersonService(PersonDao personDao) {
this.personDao = personDao;
}

// setter方法注入
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}

// 接口注入:实现DependencyInjecter接口
@Override
public void injectPersonDao(PersonDao personDao) {
this.personDao = personDao;
}

... ...
}

我们来看下使用一般的依赖注入方法时,代码会是怎么样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MainActivity extends AppCompatActivity {

private PersonService mService;

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

// 创建PersonService的依赖:personDao
PersonDao personDao = new PersonDaoImpl();
// 通过构造方法注入依赖
mService = new PersonService(personDao);
}
}

看起来还好是吧?但现实情况下,依赖情况往往是比较复杂的,比如很可能我们的依赖关系如下图:
2019-9-2-11-38-41.png

PersonDaoImpl依赖类A,类A依赖B,B依赖C和D…在这种情况下,我们就要写出下面这样的代码了:

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 MainActivity extends AppCompatActivity {

private PersonService mService;

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

// 创建依赖D
D d = new D();
// 创建依赖C
C c = new C();
// 创建依赖B
B b = new B(c, d);
// 创建依赖A
A a = new A(b);
// 创建PersonService的依赖:personDao
PersonDao personDao = new PersonDaoImpl(a);
// 通过构造方法注入依赖
mService = new PersonService(personDao);
}
}

MainActivity只是想使用PersonService而已,却不得不关注PersonService的依赖是什么、PersonDaoImpl依赖的依赖是什么,需要把整个依赖关系搞清楚才能使用PersonService。而且还有一个不好的地方,一旦依赖关系变更了,比如A不再依赖B了,那么就得修改所有创建A的地方。那么,有没有更好的方式呢?Dagger就是为此而生的,让我们看看使用Dagger后,MainActivity会变成什么模样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MainActivity extends AppCompatActivity {

@Inject
PersonService mService;

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

// Dagger注入,读者现在可先不关注里面做了什么操作
DaggerPersonServiceComponent.create().inject(MainActivity.this);

// 注意,mService已经是非空了,可以正常使用
mService.update(1, "HansChen");
......
}
}

之前创建A、B、C、D、PersonDaoImpl等依赖的代码全不见了,只需要调用一个注入语句就全搞定了。调用了注入语句之后,mService就可以正常使用了,是不是挺方便呢?至于这句注入语句具体干了什么,读者现在可以先不管,后面会有详细说明,这里只是做一个使用演示而已。

我们大概猜想一下,在MainActivity使用PersonService需要做哪些?

  1. 分析生成依赖关系图,如PersonService–>PersonDaoImpl–>A–>B–>C&D
  2. 根据依赖关系图获取相关依赖,比如依次创建D、C、B、A、PersonDaoImpl、PersonService的实例
  3. 把生成的PersonService实例传递给MainActivity的mService成员变量

其实Dagger做的也就是上面这些事情了,接下来就让我们真正开始学习Dagger吧

声明需要注入的对象

首先我们应该用javax.inject.Inject去注解需要被自动注入的对象,@Inject是Java标准的依赖注入(JSR-330)注解。比如下面栗子中,需要注入的对象就是MainActivity的mService。这里有个要注意的地方,被@Inject注解的变量不能用private修饰

1
2
3
4
5
6
7
public class MainActivity extends AppCompatActivity {

// 注意,不能被private修饰
@Inject
PersonService mService;
......
}

如何实例化出依赖?

在执行依赖注入的时候,Dagger会查找@Inject注解的成员变量,并尝试获取该类的实例,Dagger最直接的方式就是直接new出相应的对象了。实例化对象的时候,会调用对象的构造方法,但假如有多个构造方法,具体用哪个构造方法来实例化对象?Dagger肯定是不会帮我们“擅自做主”的,用哪个构造方法来实例化对象应该是由我们做主的,所以我们需要给相应的构造方法添加@Inject注解
当Dagger需要实例化该对象的时候,会调用@Inject注解的构造方法来实例化对象:

1
2
3
4
5
6
7
8
9
10
11
12
public class PersonService implements DependencyInjecter {

private PersonDao personDao;

// 用@Inject注解,相当于告诉Dagger需要实例化PersonService的时候,请调用这个构造方法
@Inject
public PersonService(PersonDao personDao) {
this.personDao = personDao;
}

......
}

聪明的你应该发现了,调用PersonService的构造方法需要传入PersonDao实例,所以要实例化PersonService,必须先要实例化PersonDao,Dagger会帮我们自动分析出这个依赖关系,并把它添加到依赖关系图里面!Dagger会尝试先去实例化一个PersonDao,如果PersonDao又依赖于另外一个对象A,那么就先尝试去实例化A……以此类推,是不是很像递归?当所有依赖都被实例化出来之后,我们的PersonService当然也被构造出来了。

问题又来了,如果PersonDao是一个接口呢?Dagger怎么知道这个接口应该怎么实现?答案是不知道的,那么Dagger怎么实例化出一个接口出来?这个就是Module存在的意义之一了。关于Module的讲解我们会在后面详细说明,我们现在只要知道,Module里面会定义一些方法,这些方法会返回我们的依赖,就像:

1
2
3
4
5
6
7
8
9
10
11
@Module
public class PersonServiceModule {

/**
* 提供PersonDao接口实例
*/
@Provides
PersonDao providePersonDao(A a) {
return new PersonDaoImpl(a);
}
}

Dagger根据需求获取一个实例的时候,并不总是通过new出来的,它会优先查找Module
中是否有返回相应实例的方法,如果有,就调用Module的方法来获取实例。

比如你用@Inject注解了一个成员变量,Dagger会查找Module中是否有用@Provides注解的,返回该类实例的方法,有的话就会调用provide方法来获得实例,然后注入,如果没有的话Dagger就会尝试new出一个实例。就像我们现在这个栗子,PersonService依赖于PersonDao接口,Dagger不能直接为我们new出一个接口,但我们可以提供一个Module,在Module中定义一个返回PersonDao接口实例的方法,这样,Dagger就可以解决实例化PersonDao的问题了。

我们再梳理一下流程,如果我们用@Inject注解了一个成员变量,并调用注入代码之后,Dagger会这样处理:

  1. 查找Module中是否有用@Provides注解的,返回该类实例的方法
  2. 如果有,就调用那个provide方法来获得实例,然后注入
  3. 如果没有,就尝试调用相应的类中被@Inject注解的构造方法new出一个实例,然后注入
  4. 如果没有一个构造方法被@Inject注解,Dagger会因不能满足依赖而出错

所以假如一个变量被@Inject注解,要么在Module中提供provide方法获取实例,要么该类提供一个被@Inject注解的构造方法,否则Dagger会出错

Module的使用

一般而言,Dagger会获取所有依赖的实例,比如当需要一个TestBean的时候,会通过new TestBean()创建实例并注入到类中。但是,以下情况会就不好处理了:

  1. 需要生成的是一个接口,而Dagger不能直接实例化接口
  2. 不能在第三方库的类中添加注解
  3. 可配置的对象必须是配置的

为了解决以上问题,我们需要定义一个被@Module注解的类,在里面定义用@Provides注解的方法。用该方法返回所需的实例。

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
@Module
public class PersonServiceModule {

@Provides
D provideD() {
return new D();
}

@Provides
C provideC() {
return new C();
}

@Provides
B provideB(C c, D d) {
return new B(c, d);
}

@Provides
A provideA(B b) {
return new A(b);
}

/**
* 提供PersonDao实例
*/
@Provides
PersonDao providePersonDao(A a) {
return new PersonDaoImpl(a);
}
}

就像providePersonDao返回了PersonDao接口实例,Dagger虽然不能直接实例化出PersonDao接口,但却可以调用Module的providePersonDao方法来获得一个实例。providePersonDao方法需要传入A的实例,那么这里也构成了一个依赖关系图。Dagger会先获取A的实例,然后把实例传递给providePersonDao方法。

Component的使用

到目前为止,我们虽然知道了:

  • Dagger怎么获取实例:
    • 从Module的provide方法中获取
    • 通过@Inject注解的构造方法new出新的实例
  • Dagger会推导provide方法和构造方法的参数,形成依赖图,并“满足”我们依赖图的需求,获取依赖的实例

看样子需要注入的依赖可以获取了,但是不是总觉得还有点“零碎”,整个流程还没连贯起来?比如,Module既然是一个类,生成依赖图的时候,怎么知道跟哪个Module挂钩?即使最后生成了需要的实例,注入的“目的地”是哪里?怎么才能把它注入到“目的地”?残缺的这部分功能,正是Component提供的,Component起到了一个桥梁的作用,贯通Module和注入目标。我们来看看最开始那个例子,我们是怎么进行依赖注入的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MainActivity extends AppCompatActivity {

@Inject
PersonService mService;

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

PersonServiceComponent component = DaggerPersonServiceComponent.builder()
.personServiceModule(new PersonServiceModule())
.build();
// 注入,所有@Inject注解的成员变量都会同时注入
component.inject(MainActivity.this);

// 通过component获取实例,注意,这里只是演示用法,其实mService在component.inject的时候已经完成了注入
mService = component.getPersonService();
}
}

这个DaggerPersonServiceComponent是什么鬼?DaggerPersonServiceComponent其实是Dagger为我们自动生成的类,它实现了一个Component接口(这个接口是需要我们自己写的),我们来看下它实现的接口长什么样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 指定PersonServiceModule,当需要获取某实例的时候,会查找PersonServiceModule中是否有返回相应类型的方法,有的话就通过该方法获得实例
*
* @author HansChen
*/
@Component(modules = PersonServiceModule.class)
public interface PersonServiceComponent {

/**
* 查找activity中被@Inject注解的成员变量,并尝试获取相应的实例,把实例赋给activity的成员变量
* 注意函数格式:返回值为空、带有一个参数
*/
void inject(MainActivity activity);

/**
* Dagger会尝试从Module中获取PersonService实例,如果Module中不能获取对应实例,则通过PersonService的构造方法new出一个实例
* 注意函数格式:参数为空,返回值非空
*/
PersonService getPersonService();
}

这个接口被Component注解修饰,它里面可以定义3种类型的方法:

  • 返回值为空,有一个参数:查找参数中被@Inject注解的成员变量,并尝试获取相应的实例(通过Module的provide方法或@Inject注解的构造方法new出新的实例),把实例赋给参数的成员变量
  • 返回值非空,参数为空:获取相应实例并返回
  • 返回值是Component,参数是Moduld,通过该方法可以创建SubComponent实例

既然获取实例的时候,有可能用到Module,那么就必须为这个Component指定使用的Module是什么。具体做法就是在@Component注解中指定modules。
定义好Component之后,Dagger会自动帮我们生成实现类,这就是Dagger强大的地方!生成的类名格式是:Dagger+Component名。
Component提供了2种方法,一个是注入式方法,一个是获取实例方法。具体用什么方法,就看个人需求了。一个Component其实也对应了一个依赖图,因为Component使用哪个Module是确定不变的,依赖关系无非也就是跟Module和类的定义有关。一旦这些都确定下来了,在这个Component范围内,依赖关系也就被确定下来了。额外再说一点,在Dagger1中,Component的功能是由ObjectGraph实现的,Component是用来代替它的。

Component定义好之后,build一下工程,Dagger就会自动为我们生成实现类了,就可以使用自动生成的实现类来进行依赖注入了。到现在为止,我们已经通过Dagger完成了依赖注入。可能看起来比正常方法麻烦得多,但是Dagger框架可以让依赖的注入和配置独立于组件之外,它帮助你专注在那些重要的功能类上。通过声明依赖关系和指定规则构建整个应用程序。

熟悉完Dagger基本的使用之后,接下来我们来讲解一些稍微高级一点的用法:

Dagger的进阶使用

Components之间的关系

在Dagger中,Component之间可以有两种关系:Subcomponents和Component dependencies。他们有什么作用呢?比如在我们应用中,经常会有一些依赖我们在各个界面都使用得到,比如操作数据库、比如网络请求。假设我们有个ServerApi的接口,在页面A、B、C都使用到了,那么我们要在页面A、B、C的Component里面都能获取到ServerApi的实例,但显然,获取ServerApi实例的方法都是一样的,我们不想写重复的代码。于是我们可定义一个ApplicationComponent,在里面返回ServerApi实例,通过Component之间的关系便可以共享ApplicationComponent提供的依赖图。

下面通过Android中的一个小栗子来说明Subcomponents和Component dependencies如何使用

dependencies

先说明下各个模块之间的关系
首先,我们定义一个ApplicationComponent,它定义了一个方法,通过它来获得ServerApi实例。ApplicationComponent还关联了ApplicationModule,这个Module是ServerApi实例的提供者,注意,这个Moduld还可以返回Context实例
2019-9-2-11-41-18.png

1
2
3
4
5
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

ServerApi getServerApi();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Module
public class ApplicationModule {

private final Context mAppContext;

ApplicationModule(Context context) {
mAppContext = context.getApplicationContext();
}

@Provides
Context provideAppContext() {
return mAppContext;
}

@Provides
ServerApi provideServerApi(Context context) {
return new ServerApiImpl(context);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DemoApplication extends Application {

private ApplicationComponent mAppComponent;

@Override
public void onCreate() {
super.onCreate();
mAppComponent = DaggerApplicationComponent.builder().applicationModule(new ApplicationModule(this)).build();
}

public ApplicationComponent getAppComponent() {
return mAppComponent;
}
}

MainActivity使用MVP模式,在MainPresenter里面需要传入一个ServerApi对象
2019-9-2-11-43-13.png

1
2
3
4
5
6
// 注意,这里有个dependencies声明
@Component(dependencies = ApplicationComponent.class, modules = MainPresenterModule.class)
public interface MainPresenterComponent {

MainPresenter getMainPresenter();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Module
public class MainPresenterModule {

private MainView mMainView;

public MainPresenterModule(MainView mainView) {
this.mMainView = mainView;
}

@Provides
MainView provideMainView() {
return mMainView;
}
}
1
2
3
4
5
6
7
8
9
10
11
public class MainPresenter {

private MainView mMainView;
private ServerApi mServerApi;

@Inject
public MainPresenter(MainView mainView, ServerApi serverApi) {
this.mMainView = mainView;
this.mServerApi = serverApi;
}
}

先抛开dependencies,我们分析这个这个依赖树是怎么样的
2019-9-2-11-43-43.png
Component中getMainPresenter的目的很简单,就是返回MainPresenter,而MainPresenter又依赖MainView和ServerApi,MainView还好说,在MainPresenterModule中有provide方法,但是ServerApi呢?就像上面说的那样,如果我们在这个Moduld中也添加相应的provide方法,那真是太麻烦了(当然,这样做完全是可以实现的),所以我们依赖了ApplicationComponent,通过dependencies,在被依赖的Component暴露的对象,在子Component中是可见的。这个是什么意思呢?意思有两个:

  1. 被依赖Component接口暴露的对象,可以添加到依赖者的依赖图中
  2. Component接口没有暴露的对象,依赖者是不可见的

对于第一点应该比较好理解,就像这个栗子,MainPresenterComponent生成MainPresenter需要ServerApi,而ApplicationComponent中有接口暴露了ServerApi,所以MainPresenterComponent可以获得ServerApi
对于第二点,假设MainPresenter还需要传入一个Context对象,我们注意到,ApplicationModule是可以提供Context的,那MainPresenterComponent能不能通过ApplicationComponent获取Context实例?答案是不行的,因为ApplicationComponent没有暴露这个对象。想要获取Context,除非ApplicationComponent中再添加一个getContext的方法。

他们之间的关系可以用下图描述:
2019-9-2-11-44-12.png

Subcomponents

Subcomponents 实现方法一:

  • 先定义子 Component,使 用@Subcomponent 标注(不可同时再使用 @Component)
  • 父 Component 中定义获得子 Component 的方法

让我们对上面的栗子改造改造:
去除MainPresenterComponent的Component注解,改为Subcomponent:

1
2
3
4
5
6
7
@Subcomponent(modules = MainPresenterModule.class)
public interface MainPresenterComponent {

void inject(MainActivity activity);

MainPresenter getMainPresenter();
}

在ApplicationComponent中新增plus方法(名字可随意取),返回值为MainPresenterComponent,参数为MainPresenterModule:

1
2
3
4
5
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

MainPresenterComponent plus(MainPresenterModule module);
}

这样,就构建了一个ApplicationComponent的子图:MainPresenterComponent。子图和dependencies的区别就是,子图可以范围父图所有的依赖,也就是说,子图需要的依赖,不再需要在父Component中暴露任何对象,可以直接通过父图的Moduld提供!他们的关系变为了:
2019-9-2-11-44-34.png

这里需要注意的是,以上代码直接在父 Component 返回子 Component 的形式,要求子 Component 依赖的 Module 必须包含一个无参构造函数,用以自动实例化。如果 Module 需要传递参数,则需要使用 @Subcomponent.builder 的方式,实现方法二实现步骤如下:

  • 在子 Component,定义一个接口或抽象类(通常定义为 Builder),使用 @Subcomponent.Builder 标注
    • 编写返回值为 Builder,方法的参数为需要传入参数的 Module
    • 编写返回值为当前子 Component的 无参方法
  • 父 Component 中定义获得子 Component.Builder 的方法

代码如下:

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
@Module
public class TestModule {
public TestModule(String test) {
}

@Provides
AuthManager provideAuthManager() {
return AuthManager.getInstance();
}
}

@Subcomponent(modules = {TestModule.class})
public interface TestComponent {

AuthManager getAuthManager();

@Subcomponent.Builder
interface Builder {

Builder createBuilder(TestModule module);

TestComponent build();
}
}

@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
...
TestComponent.Builder testComponentBuilder();
}


// 使用
TestComponent testComponent = mApplicationComponent.testComponentBuilder().createBuilder(new TestModule("test")).build();

Binds注解

在Dagger2中,一般都是使用@provide方法注入接口。在Android 中,一般我们会这样做,创建一个接口 Presenter 命名 为 HomePresenter

1
2
3
public interface HomePresenter {
Observable<List<User>> loadUsers()
}

然后创建一个这个接口的实例:HomePresenterImp

1
2
3
4
5
6
7
8
public class HomePresenterImp implements HomePresenter {
public HomePresenterImp(){
}
@Override
public Observable<List<User>> loadUsers(){
//Return user list observable
}
}

然后在 Module 中,提供实例化的 provide 方法:

1
2
3
4
5
6
7
@Module
public class HomeModule {
@Provides
public HomePresenter providesHomePresenter(){
return new HomePresenterImp();
}
}

但是,如果我们需要添加一个依赖到 presenter 叫 UserService,那就意味着,我们也要在 module 中添加一个 provide 方法提供这个 UserService,然后在 HomePresenterImp 类中加入一个 UserService 参数的构造方法。
有没有觉得这种方法很麻烦呢?我们还可以用 @Binds 注解,如:

1
2
3
4
5
6
7
@Module
public abstract class HomeModule {
// 变为 abstract 方法, 同时 Module 也必须声明为 abstract, 传入的参数必须为返回参数的实现类
// 当需要 HomePresenter 时,dagger 会自动实例化 HomePresenterImp 并返回
@Binds
public abstract HomePresenter bindHomePresenter(HomePresenterImp homePresenterImp);
}

除了方便,使用 @Binds 注解还可以让 dagger2 生成的代码效率更高。但是需要注意的是,由于 Module 变为抽象类,Module 不能再包含非 static 的带 @Provides 注解的方法。而且这时候,依赖此 Module 的 Component 也不需要传入此 Module 实例了(也实例化不了,因为它是抽象的)。相当于此 Module 仅仅作为描述依赖关系的一个类

Scopes

Scopes可是非常的有用,Dagger2可以通过自定义注解限定注解作用域。@Singleton是被Dagger预先定义的作用域注解。

  • 没有指定作用域的@Provides方法将会在每次注入的时候都创建新的对象
  • 一个没有scope的component不可以依赖一个有scope的组件component
  • 子组件和父组件的scope不能相同
  • Module中provide方法的scope需要与Component的scope一致

我们通常的ApplicationComponent都会使用Singleton注解,也就会是说我们如果自定义component必须有自己的scope。读者到这里,可能还不能理解Scopes的作用,我们先来看下默认提供的Singlton到底有什么作用,然后再讨论Scopes的意义:

Singlton

Singletons是java提供的一个scope,我们来看看Singletons能做什么事情。
为@Provides注释的方法或可注入的类添加添加注解@Singlton,构建的这个对象图表将使用唯一的对象实例,比如我们有个ServerApi
方法一:用@Singleton注解类:

1
2
3
4
5
6
7
8
9
10
11
@Singleton
public class ServerApi {

@Inject
public ServerApi() {
}

public boolean login(String username, String password) {
return "HansChen".equals(username) && "123456".equals(password);
}
}

方法二:用@Singleton注解Module的provide方法:

1
2
3
4
5
6
7
8
9
@Module
public class ApplicationModule {

@Singleton
@Provides
ServerApi provideServerApi() {
return new ServerApi();
}
}

然后我们有个Component:

1
2
3
4
5
6
@Singleton
@Component(modules = ApplicationModule.class)
public interface ApplicationComponent {

ServerApi getServerApi();
}

然后执行依赖注入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class MainActivity extends AppCompatActivity {

@Inject
ServerApi mService;

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

ApplicationComponent component = DaggerApplicationComponent.create();
Log.d("Hans", component.getServerApi().toString());
Log.d("Hans", component.getServerApi().toString());
Log.d("Hans", component.getServerApi().toString());
}
}

使用了以上两种方法的任意一种,我们都会发现,通过component.getServerApi()获得的实例都是同一个实例。不过要注意一点的是,如果类用@Singleton注解了,但Module中又存在一个provide方法是提供该类实例的,但provide方法没有用@Singleton注解,那么Component中获取该实例就不是单例的,因为会优先查找Module的方法。
这个单例是相对于同一个Component而言的,不同的Component获取到的实例将会是不一样的。

自定义Scope

既然一个没有scope的component不可以依赖一个有scope的组件component,那么我们必然需要自定义scope来去注解自己的Component了,定义方法如下:

1
2
3
4
5
@Documented
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface FragmentScoped {
}

定义出来的FragmentScoped在使用上和Singleton是一样的,那它和Singleton除了是不一样的注解之外,还有什么不一样呢?答案是没有!我们自定义的scope和Singleton并没有任何不一样,不会因为Singleton是java自带的注解就会有什么区别。

那么,这个scope的设定是为了什么呢?

scope的作用

scope除了修饰provide方法可以让我们获得在同一个Component实例范围内的单例之外,主要的作用就是对Component和Moduld的分层管理以及依赖逻辑的可读性。
这里借用一个网络上的图片说明:
2019-9-2-11-44-58.png

ApplicationComponent一般会用singleton注解,相对的,它的Module中provide方法也只能用singleton注解。UserComponent是用UserSCope能直接使用ApplicationModule吗?不能!因为他俩的scope不一致,这就是这个设定带来的好处,防止不同层级的组件混乱。另外,因为有了scope的存在,各种组件的作用和生命周期也变得可读起来了

Lazy注入

有时可能会需要延迟获取一个实例。对任何绑定的 T,可以构建一个 Lazy 来延迟实例化直至第一次调用 Lazy 的 get() 方法。注入之后,第一次get的时会实例化出 T,之后的调用都会获取相同的实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MainActivity extends AppCompatActivity implements MainView {

// 懒加载
@Inject
Lazy<MainPresenter> mPresenter;

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

MainPresenterComponent component = DaggerMainPresenterComponent.builder()
.mainPresenterModule(new MainPresenterModule(this))
.applicationComponent(((DemoApplication) getApplication()).getAppComponent())
.build();
component.inject(this);
Log.d("Hans", mPresenter.get().toString()); // 实例化MainPresenter
Log.d("Hans", mPresenter.get().toString()); // 跟上次获取的实例是同一个实例
}
}

Provider注入

跟Lazy注入不一样的是,有时候我们希望每次调用get的时候,获取到的实例都是不一样的,这时候可以用Provider注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class MainActivity extends AppCompatActivity implements MainView {

// Provider
@Inject
Provider<MainPresenter> mPresenter;

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

MainPresenterComponent component = DaggerMainPresenterComponent.builder()
.mainPresenterModule(new MainPresenterModule(this))
.applicationComponent(((DemoApplication) getApplication()).getAppComponent())
.build();
component.inject(this);
Log.d("Hans", mPresenter.get().toString()); // 实例化MainPresenter
Log.d("Hans", mPresenter.get().toString()); // 获取新的MainPresenter实例
}
}

Qualifiers注入

到目前为止,我们的demo里,Moduld的provide返回的对象都是不一样的,但是下面这种情况就不好处理了:

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

......

// 返回ServerApi实例
@Provides
ServerApi provideServerApiA(Context context) {
return new ServerApiImplA(context);
}

// 返回ServerApi实例
@Provides
ServerApi provideServerApiB(Context context) {
return new ServerApiImplB(context);
}
}

provideServerApiA和provideServerApiB返回的都是ServerApi,Dagger是无法判断用哪个provide方法的。这时候就需要添加Qualifiers了:

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

......

@Provides
@Named("ServerApiImplA")
ServerApi provideServerApiA(Context context) {
return new ServerApiImplA(context);
}

@Provides
@Named("ServerApiImplB")
ServerApi provideServerApiB(Context context) {
return new ServerApiImplB(context);
}
}

通过这样一个限定,就能区分出2个方法的区别了,当然,在使用过程中,也同样要指明你用哪个name的实例,Dagger会根据你的name来选取对应的provide方法:

1
2
3
4
5
6
7
8
9
10
11
public class MainPresenter {

private MainView mMainView;
private ServerApi mServerApi;

@Inject
public MainPresenter(MainView mainView, @Named("ServerApiImplA") ServerApi serverApi) {
this.mMainView = mainView;
this.mServerApi = serverApi;
}
}

除了用Named注解,你也可以创建你自己的限定注解:

1
2
3
4
5
6
@Qualifier
@Documented
@Retention(RUNTIME)
public @interface YourQualifier {
String value() default "";
}

编译时验证

Dagger 包含了一个注解处理器(annotation processor)来验证模块和注入。这个过程很严格而且会抛出错误,当有非法绑定或绑定不成功时。下面这个例子缺少了 Executor:

1
2
3
4
5
6
@Module
class DripCoffeeModule {
@Provides Heater provideHeater(Executor executor) {
return new CpuHeater(executor);
}
}

当编译时,javac 会拒绝绑定缺少的部分:

1
2
[ERROR] COMPILATION ERROR :
[ERROR] error: java.util.concurrent.Executor cannot be provided without an @Provides-annotated method.

可以通过给方法 Executor 添加@Provides注解来解决这个问题,或者标记这个模块是不完整的。不完整的模块允许缺少依赖关系

1
2
3
4
5
6
@Module(complete = false)
class DripCoffeeModule {
@Provides Heater provideHeater(Executor executor) {
return new CpuHeater(executor);
}
}

小结

第一次接触用Dagger框架写的代码时候,如果不了解各种注解作用的时候,那真会有一脸懵逼的感觉,而且单看文章,其实还是很抽象,建议大家用Dagger写个小demo玩玩,很快就上手了,这里提供几个使用Dagger的栗子,希望可以帮助大家上手Dagger

设计模式之工厂模式(Factory)

作者 chenhang
2016年11月26日 00:00

概述

根据依赖倒置原则,我们知道,我们应优先依赖抽象类而不是具体类。在应用开发过程中,有很多实体类都是非常易变的,依赖它们会带来问题,所以我们更应该依赖于抽象接口,已使我们免受大多数变化的影响。
工厂模式(Factory)允许我们只依赖于抽象接口就能创建出具体对象的实例,所以在开发中,如果具体类是高度易变的,那么该模式就非常有用。

接下来我们就通过代码举例说明什么是工厂模式

简单工厂模式

假设我们现在有个需求:把一段数据用Wi-Fi或者蓝牙发送出去。
需求很简单是吧?刷刷刷就写下了以下实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private String mode; //Wi-Fi|Bluetooth

public void onClick() {
byte[] data = {0x00, 0x01};

if ("Wi-Fi".equals(mode)) {
sendDataByWiFi(data);
} else {
sendDataByBluetooth(data);
}
}

private void sendDataByWiFi(byte[] data) {
// send data via Wi-Fi
}

private void sendDataByBluetooth(byte[] data) {
// send data via Bluetooth
}

但是上面的代码扩展性并不高,违反了开放封闭原则。比如现在又有了个新的需求,需要用zigbee把数据发送出去,就得再新增一个sendDataByZigbee方法了,而且还得修改onClick里面的逻辑。那么比较好的方法是怎么样的呢?

定义一个数据发送器类:

1
2
3
4
5
6
7
8
9
/**
* 数据发送器Sender
*
* @author HansChen
*/
public interface Sender {

void sendData(byte[] data);
}

实现WiFi数据发送:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Sender的实现类,通过Wi-Fi发送数据
*
* @author HansChen
*/
public class WiFiSender implements Sender {

@Override
public void sendData(byte[] data) {
System.out.println("Send data by Wi-Fi");
}
}

实现蓝牙数据发送:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Sender的实现类,通过蓝牙发送数据
*
* @author HansChen
*/
public class BluetoothSender implements Sender {

@Override
public void sendData(byte[] data) {
System.out.println("Send data by Bluetooth");
}
}

这样,原来发送数据的地方就改为了:

1
2
3
4
5
6
7
8
9
10
11
12
13
private String mode; //Wi-Fi|Bluetooth

public void onClick() {
byte[] data = {0x00, 0x01};

Sender sender;
if ("Wi-Fi".equals(mode)) {
sender = new WiFiSender();
} else {
sender = new BluetoothSender();
}
sender.sendData(data);
}

有没有觉得代码优雅了一点?但是随着发送器Sender的实现类越来越多,每增加一个实现类,就需要在onClick里面实例化相应的实现类,能不能用一个单独的类来做这个创造实例的过程呢?这就是我们讲到的工厂。我们新增一个工厂类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 简单工厂类
*
* @author HansChen
*/
public class SimpleFactory {

public static Sender createSender(String mode) {
switch (mode) {
case "Wi-Fi":
return new WiFiSender();
case "Bluetooth":
return new BluetoothSender();
default:
throw new IllegalArgumentException("illegal type: " + mode);
}
}
}

这样一来,怎么实例化数据发送器我们也不用管了,最终代码变为:

1
2
3
4
5
6
7
8
private String mode; //Wi-Fi|Bluetooth

public void onClick() {
byte[] data = {0x00, 0x01};

Sender sender = SimpleFactory.createSender(mode);
sender.sendData(data);
}

好了,到这里我们就完成了简单工厂模式的应用了,下图就是简单工厂模式的结构图:
2019-9-2-11-28-47.png

工厂方法模式

简单工厂模式的优点在于工厂类包含了必要的判断逻辑,根据传入的参数动态实例化相关的类,对于客户端来说,去除了与具体产品的依赖。但是这里还是会有个问题,假设上面例子中新增了一个zigbee发送器,那么一定是需要修改简单工厂类的,也就是说,我们不但对扩展开放了,对修改也开放了,这是不好的。解决的方法是使用工厂方法模式,工厂方法模式是指定义一个用于创建对象的接口,让子类决定实例化哪一个类。下面还是通过代码来说明:

在简单工厂模式的基础上,让我们对工厂类也升级一下,首先定义一个工厂类接口:

1
2
3
4
public interface SenderFactory {

Sender createSender();
}

然后为每一个发送器的实现类各创建一个具体的工厂方法去实现这个接口

定义WiFiSender的工厂类:

1
2
3
4
5
6
7
public class WiFiSenderFactory implements SenderFactory {

@Override
public Sender createSender() {
return new WiFiSender();
}
}

定义BluetoothSender的工厂类:

1
2
3
4
5
6
7
public class BluetoothSenderFactory implements SenderFactory {

@Override
public Sender createSender() {
return new BluetoothSender();
}
}

这样,即使有新的Sender实现类加进来,我们只需要新增相应的工厂类就行了,不需要修改原有的工厂,下图就是工厂方法模式的结构图:
2019-9-2-11-28-17.png

客户端调用代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private String mode; //Wi-Fi|Bluetooth

public void onClick() {
byte[] data = {0x00, 0x01};

SenderFactory factory;
if ("Wi-Fi".equals(mode)) {
factory = new WiFiSenderFactory();
} else {
factory = new BluetoothSenderFactory();
}
Sender sender = factory.createSender();
sender.sendData(data);
}

细心的读者可能已经发现了,工厂方法模式实现时,客户端需要决定实例化哪一个工厂类,相比于简单工厂模式,客户端多了一个选择判断的问题,也就是说,工厂方法模式把简单工厂模式的内部逻辑判断移到了客户端!你想要加功能,本来是修改简单工厂类的,现在改为修改客户端。但是这样带来的好处是整个工厂和产品体系都没有“修改”的变化,只有“扩展”的变化,完全符合了开放封闭原则。

总结

简单工厂模式和工厂方法模式都封装了对象的创建,它们使得高层策略模块在创建类的实例时无需依赖于这些类的具体实现。但是两种工厂模式之间又有差异:

  • 简单工厂模式:最大的优点在于工厂类包含了必要的判断逻辑,根据客户端的条件动态地实例化相关的类。但这也是它的缺点,当扩展功能的时候,需要修改工厂方法,违反了开放封闭原则
  • 工厂方法模式:符合开放封闭原则,但这带来的代价是扩展的时候要增加相应的工厂类,增加了开发量,而且需要修改客户端代码

Fragment源码分析

作者 chenhang
2016年11月15日 00:00

概述

Fragment表示 Activity 中的行为或用户界面部分。您可以将多个 Fragment 组合在一个 Activity 中来构建多窗格 UI,以及在多个 Activity 中重复使用某个 Fragment。您可以将 Fragment 视为 Activity 的模块化组成部分,它具有自己的生命周期,能接收自己的输入事件,并且您可以在 Activity 运行时添加或移除 Fragment。
Fragment 必须始终嵌入在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影响。 例如,当 Activity 暂停时,其中的所有 Fragment 也会暂停;当 Activity 被销毁时,所有 Fragment 也会被销毁。 不过,当 Activity 正在运行(处于已恢复生命周期状态)时,您可以独立操纵每个 Fragment,如添加或移除它们。 当您执行此类 Fragment 事务时,您也可以将其添加到由 Activity 管理的返回栈 — Activity 中的每个返回栈条目都是一条已发生 Fragment 事务的记录。 返回栈让用户可以通过按返回按钮撤消 Fragment 事务(后退)。

当您将 Fragment 作为 Activity 布局的一部分添加时,它存在于 Activity 视图层次结构的某个 ViewGroup 内部,并且 Fragment 会定义其自己的视图布局。您可以通过在 Activity 的布局文件中声明Fragment,将其作为 <fragment> 元素插入您的 Activity 布局中,或者通过将其添加到某个现有 ViewGroup,利用应用代码进行插入。不过,Fragment 并非必须成为 Activity 布局的一部分;您还可以将没有自己 UI 的 Fragment 用作 Activity 的不可见工作线程。

本文将通过分析源码,对 Fragment 的创建、销毁以及生命周期做一个更深入的认识。

建议读者在看这篇文章的时候,先看下Fragment事务管理源码分析,对Fragment管理类先有一个比较清楚的认识。

分析入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 构造并显示Fragment
*
* @param containerViewId 容器控件id
* @param clz Fragment类
*/
protected void showFragment(@IdRes int containerViewId, Class<? extends Fragment> clz) {
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();//开始事务管理
try {
Fragment f = clz.newInstance();
ft.add(containerViewId, f, clz.getName());//添加操作
ft.commit();//提交事务
} catch (Exception e) {
e.printStackTrace();
}
}

上面的代码就是动态地往containerViewId里添加一个Fragment并让它显示出来,可以看到,这个涉及到Fragment的事务管理,详细可以参考Fragment事务管理源码分析,这里就不再阐述了。

代码分析

BackStackRecord#run

调用了commit之后,真正执行的地方是在BackStackRecord的run方法:

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
public void run() {

......

if (mManager.mCurState >= Fragment.CREATED) {
SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
calculateFragments(firstOutFragments, lastInFragments);
beginTransition(firstOutFragments, lastInFragments, false);
}
//遍历链表,根据cmd事务类型依次处理事务
Op op = mHead;
while (op != null) {
switch (op.cmd) {
case OP_ADD: {
//添加一个新的Fragment
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.addFragment(f, false);
}
break;
case OP_REPLACE: {
Fragment f = op.fragment;
int containerId = f.mContainerId;
if (mManager.mAdded != null) {
for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {
Fragment old = mManager.mAdded.get(i);
if (old.mContainerId == containerId) {
if (old == f) {
op.fragment = f = null;
} else {
if (op.removed == null) {
op.removed = new ArrayList<Fragment>();
}
op.removed.add(old);
old.mNextAnim = op.exitAnim;
if (mAddToBackStack) {
old.mBackStackNesting += 1;
}
mManager.removeFragment(old, mTransition, mTransitionStyle);
}
}
}
}
if (f != null) {
f.mNextAnim = op.enterAnim;
mManager.addFragment(f, false);
}
}
break;
case OP_REMOVE: {
Fragment f = op.fragment;
f.mNextAnim = op.exitAnim;
mManager.removeFragment(f, mTransition, mTransitionStyle);
}
break;
case OP_HIDE: {
Fragment f = op.fragment;
f.mNextAnim = op.exitAnim;
mManager.hideFragment(f, mTransition, mTransitionStyle);
}
break;
case OP_SHOW: {
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.showFragment(f, mTransition, mTransitionStyle);
}
break;
case OP_DETACH: {
Fragment f = op.fragment;
f.mNextAnim = op.exitAnim;
mManager.detachFragment(f, mTransition, mTransitionStyle);
}
break;
case OP_ATTACH: {
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.attachFragment(f, mTransition, mTransitionStyle);
}
break;
default: {
throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
}
}

op = op.next;
}

mManager.moveToState(mManager.mCurState, mTransition,
mTransitionStyle, true);

if (mAddToBackStack) {
mManager.addBackStackState(this);
}
}

因为我们调用的是add操作,所以执行的代码片段是:

1
2
3
4
5
6
case OP_ADD: {
Fragment f = op.fragment;
f.mNextAnim = op.enterAnim;
mManager.addFragment(f, false);
}
break;

参数解释:

  • op.fragment:showFragment中创建的Fragment实例,并且现在Fragment的mTag、mFragmentId、mContainerId已被初始化过了
  • op.enterAnim:入场动画,可以先不管
  • mManager:FragmentManagerImpl实例

FragmentManagerImpl#addFragment

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 void addFragment(Fragment fragment, boolean moveToStateNow) {
//已添加的Fragment列表
if (mAdded == null) {
mAdded = new ArrayList<Fragment>();
}

//设置Fragment的mIndex,并把Fragment添加到mActive列表
makeActive(fragment);

//判断是否被detach。默认为false
if (!fragment.mDetached) {
if (mAdded.contains(fragment)) {
throw new IllegalStateException("Fragment already added: " + fragment);
}
//把Fragment添加到mAdded列表
mAdded.add(fragment);
//设置Fragment标记位
fragment.mAdded = true;
fragment.mRemoving = false;
//判断是否需要刷新菜单
if (fragment.mHasMenu && fragment.mMenuVisible) {
mNeedMenuInvalidate = true;
}
//在这次分析中moveToStateNow为false,moveToState方法在本方法外层方法中调用
if (moveToStateNow) {
moveToState(fragment);
}
}
}

addFragment里面把Fragment加入mActive和mAdded列表,并且设置标记为fragment.mAdded为true,fragment.mRemoving为false。
执行完ADD操作后,执行moveToState,moveToState顾名思义,就是把Fragment变为某种状态

1
2
3
4
5
6
7
8
//mManager.mCurState的状态很重要,我们下面会分析它现在处于什么状态
mManager.moveToState(mManager.mCurState, mTransition,
mTransitionStyle, true);

//添加本次操作到回退栈中
if (mAddToBackStack) {
mManager.addBackStackState(this);
}

Fragment状态

我们知道Fragment的生命周期是依赖于Activity的,比如Activity处于onResume,那么Fragment也会处于onResume状态,这里的参数mManager.mCurState对应的状态有:

1
2
3
4
5
6
7
static final int INVALID_STATE = -1;   // Invalid state used as a null value.
static final int INITIALIZING = 0; // Not yet created.
static final int CREATED = 1; // Created.
static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
static final int STOPPED = 3; // Fully created, not started.
static final int STARTED = 4; // Created and started, not resumed.
static final int RESUMED = 5; // Created started and resumed.

mCurState的初始状态是Fragment.INITIALIZING,那么在BackStackRecord中调用moveToState的时候,mCurState是什么值呢?它是会受Activity生命周期影响而变化的,我们来看下FragmentActivity的代码

1
2
3
4
5
6
7
8
9
10
11
12
@SuppressWarnings("deprecation")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//绑定FragmentManager
mFragments.attachHost(null /*parent*/);

super.onCreate(savedInstanceState);
... ...

//分发Fragment的create事件
mFragments.dispatchCreate();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void dispatchCreate() {
mHost.mFragmentManager.dispatchCreate();
}

public void dispatchCreate() {
mStateSaved = false;
//注意这里设置了新的state
moveToState(Fragment.CREATED, false);
}

void moveToState(int newState, boolean always) {
moveToState(newState, 0, 0, always);
}

void moveToState(int newState, int transit, int transitStyle, boolean always) {

... ...
//给mCurState赋值
mCurState = newState;
... ...
}

在onCreate中把mCurState变为Fragment.CREATED状态了,Activity的其他生命周期方法回调的时候,也会改变这个状态,大致整理如下:

  • onCreate:Fragment.CREATED
  • onStart:Fragment.ACTIVITY_CREATED–>Fragment.STARTED (Fragment.ACTIVITY_CREATED只会在Activity创建之后触发一次,Fragment.STARTED每次onStart的时候都会触发)
  • onResume:Fragment.RESUMED
  • onPause:Fragment.STARTED
  • onStop:Fragment.STOPPED
  • onDestroy:Fragment.INITIALIZING

下面是一张状态迁移图:
2019-9-2-11-22-55.png

所以随着Activity生命周期的推进,Activity内所有Fragment的生命周期也会跟着推进。从Activity创建到显示出来,最后会处于onResume状态,那么我们这次就直接分析当前Activity处于onResume调用之后的情形好了。所以假定现在mCurState为Fragment.RESUMED

让我们继续跟踪FragmentManagerImpl

FragmentManagerImpl#moveToState

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
void moveToState(int newState, int transit, int transitStyle, boolean always) {
if (mHost == null && newState != Fragment.INITIALIZING) {
throw new IllegalStateException("No activity");
}

if (!always && mCurState == newState) {
return;
}

mCurState = newState;
if (mActive != null) {
boolean loadersRunning = false;
//遍历所有Active状态的Fragment,改变所有Fragment的状态
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null) {
//关键代码
moveToState(f, newState, transit, transitStyle, false);
if (f.mLoaderManager != null) {
loadersRunning |= f.mLoaderManager.hasRunningLoaders();
}
}
}

if (!loadersRunning) {
startPendingDeferredFragments();
}

//让Activity刷新Menu
if (mNeedMenuInvalidate && mHost != null && mCurState == Fragment.RESUMED) {
mHost.onInvalidateOptionsMenu();
mNeedMenuInvalidate = false;
}
}
}

设置最新的mCurState状态,通过上面的分析,我们知道newState等于Fragment.RESUMED。遍历mActive列表中保存的Fragment,改变Fragment状态,这里又调用了一个moveToState方法,这个方法就是真正回调Fragment生命周期的地方

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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {

// Fragments被detach或Fragment没有添加到mAdded列表的话,设置目标Fragment的新状态为CREATED状态,此次分析中不会进入这个分支
if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) {
newState = Fragment.CREATED;
}
//此次分析中f.mRemoving为false
if (f.mRemoving && newState > f.mState) {
// While removing a fragment, we can't change it to a higher state.
newState = f.mState;
}
// 是否延时启动
if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) {
newState = Fragment.STOPPED;
}

if (f.mState < newState) {
//此次命中的分支
......

//根据Fragment当前的状态,选择case的分支。需要注意的是,这里的switch case是没有break语句的。这种设计可以让Fragment把自身的状态依次推进到目标状态
switch (f.mState) {
case Fragment.INITIALIZING:
if (f.mSavedFragmentState != null) {
......
}
f.mHost = mHost;
//mParent是在FragmentActivity的onCreate方法中调用attachHost传进来的,传进来的是空值
f.mParentFragment = mParent;
f.mFragmentManager = mParent != null
? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl();
f.mCalled = false;
//【Fragment生命周期】onAttach回调,里面会把mCalled设置为true
f.onAttach(mHost.getContext());
if (!f.mCalled) {
throw new SuperNotCalledException("Fragment " + f
+ " did not call through to super.onAttach()");
}

if (f.mParentFragment == null) {
//让Activity可以监听到Fragment的attach
mHost.onAttachFragment(f);
} else {
f.mParentFragment.onAttachFragment(f);
}

//f.mRetaining默认为false
if (!f.mRetaining) {
//关键代码,内部会调用【Fragment生命周期】onCreate
f.performCreate(f.mSavedFragmentState);
} else {
f.restoreChildFragmentState(f.mSavedFragmentState, true);
f.mState = Fragment.CREATED;
}
f.mRetaining = false;

//Fragment是否定义在Layout文件的<fragment>标签中的,本次栗子为代码动态添加Fragment,所以为false
if (f.mFromLayout) {
// For fragments that are part of the content view
// layout, we need to instantiate the view immediately
// and the inflater will take care of adding it.
f.mView = f.performCreateView(f.getLayoutInflater(
f.mSavedFragmentState), null, f.mSavedFragmentState);
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
if (f.mHidden) f.mView.setVisibility(View.GONE);
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
}
//注意,这里没有break
case Fragment.CREATED:
if (newState > Fragment.CREATED) {
if (!f.mFromLayout) {
//开始创建Fragment的view
ViewGroup container = null;
if (f.mContainerId != 0) {
if (f.mContainerId == View.NO_ID) {
throwException(new IllegalArgumentException(""));
}

//调用Activity的findViewById方法查找控件
container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
if (container == null && !f.mRestored) {
......
}
}
f.mContainer = container;
//关键代码,内部会调用【Fragment生命周期】onCreateView,并返回Fragment中new出的视图
f.mView = f.performCreateView(f.getLayoutInflater(
f.mSavedFragmentState), container, f.mSavedFragmentState);
if (f.mView != null) {
f.mView.setSaveFromParentEnabled(false);
if (container != null) {
//设置入场动画
Animator anim = loadAnimator(f, transit, true,
transitionStyle);
if (anim != null) {
anim.setTarget(f.mView);
setHWLayerAnimListenerIfAlpha(f.mView, anim);
anim.start();
}
//把Fragment的view加入到父控件
container.addView(f.mView);
}
if (f.mHidden) f.mView.setVisibility(View.GONE);

//【Fragment生命周期】onViewCreated回调
f.onViewCreated(f.mView, f.mSavedFragmentState);
}
}

//关键代码,内部会调用【Fragment生命周期】onActivityCreated
f.performActivityCreated(f.mSavedFragmentState);
if (f.mView != null) {
f.restoreViewState(f.mSavedFragmentState);
}
f.mSavedFragmentState = null;
}
case Fragment.ACTIVITY_CREATED:
if (newState > Fragment.ACTIVITY_CREATED) {
f.mState = Fragment.STOPPED;
}
case Fragment.STOPPED:
if (newState > Fragment.STOPPED) {
if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
//关键代码,内部会调用【Fragment生命周期】onStart
f.performStart();
}
case Fragment.STARTED:
if (newState > Fragment.STARTED) {
//关键代码,内部会调用【Fragment生命周期】onResume
f.performResume();
// Get rid of this in case we saved it and never needed it.
f.mSavedFragmentState = null;
f.mSavedViewState = null;
}
}
} else if (f.mState > newState) {
//state降级处理
......
}

if (f.mState != newState) {
f.mState = newState;
}
}

这段代码逻辑还是比较长,我把注释写在代码里了。可以看到,这个代码写得很巧妙,通过switch case控制,可以一层一层地把Fragment的生命周期推进下去,比如当前fragnemt的state是Fragment.STARTED,那么它就只会执行performResume,如果Fragment的状态是Fragment.INITIALIZING,那么就会从switch的最开始依次执行下来,把Fragment的生命周期onAttach–>onResume依次调用。
简要说明下上面的代码:

  • mHost是FragmentHostCallback抽象类的实例,它的实现类是Activity的HostCallbacks
  • mParent为null
  • mHost.getContext()获取的context就是宿主Activity实例
  • Fragment中创建的View会自动通过container.addView(f.mView)添加到父控件中

很多Fragment的生命周期是通过Fragment的performXxx()方法去调用的,比如:

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
void performCreate(Bundle savedInstanceState) {
......
onCreate(savedInstanceState);
......
}

View performCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
......
return onCreateView(inflater, container, savedInstanceState);
}

void performActivityCreated(Bundle savedInstanceState) {
......
onActivityCreated(savedInstanceState);
......
}

void performStart() {
......
onStart();
......
}

void performResume() {
......
onResume();
......
}

Fragment状态的降级操作

有些童鞋们可能会有疑问,上面只分析到了onAttach->onResume生命周期的回调,那onPause、onDestroy等方法又是什么时候执行的呢?我们再看下刚才的代码

1
2
3
4
5
6
if (f.mState < newState) {
......
} else if (f.mState > newState) {
//state降级处理
......
}

答案就是在else if分支里面,比如当Acivity锁屏的时候,就Activity生命周期会自动回调onPause,从而触发dispatchPause,在里面调用moveToState(Fragment.STARTED, false);
由于Fragment当前的状态是RESUMED状态,大于newState,所以就会走else if的分支,触发相应的生命周期方法。else if分支的逻辑和state升级的差不多,这里就再进行分析了

生命周期

2019-9-2-11-26-16.png
最后,放张官网上公布的Fragment生命周期图,通过代码分析,我们发现代码的中生命周期的调用顺序和图中确实是一致的

总结

本文大致地从源码的角度分析了Fragment创建、生命周期回调的过程,如果读者对Fragment的removereplacehidedetachattach等操作有兴趣的话,可以自行分析,核心代码主要在BackStackRecord类的run方法以及FragmentManagerImpl的moveToState方法中。

❌
❌