程序员徐公

微信公众号:【徐公】

0%

Android 开发中,组件化,模块化是一个老生常谈的问题。随着项目复杂性的增长,模块化是一个必然的趋势。除非你能忍受改一下代码,就需要六七分钟的漫长时间。

模块化,组件化随之带来的另外一个问题是页面的跳转问题,由于代码的隔离,代码之间有时候会无法互相访问。于是,路由(Router)框架诞生了。

目前用得比较多的有阿里的 ARouter,美团的 WMRouter,ActivityRouter 等。

今天,就让我们一起来看一下怎样实现一个路由框架。
实现的功能有。

  1. 基于编译时注解,使用方便
  2. 结果回调,每次跳转 Activity 都会回调跳转结果
  3. 除了可以使用注解自定义路由,还支持手动分配路由
  4. 支持多模块使用,支持组件化使用

使用说明

基本使用

第一步,在要跳转的 activity 上面注明 path,

1
2
3
4
5
6
7
8
@Route(path = "activity/main")
public class MainActivity extends AppCompatActivity {

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

在要跳转的地方

1
Router.getInstance().build("activity/main").navigation(this);

如果想在多 moule 中使用

第一步,使用 @Modules({"app", "sdk"}) 注明总共有多少个 moudle,并分别在 moudle 中注明当前 moudle 的 名字,使用 @Module("") 注解。注意 @Modules({“app”, “sdk”}) 要与 @Module(“”) 一一对应。

在主 moudle 中,

1
2
3
4
5
6
7
8
9
10
@Modules({"app", "moudle1"})
@Module("app")
public class RouterApplication extends Application {

@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
Router.getInstance().init();
}
}

在 moudle1 中,

1
2
3
4
5
6
7
8
9
10
@Route(path = "my/activity/main")
@Module("moudle1")
public class MainActivity extends AppCompatActivity {

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

这样就可以支持多模块使用了。

自定义注入 router

1
Router.getInstance().add("activity/three", ThreeActivity.class);

跳转的时候调用

1
Router.getInstance().build("activity/three").navigation(this);

结果回调

路由跳转结果回调。

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
Router.getInstance().build("my/activity/main", new RouterCallback() {
@Override
public boolean beforeOpen(Context context, Uri uri) {
// 在打开路由之前
Log.i(TAG, "beforeOpen: uri=" + uri);
return false;
}

// 在打开路由之后(即打开路由成功之后会回调)
@Override
public void afterOpen(Context context, Uri uri) {
Log.i(TAG, "afterOpen: uri=" + uri);

}

// 没有找到改 uri
@Override
public void notFind(Context context, Uri uri) {
Log.i(TAG, "notFind: uri=" + uri);

}

// 发生错误
@Override
public void error(Context context, Uri uri, Throwable e) {
Log.i(TAG, "error: uri=" + uri + ";e=" + e);
}
}).navigation(this);

startActivityForResult 跳转结果回调

1
2
3
4
5
6
7
Router.getInstance().build("activity/two").navigation(this, new Callback() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.i(TAG, "onActivityResult: requestCode=" + requestCode + ";resultCode=" + resultCode + ";data=" + data);
}
});


原理说明

实现一个 Router 框架,涉及到的主要的知识点如下:

  1. 注解的处理
  2. 怎样解决多个 module 之间的依赖问题,以及如何支持多 module 使用
  3. router 跳转及 activty startActivityForResult 的处理

我们带着这三个问题,一起来探索一下。

总共分为四个部分,router-annotion, router-compiler,router-api,stub

router-annotion 主要是定义注解的,用来存放注解文件

router-compiler 主要是用来处理注解的,自动帮我们生成代码

router-api 是对外的 api,用来处理跳转的。

stub 这个是存放一些空的 java 文件,提前占坑。不会打包进 jar。

router-annotion

主要定义了三个注解

1
2
3
4
5
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
String path();
}
1
2
3
4
5
@Retention(RetentionPolicy.CLASS)
public @interface Modules {
String[] value();
}

1
2
3
4
@Retention(RetentionPolicy.CLASS)
public @interface Module {
String value();
}

Route 注解主要是用来注明跳转的 path 的。

Modules 注解,注明总共有多少个 moudle。

Module 注解,注明当前 moudle 的名字。

Modules,Module 注解主要是为了解决支持多 module 使用的。


router-compiler

router-compiler 只有一个类 RouterProcessor,他的原理其实也是比较简单的,扫描那些类用到注解,并将这些信息存起来,做相应的处理。这里是会生成相应的 java 文件。

主要包括以下两个步骤

  1. 根据是否有 @Modules @Module 注解,然后生成相应的 RouterInit 文件
  2. 扫描 @Route 注解,并根据 moudleName 生成相应的 java 文件

注解基本介绍

在讲解 RouterProcessor 之前,我们先来了解一下注解的基本知识。

如果对于自定义注解还不熟悉的话,可以先看我之前写的这两篇文章。Android 自定义编译时注解1 - 简单的例子Android 编译时注解 —— 语法详解

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 RouterProcessor extends AbstractProcessor {
private static final boolean DEBUG = true;
private Messager messager;
private Filer mFiler;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
mFiler = processingEnv.getFiler();
UtilManager.getMgr().init(processingEnv);
}


/**
* 定义你的注解处理器注册到哪些注解上
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(Route.class.getCanonicalName());
annotations.add(Module.class.getCanonicalName());
annotations.add(Modules.class.getCanonicalName());
return annotations;
}

/**
* java版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}


}

首先我们先来看一下 getSupportedAnnotationTypes 方法,这个方法返回的是我们支持扫描的注解。

注解的处理

接下来我们再一起来看一下 process 方法

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
 @Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 注解为 null,直接返回
if (annotations == null || annotations.size() == 0) {
return false;
}

UtilManager.getMgr().getMessager().printMessage(Diagnostic.Kind.NOTE, "process");
boolean hasModule = false;
boolean hasModules = false;
// module
String moduleName = "RouterMapping";
Set<? extends Element> moduleList = roundEnv.getElementsAnnotatedWith(Module.class);
if (moduleList != null && moduleList.size() > 0) {
Module annotation = moduleList.iterator().next().getAnnotation(Module.class);
moduleName = moduleName + "_" + annotation.value();
hasModule = true;
}
// modules
String[] moduleNames = null;
Set<? extends Element> modulesList = roundEnv.getElementsAnnotatedWith(Modules.class);
if (modulesList != null && modulesList.size() > 0) {
Element modules = modulesList.iterator().next();
moduleNames = modules.getAnnotation(Modules.class).value();
hasModules = true;
}

debug("generate modules RouterInit annotations=" + annotations + " roundEnv=" + roundEnv);
debug("generate modules RouterInit hasModules=" + hasModules + " hasModule=" + hasModule);
// RouterInit
if (hasModules) { // 有使用 @Modules 注解,生成 RouterInit 文件,适用于多个 moudle
debug("generate modules RouterInit");
generateModulesRouterInit(moduleNames);
} else if (!hasModule) { // 没有使用 @Modules 注解,并且有使用 @Module,生成相应的 RouterInit 文件,使用与单个 moudle
debug("generate default RouterInit");
generateDefaultRouterInit();
}

// 扫描 Route 注解
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Route.class);
List<TargetInfo> targetInfos = new ArrayList<>();
for (Element element : elements) {
System.out.println("elements =" + elements);
// 检查类型
if (!Utils.checkTypeValid(element)) continue;
TypeElement typeElement = (TypeElement) element;
Route route = typeElement.getAnnotation(Route.class);
targetInfos.add(new TargetInfo(typeElement, route.path()));
}

// 根据 module 名字生成相应的 java 文件
if (!targetInfos.isEmpty()) {
generateCode(targetInfos, moduleName);
}
return false;
}

,首先判断是否有注解需要处理,没有的话直接返回 annotations == null || annotations.size() == 0

接着我们会判断是否有 @Modules 注解(这种情况是多个 moudle 使用),有的话会调用 generateModulesRouterInit(String[] moduleNames) 方法生成 RouterInit java 文件,当没有 @Modules 注解,并且没有 @Module (这种情况是单个 moudle 使用),会生成默认的 RouterInit 文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 private void generateModulesRouterInit(String[] moduleNames) {
MethodSpec.Builder initMethod = MethodSpec.methodBuilder("init")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC);
for (String module : moduleNames) {
initMethod.addStatement("RouterMapping_" + module + ".map()");
}
TypeSpec routerInit = TypeSpec.classBuilder("RouterInit")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(initMethod.build())
.build();
try {
JavaFile.builder(Constants.ROUTE_CLASS_PACKAGE, routerInit)
.build()
.writeTo(mFiler);
} catch (Exception e) {
e.printStackTrace();
}
}

假设说我们有”app”,”moudle1” 两个 moudle,那么我们最终生成的代码是这样的。

1
2
3
4
5
6
public final class RouterInit {
public static final void init() {
RouterMapping_app.map();
RouterMapping_moudle1.map();
}
}

如果我们都没有使用 @Moudles 和 @Module 注解,那么生成的 RouterInit 文件大概是这样的。

1
2
3
4
5
public final class RouterInit {
public static final void init() {
RouterMapping.map();
}
}

这也就是为什么有 stub module 的原因。因为默认情况下,我们需要借助 RouterInit 去初始化 map。如果没有这两个文件,ide 编辑器 在 compile 的时候就会报错。

1
compileOnly project(path: ':stub')

我们引入的方式是使用 compileOnly,这样的话再生成 jar 的时候,不会包括这两个文件,但是可以在 ide 编辑器中运行。这也是一个小技巧。

Route 注解的处理

我们回过来看 process 方法连对 Route 注解的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 扫描 Route 自己注解
Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Route.class);
List<TargetInfo> targetInfos = new ArrayList<>();
for (Element element : elements) {
System.out.println("elements =" + elements);
// 检查类型
if (!Utils.checkTypeValid(element)) continue;
TypeElement typeElement = (TypeElement) element;
Route route = typeElement.getAnnotation(Route.class);
targetInfos.add(new TargetInfo(typeElement, route.path()));
}

// 根据 module 名字生成相应的 java 文件
if (!targetInfos.isEmpty()) {
generateCode(targetInfos, moduleName);
}

首先会扫描所有的 Route 注解,并添加到 targetInfos list 当中,接着调用 generateCode 方法生成相应的文件。

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 void generateCode(List<TargetInfo> targetInfos, String moduleName) {

MethodSpec.Builder methodSpecBuilder = MethodSpec.methodBuilder("map")
// .addAnnotation(Override.class)
.addModifiers(Modifier.STATIC)
.addModifiers(Modifier.PUBLIC);

// .addParameter(parameterSpec);
for (TargetInfo info : targetInfos) {
methodSpecBuilder.addStatement("com.xj.router.api.Router.getInstance().add($S, $T.class)", info.getRoute(), info.getTypeElement());
}


TypeSpec typeSpec = TypeSpec.classBuilder(moduleName)
// .addSuperinterface(ClassName.get(interfaceType))
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpecBuilder.build())
.addJavadoc("Generated by Router. Do not edit it!\n")
.build();
try {
JavaFile.builder(Constants.ROUTE_CLASS_PACKAGE, typeSpec)
.build()
.writeTo(UtilManager.getMgr().getFiler());
System.out.println("generateCode: =" + Constants.ROUTE_CLASS_PACKAGE + "." + Constants.ROUTE_CLASS_NAME);
} catch (Exception e) {
e.printStackTrace();
System.out.println("generateCode:e =" + e);
}

}

这个方法主要是使用 javapoet 生成 java 文件,关于 javaposet 的使用可以见官网文档,生成的 java 文件是这样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.xj.router.impl;

import com.xj.arounterdemo.MainActivity;
import com.xj.arounterdemo.OneActivity;
import com.xj.arounterdemo.TwoActivity;

/**
* Generated by Router. Do not edit it!
*/
public class RouterMapping_app {
public static void map() {
com.xj.router.api.Router.getInstance().add("activity/main", MainActivity.class);
com.xj.router.api.Router.getInstance().add("activity/one", OneActivity.class);
com.xj.router.api.Router.getInstance().add("activity/two", TwoActivity.class);
}
}

可以看到我们定义的注解信息,最终都会调用 Router.getInstance().add() 方法存放起来。


router-api

这个 module 主要是多外暴露的 api,最主要的一个文件是 Router。

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 class Router {

private static final String TAG = "ARouter";

private static final Router instance = new Router();

private Map<String, Class<? extends Activity>> routeMap = new HashMap<>();
private boolean loaded;

private Router() {
}

public static Router getInstance() {
return instance;
}

public void init() {
if (loaded) {
return;
}
RouterInit.init();
loaded = true;
}
}

当我们想要初始化 Router 的时候,代用 init 方法即可。 init 方法会先判断是否初始化过,没有初始化过,会调用 RouterInit#init 方法区初始化。

而在 RouterInit#init 中,会调用 RouterMap_{@moduleName}#map 方法初始化,改方法又调用 Router.getInstance().add() 方法,从而完成初始化

router 跳转回调

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 interface RouterCallback {

/**
* 在跳转 router 之前
* @param context
* @param uri
* @return
*/
boolean beforeOpen(Context context, Uri uri);

/**
* 在跳转 router 之后
* @param context
* @param uri
*/
void afterOpen(Context context, Uri uri);

/**
* 没有找到改 router
* @param context
* @param uri
*/
void notFind(Context context, Uri uri);

/**
* 跳转 router 错误
* @param context
* @param uri
* @param e
*/
void error(Context context, Uri uri, Throwable e);
}
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 void navigation(Activity context, int requestCode, Callback callback) {
beforeOpen(context);
boolean isFind = false;
try {
Activity activity = (Activity) context;
Intent intent = new Intent();
intent.setComponent(new ComponentName(context.getPackageName(), mActivityName));
intent.putExtras(mBundle);
getFragment(activity)
.setCallback(callback)
.startActivityForResult(intent, requestCode);
isFind = true;
} catch (Exception e) {
errorOpen(context, e);
tryToCallNotFind(e, context);
}

if (isFind) {
afterOpen(context);
}

}

private void tryToCallNotFind(Exception e, Context context) {
if (e instanceof ClassNotFoundException && mRouterCallback != null) {
mRouterCallback.notFind(context, mUri);
}
}



主要看 navigation 方法,在跳转 activity 的时候,首先会会调用
beforeOpen 方法回调 RouterCallback#beforeOpen。接着 catch exception 的时候,如果发生错误,会调用 errorOpen 方法回调 RouterCallback#errorOpen 方法。同时调用 tryToCallNotFind 方法判断是否是 ClassNotFoundException,是的话回调 RouterCallback#notFind。

如果没有发生 eception,会回调 RouterCallback#afterOpen。

Activity 的 startActivityForResult 回调

可以看到我们的 Router 也是支持 startActivityForResult 的

1
2
3
4
5
6
7
Router.getInstance().build("activity/two").navigation(this, new Callback() {
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
Log.i(TAG, "onActivityResult: requestCode=" + requestCode + ";resultCode=" + resultCode + ";data=" + data);
}
});

它的实现原理其实很简单,是借助一个空白 fragment 实现的,原理的可以看我之前的这一篇文章。

Android Fragment 的妙用 - 优雅地申请权限和处理 onActivityResult


小结

如果觉得效果不错的话,请到 github 上面 star, 谢谢。 Router

我们的 Router 框架,流程大概是这样的。


题外话

看了上面的文章,文章一开头提到的三个问题,你懂了吗,欢迎在评论区留言评论。

  1. 注解的处理
  2. 怎样解决多个 module 之间的依赖问题,以及如何支持多 module 使用
  3. router 跳转及 activty startActivityForResult 的处理

其实,现在很多 router 框架都借助 gradle 插件来实现。这样有一个好处,就是在多 moudle 使用的时候,我们只需要 apply plugin 就 ok,对外屏蔽了一些细节。但其实,他的原理跟我们上面的原理都是差不多的。

接下来,我也会写 gradle plugin 相关的文章,并借助 gradle 实现 Router 框架。有兴趣的话可以关注我的微信公众号,徐公码字,谢谢。

相关文章

java Type 详解

java 反射机制详解

注解使用入门(一)

Android 自定义编译时注解1 - 简单的例子

Android 编译时注解 —— 语法详解

带你读懂 ButterKnife 的源码

Android Fragment 的妙用 - 优雅地申请权限和处理 onActivityResult

Android 点九图机制讲解及在聊天气泡中的应用

面试官,怎样实现 Router 框架?

扫一扫,欢迎关注我的微信公众号 stormjun94(徐公码字), 目前是一名程序员,不仅分享 Android开发相关知识,同时还分享技术人成长历程,包括个人总结,职场经验,面试经验等,希望能让你少走一点弯路。

@[toc]
往期文章

Android 面试必备 - http 与 https 协议

Android 面试必备 - 计算机网络基本知识(TCP,UDP,Http,https)

Android 面试必备 - 线程

Android 面试必备 - JVM 及 类加载机制

Android 面试必备 - 系统、App、Activity 启动过程

面试官系列- 你真的了解 http 吗

面试官问, https 真的安全吗,可以抓包吗,如何防止抓包吗

Android_interview github 地址

我的 CSDN 博客:https://blog.csdn.net/gdutxiaoxu

我的掘金:https://juejin.im/user/58aa8508570c35006bbd9e03

github: https://github.com/gdutxiaoxu/

微信公众号:徐公码字(stormjun94)

知乎:https://www.zhihu.com/people/xujun94

有兴趣的话可以关注我的公众号 徐公码字(stormjun94),第一时间会在上面更新

image

前言

转眼间,2020 年已过去一大半了,2020 年很难,各企业裁员的消息蛮多的,降职,不发年终奖等等。2020 年确实是艰难的一年。然而生活总是要继续,时间不给你丧的机会!如果我们能坚持下来,不断提高自己,说不定会有新的机会。

面试中,网络(http, https, tcp, udp), jvm, 类加载机制等这些基础的知识点是高频出现的,每个程序员都能说上好多。但不一定说到重点,以及理解背后的原理。

我在面试的过程中也经常被问到,于是总结记录了下来。千万不要小瞧这些基础,有时候,你算法,项目经验都过了,但是基础答得不太好。结果可能会通过,但这肯定会影响你的评级,这是特别吃亏的。所以,不如花点时间背一下,理解一下背后的原理。

举一个简单的例子, https 连接过程是怎样的,使用了了哪种加密方式,可以抓包吗,怎样防止抓包,你是否能够对答如下。

废话不多说,开始进入正文。

背景

我们知道,http 通信存在以下问题:

  • 通信使用明文可能会被窃听
  • 不验证通信方的身份可能遭遇伪装
  • 无法证明报文的完整型,可能已遭篡改

使用 https 可以解决数据安全问题,但是你真的理解 https 吗?

当面试官连续对你发出灵魂追问的时候,你能对答如流吗

  1. 什么是 https,为什么需要 https
  2. https 的连接过程
  3. https 的加密方式是怎样的,对称加密和非对称加密,为什么要这样设计?内容传输为什么要使用对称机密
  4. https 是绝对安全的吗
  5. https 可以抓包吗

如果你能对答自如,恭喜你,https 你已经掌握得差不多了,足够应付面试了。

什么是 https

简单来说, https 是 http + ssl,对 http 通信内容进行加密,是HTTP的安全版,是使用TLS/SSL加密的HTTP协议

Https的作用:

  1. 内容加密 建立一个信息安全通道,来保证数据传输的安全;
  2. 身份认证 确认网站的真实性
  3. 数据完整性 防止内容被第三方冒充或者篡改

什么是SSL

SSL 由 Netscape 公司于1994年创建,它旨在通过Web创建安全的Internet通信。它是一种标准协议,用于加密浏览器和服务器之间的通信。它允许通过Internet安全轻松地传输账号密码、银行卡、手机号等私密信息。

SSL证书就是遵守SSL协议,由受信任的CA机构颁发的数字证书。

SSL/TLS的工作原理:

需要理解SSL/TLS的工作原理,我们需要掌握加密算法。加密算法有两种:对称加密和非对称加密:

对称加密:通信双方使用相同的密钥进行加密。特点是加密速度快,但是缺点是需要保护好密钥,如果密钥泄露的话,那么加密就会被别人破解。常见的对称加密有AES,DES算法。

非对称加密:它需要生成两个密钥:公钥(Public Key)和私钥(Private Key)。

公钥顾名思义是公开的,任何人都可以获得,而私钥是私人保管的。相信大多程序员已经对这种算法很熟悉了:我们提交代码到github的时候,就可以使用SSH key:在本地生成私钥和公钥,私钥放在本地.ssh目录中,公钥放在github网站上,这样每次提交代码,不用麻烦的输入用户名和密码了,github会根据网站上存储的公钥来识别我们的身份。

公钥负责加密,私钥负责解密;或者,私钥负责加密,公钥负责解密。这种加密算法安全性更高,但是计算量相比对称加密大很多,加密和解密都很慢。常见的非对称算法有RSA。

https 的连接过程

image.png

https 的连接过程大概分为两个阶段,证书验证阶段和数据传输阶段

证书验证阶段

大概分为三个步骤

  1. 浏览器发起请求
  2. 服务器接收到请求之后,会返回证书,包括公钥
  3. 浏览器接收到证书之后,会检验证书是否合法,不合法的话,会弹出告警提示(怎样验证合法,下文会详细解析,这里先忽略)

数据传输阶段

证书验证合法之后

  1. 浏览器会生成一个随机数,
  2. 使用公钥进行加密,发送给服务端
  3. 服务器收到浏览器发来的值,使用私钥进行解密
  4. 解析成功之后,使用对称加密算法进行加密,传输给客户端

之后双方通信就使用第一步生成的随机数进行加密通信。

https 的加密方式是怎样的,对称加密和非对称加密,为什么要这样设计

从上面我们可以知道,https 加密是采用对称加密和非对称机密一起结合的。

在证书验证阶段,使用非对称加密。
在数据传输阶段,使用对称机密。

这样设计有一个好处,能最大程度得兼顾安全效率。

在证书验证阶段,使用非对称加密,需要公钥和私钥,假如浏览器的公钥泄漏了,我们还是能够确保随机数的安全,因为加密的数据只有用私钥才能解密。这样能最大程度确保随机数的安全。

在内容传输阶段,使用对称机密,可以大大提高加解密的效率。

内容传输为什么要使用对称机密

  1. 对称加密效率比较高
  2. 一对公私钥只能实现单向的加解密。只有服务端保存了私钥。如果使用非对称机密,相当于客户端必须有自己的私钥,这样设计的话,每个客户端都有自己的私钥,这很明显是不合理的,因为私钥是需要申请的。

https 是绝对安全的吗

不是绝对安全的,可以通过中间人攻击。

什么是中间人攻击

中间人攻击是指攻击者与通讯的两端分别创建独立的联系,并交换其所收到的数据,使通讯的两端认为他们正在通过一个私密的连接与对方直接对话,但事实上整个会话都被攻击者完全控制。

HTTPS 使用了 SSL 加密协议,是一种非常安全的机制,目前并没有方法直接对这个协议进行攻击,一般都是在建立 SSL 连接时,拦截客户端的请求,利用中间人获取到 CA证书、非对称加密的公钥、对称加密的密钥;有了这些条件,就可以对请求和响应进行拦截和篡改。

image.png

过程原理:

  1. 本地请求被劫持(如DNS劫持等),所有请求均发送到中间人的服务器
  2. 中间人服务器返回中间人自己的证书
  3. 客户端创建随机数,通过中间人证书的公钥对随机数加密后传送给中间人,然后凭随机数构造对称加密对传输内容进行加密传输
  4. 中间人因为拥有客户端的随机数,可以通过对称加密算法进行内容解密
  5. 中间人以客户端的请求内容再向正规网站发起请求
  6. 因为中间人与服务器的通信过程是合法的,正规网站通过建立的安全通道返回加密后的数据
  7. 中间人凭借与正规网站建立的对称加密算法对内容进行解密
  8. 中间人通过与客户端建立的对称加密算法对正规内容返回的数据进行加密传输
  9. 客户端通过与中间人建立的对称加密算法对返回结果数据进行解密

由于缺少对证书的验证,所以客户端虽然发起的是 HTTPS 请求,但客户端完全不知道自己的网络已被拦截,传输内容被中间人全部窃取。

https 是如何防止中间人攻击的

在https中需要证书,证书的作用是为了防止”中间人攻击”的。 如果有个中间人M拦截客户端请求,然后M向客户端提供自己的公钥,M再向服务端请求公钥,作为”中介者” 这样客户端和服务端都不知道,信息已经被拦截获取了。这时候就需要证明服务端的公钥是正确的.

怎么证明呢?

就需要权威第三方机构来公正了.这个第三方机构就是CA. 也就是说CA是专门对公钥进行认证,进行担保的,也就是专门给公钥做担保的担保公司。 全球知名的CA也就100多个,这些CA都是全球都认可的,比如VeriSign、GlobalSign等,国内知名的CA有WoSign。

浏览器是如何确保CA证书的合法性?

一、证书包含什么信息?

颁发机构信息、公钥、公司信息、域名、有效期、指纹……

二、证书的合法性依据是什么?

首先,权威机构是要有认证的,不是随便一个机构都有资格颁发证书,不然也不叫做权威机构。另外,证书的可信性基于信任制,权威机构需要对其颁发的证书进行信用背书,只要是权威机构生成的证书,我们就认为是合法的。所以权威机构会对申请者的信息进行审核,不同等级的权威机构对审核的要求也不一样,于是证书也分为免费的、便宜的和贵的。

三、浏览器如何验证证书的合法性?

浏览器发起HTTPS请求时,服务器会返回网站的SSL证书,浏览器需要对证书做以下验证:

  1. 验证域名、有效期等信息是否正确。证书上都有包含这些信息,比较容易完成验证;
  2. 判断证书来源是否合法。每份签发证书都可以根据验证链查找到对应的根证书,操作系统、浏览器会在本地存储权威机构的根证书,利用本地根证书可以对对应机构签发证书完成来源验证;
  3. 判断证书是否被篡改。需要与CA服务器进行校验;
  4. 判断证书是否已吊销。通过CRL(Certificate Revocation List 证书注销列表)和 OCSP(Online Certificate Status Protocol 在线证书状态协议)实现,其中 OCSP 可用于第3步中以减少与CA服务器的交互,提高验证效率。

以上任意一步都满足的情况下浏览器才认为证书是合法的。

https 可以抓包吗

HTTPS 的数据是加密的,常规下抓包工具代理请求后抓到的包内容是加密状态,无法直接查看。

但是,我们可以通过抓包工具来抓包。它的原理其实是模拟一个中间人。

通常 HTTPS 抓包工具的使用方法是会生成一个证书,用户需要手动把证书安装到客户端中,然后终端发起的所有请求通过该证书完成与抓包工具的交互,然后抓包工具再转发请求到服务器,最后把服务器返回的结果在控制台输出后再返回给终端,从而完成整个请求的闭环。

关于 httpps 抓包的原理可以看这一篇文章。

Android平台HTTPS抓包解决方案及问题分析

有人可能会问了,既然 HTTPS 不能防抓包,那 HTTPS 有什么意义?

HTTPS 可以防止用户在不知情的情况下通信链路被监听,对于主动授信的抓包操作是不提供防护的,因为这个场景用户是已经对风险知情。要防止被抓包,需要采用应用级的安全防护,例如采用私有的对称加密,同时做好移动端的防反编译加固,防止本地算法被破解。

扩展

如何防止抓包?

对于HTTPS API接口,如何防止抓包呢?既然问题出在证书信任问题上,那么解决方法就是在我们的APP中预置证书。在TLS/SSL握手时,用预置在本地的证书中的公钥校验服务器的数字签名,只有签名通过才能成功握手。由于数字签名是使用私钥生成的,而私钥只掌握在我们手上,中间人无法伪造一个有效的签名,因此攻击失败,无法抓包。

同时,为了防止预置证书被替换,在证书存储上,可以将证书进行加密后进行「嵌入存储」,如嵌入在图片中或一段语音中。这涉及到信息隐写的领域,这个话题我们有空了详细说。

关于 Android 中Https 请求如何防止中间人攻击和Charles抓包,可以看一下这一篇文章。

Android中Https请求如何防止中间人攻击和Charles抓包原理

预置证书/公钥更新问题

这样做虽然解决了抓包问题,但是也带来了另外一个问题:我们购买的证书都是有有效期的,到期前需要对证书进行更新。主要有两种方式:

提供预置证书更新接口。在当前证书快过期时,APP请求获取新的预置证书,这过渡时期,两个证书同时有效,直到安全完成证书切换。这种方式有一定的维护成本,且不易测试。
在APP中只预埋公钥,这样只要私钥不变,即使证书更新也不用更新该公钥。但是,这样不太符合周期性更新私钥的安全审计需求。一个折中的方法是,一次性预置多个公钥,只要任意一个公钥验证通过即可。考虑到我们的证书一般购买周期是3-5年,那么3个公钥,可以使用9-15年,同时,我们在此期间还可以发布新版本废弃老公钥,添加新公钥,这样可以使公钥一直更新下去。


小结

开头说到的几个问题,你能对答如流了吗

  1. 什么是 https,为什么需要 https
  2. https 的连接过程
  3. https 的加密方式是怎样的,对称加密和非对称加密,为什么要这样设计?内容传输为什么要使用对称机密
  4. https 是绝对安全的吗
  5. https 可以抓包吗

image

@[toc]

我的 CSDN 博客:https://blog.csdn.net/gdutxiaoxu

我的掘金:https://juejin.im/user/58aa8508570c35006bbd9e03

github: https://github.com/gdutxiaoxu/

微信公众号:徐公码字(stormjun94)

知乎:https://www.zhihu.com/people/xujun94

往期文章

Android 面试必备 - http 与 https 协议

Android 面试必备 - 计算机网络基本知识(TCP,UDP,Http,https)

Android 面试必备 - 线程

Android 面试必备 - JVM 及 类加载机制

Android 面试必备 - 系统、App、Activity 启动过程

Android_interview github 地址

有兴趣的话可以关注我的公众号 徐公码字(stormjun94),第一时间会在上面更新

image

面试常见

一道经典的面试题

还记得这道经典的面试题目吗?从 URL 在浏览器被被输入到页面展现的过程中发生了什么?

总体来说分为以下几个过程:

  • DNS 解析:将域名解析成 IP 地址
  • TCP 连接:TCP 三次握手
  • 发送 HTTP 请求
  • 服务器处理请求并返回 HTTP 报文
  • 浏览器解析渲染页面
  • 断开连接:TCP 四次挥手

完整的可以看以下下面的图片

image

http 必备基础知识

HTTP 是一种 超文本传输协议(Hypertext Transfer Protocol),HTTP 是一个在计算机世界里专门在两点之间传输文字、图片、音频、视频等超文本数据的约定和规范

image

HTTP 主要内容分为三部分,超文本(Hypertext)、传输(Transfer)、协议(Protocol)。

  • 超文本就是不单单只是本文,它还可以传输图片、音频、视频,甚至点击文字或图片能够进行超链接的跳转。
  • 上面这些概念可以统称为数据,传输就是数据需要经过一系列的物理介质从一个端系统传送到另外一个端系统的过程。通常我们把传输数据包的一方称为请求方,把接到二进制数据包的一方称为应答方。
  • 而协议指的就是是网络中(包括互联网)传递、管理信息的一些规范。如同人与人之间相互交流是需要遵循一定的规矩一样,计算机之间的相互通信需要共同遵守一定的规则,这些规则就称为协议,只不过是网络协议。

什么是无状态协议,HTTP 是无状态协议吗,怎么解决

无状态协议(Stateless Protocol) 就是指浏览器对于事务的处理没有记忆能力。举个例子来说就是比如客户请求获得网页之后关闭浏览器,然后再次启动浏览器,登录该网站,但是服务器并不知道客户关闭了一次浏览器。

HTTP 就是一种无状态的协议,他对用户的操作没有记忆能力。可能大多数用户不相信,他可能觉得每次输入用户名和密码登陆一个网站后,下次登陆就不再重新输入用户名和密码了。这其实不是 HTTP 做的事情,起作用的是一个叫做 小甜饼(Cookie) 的机制。它能够让浏览器具有记忆能力。
如果你的浏览器允许 cookie 的话,查看方式 chrome://settings/content/cookies

几种方法

HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法

HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT

  • GET: 通常用于请求服务器发送某些资源
  • HEAD: 请求资源的头部信息, 并且这些头部与 HTTP GET 方法请求时返回的一致. 该请求方法的一个使用场景是在下载一个大文件前先获取其大小再决定是否要下载, 以此可以节约带宽资源
  • OPTIONS: 用于获取目的资源所支持的通信选项
  • POST: 发送数据给服务器,是非幂等
  • PUT: 跟POST方法很像,也是想服务器提交数据。但是,它们之间有不同。PUT指定了资源在服务器上的位置,而POST不需要置顶资源在服务器的位置,是幂等
  • DELETE: 用于删除指定的资源
  • PATCH: 用于对资源进行部分修改
  • CONNECT: HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器
  • TRACE: 回显服务器收到的请求,主要用于测试或诊断

http get 和 post 区别

Post一般用于更新或者添加资源信息 Get一般用于查询操作,而且应该是安全和幂等的
Post更加安全 Get会把请求的信息放到URL的后面
Post传输量一般无大小限制 Get不能大于2KB
Post执行效率低 Get执行效率略高

http put 和 post 区别

举一个简单的例子

POST:用于提交请求,可以更新或者创建资源,是非幂等的

举个例子,在我们的支付系统中,一个api的功能是创建收款金额二维码,它和金额相关,每个用户可以有多个二维码,如果连续调用则会创建新的二维码,这个时候就用POST

PUT: 用于向指定的URI传送更新资源,是幂等的

还是那个例子,用户的账户二维码只和用户关联,而且是一一对应的关系,此时这个api就可以用PUT,因为每次调用它,都将刷新用户账户二维码

如果从 RESTful API 的角度来理解,PUT 方法是这么工作的:

把一个对象 V 绑定到地址 K 上;今后请求地址 K 时,就会返回对象 V。

如果地址 K 之前曾绑定过另一个对象,比如 V0,那么 V0 会被 V 替换。

举一个简单的例子,假设我的博客后台支持 RESTful API,我可以通过下面的请求发布这篇文章:

1
2
3
4
5
PUT https://gdutxiao.github.io/2018/04/16/http-put-vs-post HTTP/1.1

{
/* 文章内容正文 */
}

可以看出,使用 PUT 方法时,客户端需要在 HTTP 请求中明确指定地址 K。

正如 Java 的例子一样,PUT 方法应当支持幂等性。如果是同一个对象 V,PUT 多次与 PUT 一次返回的结果应该是相同的。客户端可以利用 PUT 的幂等性安全地重试请求,保证客户端的请求至少被服务端处理一次。

如果把上面发布文章的例子用 HTTP POST 方法重写,它可能会是下面这样:

1
2
3
4
5
POST https://gdutxiao.github.io/post-article HTTP/1.1

{
/* 文章内容正文 */
}

也就是说,地址 K 不是由客户端指定的,而是由服务端生成的。比如,服务端可能会根据日期和文章标题,为本文分配一个地址。

另外,与 PUT 方法不同,POST 方法是不支持幂等性的。同一个请求被处理两次,应当生成两份对象。换句话说,客户端应该只发送一次 POST 请求,而客户端的请求至多会被服务端处理一次。

现在问题来了,如果真的遇到了网络故障,客户端应该如何重试 POST 请求呢?解决方法其实很简单,我们可以在 POST 请求中隐藏一个唯一的 token,服务端在处理请求后把 token 存入数据库,如果这个 token 之前遇到过,服务端就知道这是重复的 POST 请求,可以不再处理了。

http 版本

1.0 与 1.1

  • http1.0一次只能处理一个请求,不能同时收发数据
  • http1.1可以处理多个请求,能同时收发数据
  • http1.1增加可更多字段,如cache-control,keep-alive.

2.0

  • http 2.0采用二进制的格式传送数据,不再使用文本格式传送数据
  • http2.0对消息头采用hpack压缩算法,http1.x的版本消息头带有大量的冗余消息
  • http2.0 采用多路复用,即用一个tcp连接处理所有的请求,真正意义上做到了并发请求,流还支持优先级和流量控制(HTTP/1.x 虽然通过 pipeline也能并发请求,但是多个请求之间的响应会被阻塞的,所以 pipeline 至今也没有被普及应用,而 HTTP/2 做到了真正的并发请求。同时,流还支持优先级和流量控制。)
  • http2.0支持server push,服务端可以主动把css,jsp文件主动推送到客户端,不需要客户端解析HTML,再发送请求,当客户端需要的时候,它已经在客户端了。

缺点

虽然 HTTP/2 解决了很多之前旧版本的问题,但是它还是存在一个巨大的问题, 主要是底层支撑的 TCP 协议造成的
。HTTP/2的缺点主要有以下几点:

  • TCP 以及 TCP+TLS建立连接的延时

HTTP/2使用TCP协议来传输的,而如果使用HTTPS的话,还需要使用TLS协议进行安全传输,而使用TLS也需要一个握手过程,
这样就需要有两个握手延迟过程

①在建立TCP连接的时候,需要和服务器进行三次握手来确认连接成功,也就是说需要在消耗完1.5个RTT之后才能进行数据传输。

②进行TLS连接,TLS有两个版本——TLS1.2和TLS1.3,每个版本建立连接所花的时间不同,大致是需要1~2个RTT。

总之,在传输数据之前,我们需要花掉 3~4 个 RTT。

  • TCP的队头阻塞并没有彻底解决

上文我们提到在HTTP/2中,多个请求是跑在一个TCP管道中的。但当出现了丢包时,HTTP/2 的表现反倒不如 HTTP/1
了。因为TCP为了保证可靠传输,有个特别的“丢包重传”机制,丢失的包必须要等待重新传输确认,HTTP/2出现丢包时,整个 TCP
都要开始等待重传,那么就会阻塞该TCP连接中的所有请求(如下图)。而对于 HTTP/1.1 来说,可以开启多个 TCP
连接,出现这种情况反到只会影响其中一个连接,剩余的 TCP 连接还可以正常传输数据。

image

Http 3.0

Google 在推SPDY的时候就已经意识到了这些问题,于是就另起炉灶搞了一个基于 UDP 协议的“QUIC”协议,让HTTP跑在QUIC上而不是TCP上。
而这个“HTTP over QUIC”就是HTTP协议的下一个大版本,HTTP/3。它在HTTP/2的基础上又实现了质的飞跃,真正“完美”地解决了“队头阻塞”问题。

image

QUIC 虽然基于 UDP,但是在原本的基础上新增了很多功能,接下来我们重点介绍几个QUIC新功能。不过HTTP/3目前还处于草案阶段,正式发布前可能会有变动,所以本文尽量不涉及那些不稳定的细节。

QUIC新功能

上面我们提到QUIC基于UDP,而UDP是“无连接”的,根本就不需要“握手”和“挥手”,所以就比TCP来得快。此外QUIC也实现了可靠传输,保证数据一定能够抵达目的地。它还引入了类似HTTP/2的“流”和“多路复用”,单个“流”是有序的,可能会因为丢包而阻塞,但其他“流”不会受到影响。具体来说QUIC协议有以下特点:

  • 实现了类似TCP的流量控制、传输可靠性的功能。

虽然UDP不提供可靠性的传输,但QUIC在UDP的基础之上增加了一层来保证数据可靠性传输。它提供了数据包重传、拥塞控制以及其他一些TCP中存在的特性。

  • 实现了快速握手功能。

由于QUIC是基于UDP的,所以QUIC可以实现使用0-RTT或者1-RTT来建立连接,这意味着QUIC可以用最快的速度来发送和接收数据,这样可以大大提升首次打开页面的速度。
0RTT 建连可以说是 QUIC 相比 HTTP2 最大的性能优势

  • 集成了TLS加密功能。

目前QUIC使用的是TLS1.3,相较于早期版本TLS1.3有更多的优点,其中最重要的一点是减少了握手所花费的RTT个数。

  • 多路复用,彻底解决TCP中队头阻塞的问题

和TCP不同,QUIC实现了在同一物理连接上可以有多个独立的逻辑数据流(如下图)。实现了数据流的单独传输,就解决了TCP中队头阻塞的问题。

image

关于 http 3.0 的,如果想了解更多,可以查看这一篇文章。解密HTTP/2与HTTP/3 的新特性

总结

  • HTTP/1.1有两个主要的缺点:安全不足和性能不高。
  • HTTP/2完全兼容HTTP/1,是“更安全的HTTP、更快的HTTPS”,头部压缩、多路复用等技术可以充分利用带宽,降低延迟,从而大幅度提高上网体验;
  • QUIC 基于 UDP 实现,是 HTTP/3 中的底层支撑协议,该协议基于 UDP,又取了 TCP 中的精华,实现了即快又可靠的协议

http 状态码

Http 状态码 含义
200 请求成功
206 支持断点下载(range = byte = 0 -1024)
301 永远移动
302 临时移动
303 See Other 查看其它地址。与301类似。使用GET和POST请求查看
304 无更新
400 Bad request,服务器无法识别
403 禁止访问
404 not found
405 Method Not Allowed 客户端请求中的方法被禁止
500 Internal Server Error 服务器内部错误,无法完成请求

关于更详细的可以查看

http 状态码

题外话

下一篇预告,将会推出 面试官系列 - https 真的安全吗,可以抓包吗,如何防止抓包。

有兴趣的话可以关注我的公众号 徐公码字(stormjun94)

image

目前从事于 Android 开发,除了分享 Android开发相关知识,还有职场心得,面试经验,学习心得,人生感悟等等。希望通过该公众号,让你看到程序猿不一样的一面,我们不只会敲代码,我们还会。。。。。。,期待你的参与

说在前面

本次推出 Android Architecture Components 系列文章,目前写好了四篇,主要是关于 lifecycle,livedata 的使用和源码分析,其余的 Navigation, Paging library,Room,WorkMannager 等春节结束之后会更新,欢迎关注我的公众号,有更新的话会第一时间会在公众号上面通知。

Android lifecycle 使用详解

Android LiveData 使用详解

Android lifecyle 源码解剖

Android livedata 源码解剖

github sample 地址: ArchiteComponentsSample

Android 技术人,一位不羁的码农。

Android 技术人


前言

在前面三篇博客中,我们已经介绍了 lifecycle 的使用及原理,livedata ,ViewModel 的常用用法,今天,让我们一起来学习 livedata 的原理。

我们先来回顾一下 LiveData 的特点:

LiveData 是一个可以被观察的数据持有类,它可以感知 Activity、Fragment或Service 等组件的生命周期。

  1. 它可以做到在组件处于激活状态的时候才会回调相应的方法,从而刷新相应的 UI。
  2. 不用担心发生内存泄漏
  3. 当 config 导致 activity 重新创建的时候,不需要手动取处理数据的储存和恢复。内部已经帮我们封装好了。
  4. 当 Actiivty 不是处于激活状态的时候,如果你想 livedata setValue 之后立即回调 obsever 的 onChange 方法,而不是等到 Activity 处于激活状态的时候才回调 obsever 的 onChange 方法,你可以使用 observeForever 方法,但是你必须在 onDestroy 的时候 removeObserver

下面,让我们一步步解剖它


原理分析

我们知道 livedata 的使用很简单,它是采用观察者模式实现的

  1. 添加观察者
  2. 在数据改变的时候设置 value,这样会回调 Observer 的 onChanged 方法
1
2
3
4
5
6
7
8
MutableLiveData<String> nameEvent = mTestViewModel.getNameEvent();
nameEvent.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
Log.i(TAG, "onChanged: s = " + s);
mTvName.setText(s);
}
});

observe 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
// 判断是否已经销毁
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
// observer 已经添加过了,并且缓存的 observer 跟 owner 的 observer 不一致,状态异常,抛出异常
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
// 已经添加过 Observer 了,返回回去
if (existing != null) {
return;
}

// 添加 observer
owner.getLifecycle().addObserver(wrapper);
}

首先,我们先来看一下它的 observe 方法,首先通过 owner.getLifecycle().getCurrentState() 获取状态,判断是否已经被销毁,如果已经被销毁,直接返回。接着用 LifecycleBoundObserver 包装起来。然后从缓存的 mObservers 中读取 observer,如果有,证明已经添加过了。

observe 方法,小结起来就是

  1. 判断是否已经销毁,如果销毁,直接移除
  2. 用 LifecycleBoundObserver 包装传递进来的 observer
  3. 是否已经添加过,添加过,直接返回
  4. 将包装后的 LifecycleBoundObserver 添加进去

因此,当 owner 你(Activity 或者 fragment) 生命周期变化的时候,会回调 LifecycleBoundObserver 的 onStateChanged 方法,onStateChanged 方法又会回调 observer 的 onChange 方法

LifecycleBoundObserver

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
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
@NonNull final LifecycleOwner mOwner;

LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<T> observer) {
super(observer);
mOwner = owner;
}

@Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}

@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());
}

@Override
boolean isAttachedTo(LifecycleOwner owner) {
return mOwner == owner;
}

@Override
void detachObserver() {
mOwner.getLifecycle().removeObserver(this);
}


}

我们来看一下 LifecycleBoundObserver,继承 ObserverWrapper,实现了 GenericLifecycleObserver 接口。而 GenericLifecycleObserver 接口又实现了 LifecycleObserver 接口。 它包装了我们外部的 observer,有点类似于代理模式。

GenericLifecycleObserver#onStateChanged

Activity 回调周期变化的时候,会回调 onStateChanged ,会先判断 mOwner.getLifecycle().getCurrentState() 是否已经 destroy 了,如果。已经 destroy,直接移除观察者。这也就是为什么我们不需要手动 remove observer 的原因

如果不是销毁状态,会调用 activeStateChanged 方法 ,携带的参数为 shouldBeActive() 返回的值。
而当 lifecycle 的 state 为 started 或者 resume 的时候,shouldBeActive 方法的返回值为 true,即表示激活。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
// immediately set active state, so we'd never dispatch anything to inactive
// owner
mActive = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
LiveData.this.mActiveCount += mActive ? 1 : -1;
if (wasInactive && mActive) {
onActive();
}
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
if (mActive) {
dispatchingValue(this);
}

}

activeStateChanged 方法中,,当 newActive 为 true,并且不等于上一次的值,会增加 LiveData 的 mActiveCount 计数。接着可以看到,onActive 会在 mActiveCount 为 1 时触发,onInactive 方法则只会在 mActiveCount 为 0 时触发。即回调 onActive 方法的时候活跃的 observer 恰好为 1,回调 onInactive 方法的时候,没有一个 Observer 处于激活状态。

当 mActive 为 true 时,会促发 dispatchingValue 方法。

dispatchingValue

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
private void dispatchingValue(@Nullable ObserverWrapper initiator) {
// 如果正在处理,直接返回
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;

do {
mDispatchInvalidated = false;
// initiator 不为 null,调用 considerNotify 方法
if (initiator != null) {
considerNotify(initiator);
initiator = null;
} else { // 为 null 的时候,遍历所有的 obsever,进行分发
for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
// 分发完成,设置为 false
mDispatchingValue = false;
}

其中 mDispatchingValue, mDispatchInvalidated 只在 dispatchingValue 方法中使用,显然这两个变量是为了防止重复分发相同的内容。当 initiator 不为 null,只处理当前 observer,为 null 的时候,遍历所有的 obsever,进行分发

considerNotify 方法

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
private void considerNotify(ObserverWrapper observer) {
// 如果状态不是在活跃中,直接返回
if (!observer.mActive) {
return;
}
// Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
//
// we still first check observer.active to keep it as the entrance for events. So even if
// the observer moved to an active state, if we've not received that event, we better not
// notify for a more predictable notification order.
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}


if (observer.mLastVersion >= mVersion) {
// 数据已经是最新,返回
return;
}
// 将上一次的版本号置为最新版本号
observer.mLastVersion = mVersion;
//noinspection unchecked
// 调用外部的 mObserver 的 onChange 方法
observer.mObserver.onChanged((T) mData);
}
  1. 如果状态不是在活跃中,直接返回,这也就是为什么当我们的 Activity 处于 onPause, onStop, onDestroy 的时候,不会回调 observer 的 onChange 方法的原因。
  2. 判断数据是否是最新,如果是最新,返回,不处理
  3. 数据不是最新,回调 mObserver.onChanged 方法。并将 mData 传递过去

setValue

1
2
3
4
5
6
7
8
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}

setValue 方法中,首先,断言是主线程,接着 mVersion + 1; 并将 value 赋值给 mData,接着调用 dispatchingValue 方法。dispatchingValue 传递 null,代表处理所有 的 observer。

这个时候如果我们依附的 activity 处于 onPause 或者 onStop 的时候,虽然在 dispatchingValue 方法中直接返回,不会调用 observer 的 onChange 方法。但是当所依附的 activity 重新回到前台的时候,会促发 LifecycleBoundObserver onStateChange 方法,onStateChange 又会调用 dispatchingValue 方法,在该方法中,因为 mLastVersion < mVersion。所以会回调 obsever 的 onChange 方法,这也就是 LiveData 设计得比较巧妙的一个地方

同理,当 activity 处于后台的时候,您多次调用 livedata 的 setValue 方法,最终只会回调 livedata observer 的 onChange 方法一次。

postValue

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
protected void postValue(T value) {
boolean postTask;
// 锁住
synchronized (mDataLock) {
// 当前没有人在处理 post 任务
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
AppToolkitTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
private final Runnable mPostValueRunnable = new Runnable() {
@Override
public void run() {
Object newValue;
synchronized (mDataLock) {
newValue = mPendingData;
// 处理完毕之后将 mPendingData 置为 NOT_SET
mPendingData = NOT_SET;
}
//noinspection unchecked
setValue((T) newValue);
}
};
  1. 首先,采用同步机制,通过 postTask = mPendingData == NOT_SET 有没有人在处理任务。 true,没人在处理任务, false ,有人在处理任务,有人在处理任务的话,直接返回
  2. 调用 AppToolkitTaskExecutor.getInstance().postToMainThread 到主线程执行 mPostValueRunnable 任务。
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
@MainThread
public void observeForever(@NonNull Observer<T> observer) {
AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && existing instanceof LiveData.LifecycleBoundObserver) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
wrapper.activeStateChanged(true);
}

private class AlwaysActiveObserver extends ObserverWrapper {

AlwaysActiveObserver(Observer<T> observer) {
super(observer);
}

@Override
boolean shouldBeActive() {
return true;
}
}

因为 AlwaysActiveObserver 没有实现 GenericLifecycleObserver 方法接口,所以在 Activity o生命周期变化的时候,不会回调 onStateChange 方法。从而也不会主动 remove 掉 observer。因为我们的 obsever 被 remove 掉是依赖于 Activity 生命周期变化的时候,回调 GenericLifecycleObserver 的 onStateChange 方法。


总结

  1. liveData 当我们 addObserver 的时候,会用 LifecycleBoundObserver 包装 observer,而 LifecycleBoundObserver 可以感应生命周期,当 activity 生命周期变化的时候,如果不是处于激活状态,判断是否需要 remove 生命周期,需要 remove,不需要,直接返回
  2. 当处于激活状态的时候,会判断是不是 mVersion最新版本,不是的话需要将上一次缓存的数据通知相应的 observer,并将 mLastVsersion 置为最新
  3. 当我们调用 setValue 的时候,mVersion +1,如果处于激活状态,直接处理,如果不是处理激活状态,返回,等到下次处于激活状态的时候,在进行相应的处理
  4. 如果你想 livedata setValue 之后立即回调数据,而不是等到生命周期变化的时候才回调数据,你可以使用 observeForever 方法,但是你必须在 onDestroy 的时候 removeObserver。因为 AlwaysActiveObserver 没有实现 GenericLifecycleObserver 接口,不能感应生命周期。

题外话

Android Architecture Components 已经写了四篇文章了,其余的 Navigation, Paging library,Room,WorkMannager 等春节结束之后再更新了,欢迎关注我的公众号,有更新的话会第一时间在公众好上面更新。

说在前面

本次推出 Android Architecture Components 系列文章,目前写好了四篇,主要是关于 lifecycle,livedata 的使用和源码分析,其余的 Navigation, Paging library,Room,WorkMannager 等春节结束之后会更新,欢迎关注我的公众号,有更新的话会第一时间会在公众号上面通知。

目录大概如下

1 LiveData 基本使用
2 自定义 Livedata
3 Livedata 共享数据
4 Livedata 小结

Android lifecycle 使用详解

Android LiveData 使用详解

Android lifecyle 源码解剖

Android livedata 源码解剖

github sample 地址: ArchiteComponentsSample

程序员徐公,一位不羁的码农。


前言

在上一篇博客中,我们讲解了 lifecycle 的使用及优点。这篇博客让我们一起来了解一下 LiveData 是怎样使用的?


为什么要引进 LiveData

LiveData 是一个可以被观察的数据持有类,它可以感知 Activity、Fragment或Service 等组件的生命周期。简单来说,他主要有一下优点。

  1. 它可以做到在组件处于激活状态的时候才会回调相应的方法,从而刷新相应的 UI
  2. 不用担心发生内存泄漏
  3. 当 config 导致 activity 重新创建的时候,不需要手动取处理数据的储存和恢复。它已经帮我们封装好了
  4. 当 Actiivty 不是处于激活状态的时候,如果你想 livedata setValue 之后立即回调 obsever 的 onChange 方法,而不是等到 Activity 处于激活状态的时候才回调 obsever 的 onChange 方法,你可以使用 observeForever 方法,但是你必须在 onDestroy 的时候 removeObserver。

回想一下,在你的项目中,是不是经常会碰到这样的问题,当网络请求结果回来的时候,你经常需要判断 Activity 或者 Fragment 是否已经 Destroy, 如果不是 destroy,才更新 UI。

而当你如果使用 Livedata 的话,因为它是在 Activity 处于 onStart 或者 onResume 的状态时,他才会进行相应的回调,因而可以很好得处理这个问题,不必写一大堆的 activity.isDestroyed()。接下来,让我们一起来看一下 LiveData 的使用


LiveData 使用

基本使用

  1. 引入相关的依赖包
1
2
3
4
5
6
// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:1.1.0"
// alternatively, just ViewModel
implementation "android.arch.lifecycle:viewmodel:1.1.0"
// alternatively, just LiveData
implementation "android.arch.lifecycle:livedata:1.1.0"
  1. 在代码中使用

LiveData 是一个抽象类,它的实现子类有 MutableLiveDataMediatorLiveData。在实际使用中,用得比较多的是 MutableLiveData。他常常结合 ViewModel 一起使用。下面,让我们一起来看一下怎样使用它?

首先,我们先写一个类继承我们的 ViewModel,里面持有 mNameEvent。

1
2
3
4
5
6
7
8
9
public class TestViewModel extends ViewModel {

private MutableLiveData<String> mNameEvent = new MutableLiveData<>();

public MutableLiveData<String> getNameEvent() {
return mNameEvent;
}

}

接着,我们在 Activity 中创建 ViewModel,并监听 ViewModel 里面 mNameEvent 数据的变化,当数据改变的时候,我们打印相应的 log,并设置给 textView,显示在界面上。这样我们就完成了对 mNameEvent 数据源的观察。

1
2
3
4
5
6
7
8
9
mTestViewModel = ViewModelProviders.of(this).get(TestViewModel.class);
MutableLiveData<String> nameEvent = mTestViewModel.getNameEvent();
nameEvent.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
Log.i(TAG, "onChanged: s = " + s);
mTvName.setText(s);
}
});

最后当我们数据源改变的时候,我们需要调用 livedata 的 setValue 或者 postvalue 方法。他们之间的区别是, 调用 setValue 方法,Observer 的 onChanged 方法会在调用 serValue 方法的线程回调。而
postvalue 方法,Observer 的 onChanged 方法将会在主线程回调。

1
mTestViewModel.getNameEvent().setValue(name);

可能部分同学有这样的疑问了,我们的 ViewModel 是通过 ViewModelProviders.of(this).get(TestViewModel.class); 方法创建出来的,如果我们要携带参数,怎么办?

其实,官方也替我们考虑好了,同样是调用 ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory) 方法,只不过,需要多传递一个 factory 参数。

Factory 是一个接口,它只有一个 create 方法。

1
2
3
4
5
6
7
8
9
10
11
12
public interface Factory {
/**
* Creates a new instance of the given {@code Class}.
* <p>
*
* @param modelClass a {@code Class} whose instance is requested
* @param <T> The type parameter for the ViewModel.
* @return a newly created ViewModel
*/
@NonNull
<T extends ViewModel> T create(@NonNull Class<T> modelClass);
}

在实际当中,我们的做法是:实现 Factory 接口,重写 create 方法,在create 方法里面调用相应的构造函数,返回相应的实例。

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 class TestViewModel extends ViewModel {

private final String mKey;
private MutableLiveData<String> mNameEvent = new MutableLiveData<>();

public MutableLiveData<String> getNameEvent() {
return mNameEvent;
}

public TestViewModel(String key) {
mKey = key;
}

public static class Factory implements ViewModelProvider.Factory {
private String mKey;

public Factory(String key) {
mKey = key;
}

@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
return (T) new TestViewModel(mKey);
}
}

public String getKey() {
return mKey;
}
}

ViewModelProviders.of(this, new TestViewModel.Factory(mkey)).get(TestViewModel.class)


自定义 Livedata

Livedata 主要有几个方法

  1. observe
  2. onActive
  3. onInactive
  4. observeForever

void observe (LifecycleOwner owner, Observer observer)

Adds the given observer to the observers list within the lifespan of the given owner. The events are dispatched on the main thread. If LiveData already has data set, it will be delivered to the observer.

void onActive ()

Called when the number of active observers change to 1 from 0.
This callback can be used to know that this LiveData is being used thus should be kept up to date.

当回调该方法的时候,表示该 liveData 正在背使用,因此应该保持最新

void onInactive ()

Called when the number of active observers change from 1 to 0.
This does not mean that there are no observers left, there may still be observers but their lifecycle states aren’t STARTED or RESUMED (like an Activity in the back stack).
You can check if there are observers via hasObservers().

当该方法回调时,表示他所有的 obervers 没有一个状态处理 STARTED 或者 RESUMED,注意,这不代表没有 observers。

Void observeForever

跟 observe 方法不太一样的是,它在 Activity 处于 onPause ,onStop, onDestroy 的时候,都可以回调 obsever 的 onChange 方法,但是有一点需要注意的是,我们必须手动 remove obsever,否则会发生内存泄漏。

这里我们以观察网络状态变化为例子讲解

  1. 首先我们自定义一个 Class NetworkLiveData,继承 LiveData,重写它的 onActive 方法和 onInactive 方法
  2. 在 onActive 方法中,我们注册监听网络变化的广播,即ConnectivityManager.CONNECTIVITY_ACTION。在 onInactive 方法的时候,我们注销广播。
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
public class NetworkLiveData extends LiveData<NetworkInfo> {

private final Context mContext;
static NetworkLiveData mNetworkLiveData;
private NetworkReceiver mNetworkReceiver;
private final IntentFilter mIntentFilter;

private static final String TAG = "NetworkLiveData";

public NetworkLiveData(Context context) {
mContext = context.getApplicationContext();
mNetworkReceiver = new NetworkReceiver();
mIntentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
}

public static NetworkLiveData getInstance(Context context) {
if (mNetworkLiveData == null) {
mNetworkLiveData = new NetworkLiveData(context);
}
return mNetworkLiveData;
}

@Override
protected void onActive() {
super.onActive();
Log.d(TAG, "onActive:");
mContext.registerReceiver(mNetworkReceiver, mIntentFilter);
}

@Override
protected void onInactive() {
super.onInactive();
Log.d(TAG, "onInactive: ");
mContext.unregisterReceiver(mNetworkReceiver);
}

private static class NetworkReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager manager = (ConnectivityManager) context
.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = manager.getActiveNetworkInfo();
getInstance(context).setValue(activeNetwork);

}
}
}

这样,当我们想监听网络变化的时候,我们只需要调用相应的 observe 方法即可,方便又快捷。

1
2
3
4
5
6
7
NetworkLiveData.getInstance(this).observe(this, new Observer<NetworkInfo>() {
@Override
public void onChanged(@Nullable NetworkInfo networkInfo) {
Log.d(TAG, "onChanged: networkInfo=" +networkInfo);
}
});


https://www.jianshu.com/p/4b7945475a6f

共享数据

Fragment Activity 之间共享数据

我们回过头来再来看一下 ViewModelProvider 的 of 方法,他主要有四个方法,分别是

  1. ViewModelProvider of(@NonNull Fragment fragment)
  2. ViewModelProvider of(@NonNull FragmentActivity activity)
  3. ViewModelProvider of(@NonNull Fragment fragment, @Nullable Factory factory)
  4. ViewModelProvider of(@NonNull FragmentActivity activity, @Nullable Factory factory)

1,2 方法之间的主要区别是传入 Fragment 或者 FragmentActivity。而我们知道,通过 ViewModel of 方法创建的 ViewModel 实例, 对于同一个 fragment 或者 fragmentActivity 实例,ViewModel 实例是相同的,因而我们可以利用该特点,在 Fragment 中创建 ViewModel 的时候,传入的是 Fragment 所依附的 Activity。因而他们的 ViewModel 实例是相同的,从而可以做到共享数据。

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

// LiveDataSampleActivity(TestFragment 依赖的 Activity)
mTestViewModel = ViewModelProviders.of(this, new TestViewModel.Factory(mkey)).get(TestViewModel.class);
MutableLiveData<String> nameEvent = mTestViewModel.getNameEvent();
nameEvent.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
Log.i(TAG, "onChanged: s = " + s);
mTvName.setText(s);
}
});


// TestFragment 中
mViewModel = ViewModelProviders.of(mActivity).get(TestViewModel.class);
mViewModel.getNameEvent().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
Log.d(TAG, "onChanged: s =" + s + " mViewModel.getKey() =" + mViewModel.getKey());
mTvName.setText(s);
boolean result = mViewModel == ((LiveDataSampleActivity) mListener).mTestViewModel;
Log.d(TAG, "onChanged: s result =" + result);
}
});

这样,LiveDataSampleActivity 和 TestFragment 中的 ViewModel 是同一个实例。即 Activity 和 Fragment 共享数据。

全局共享数据

说到全局共享数据,我们想一下我们的应用全景,比如说我的账户数据,这个对于整个 App 来说,肯定是全局共享的。有时候,当我们的数据变化的时候,我们需要通知我们相应的界面,刷新 UI。如果用传统的方式来实现,那么我们一般才采取观察者的方式来实现,这样,当我们需要观察数据的时候,我们需要添加 observer,在界面销毁的时候,我们需要移除 observer。

但是,如果我们用 LiveData 来实现的话,它内部逻辑都帮我们封装好了,我们只需要保证 AccountLiveData 是单例的就ok,在需要观察的地方调用 observer 方法即可。也不需要手动移除 observer,不会发生内存泄漏,方便快捷。

这里 AccountLiveData 的实现就不贴出来了,可以参考上面的 NetworkLiveData 实现


小结

这里说一点关于 LiveData 与 ViewModel 的应用场景,我尽量说得通俗一点,不要说得那么官方,这样对新手很难理解。

觉得不错的,请点个赞,让我们看到你们的欢呼声。你们的支持就是我写作的最大动力。

  1. LiveData 内部已经实现了观察者模式,如果你的数据要同时通知几个界面,可以采取这种方式
  2. 我们知道 LiveData 数据变化的时候,会回调 Observer 的 onChange 方法,但是回调的前提是 lifecycleOwner(即所依附的 Activity 或者 Fragment) 处于 started 或者 resumed 状态,它才会回调,否则,必须等到 lifecycleOwner 切换到前台的时候,才回调。
  3. 因此,这对性能方面确实是一个不小的提升。但是,对于你想做一些类似与在后台工作的(黑科技), liveData 就不太适合了,你可以使用 observeForever 方法,或者自己实现观察者模式去吧。

Lifecycle,LiveData, ViewModel 的基本使用到此已经讲解完毕,想了解他们的实现原理的话可以阅读这两篇文章。

Android lifecyle 源码解剖

Android livedata 源码解剖

github sample 地址: ArchiteComponentsSample

推荐阅读

Android 启动优化(一) - 有向无环图

Android 启动优化(二) - 拓扑排序的原理以及解题思路

Android 启动优化(三)- AnchorTask 开源了

Android 启动优化(四)- AnchorTask 是怎么实现的

Android 启动优化(五)- AnchorTask 1.0.0 版本正式发布了

Android 启动优化(六)- 深入理解布局优化

这几篇文章从 0 到 1,讲解 DAG 有向无环图是怎么实现的,以及在 Android 启动优化的应用。

推荐理由:现在挺多文章一谈到启动优化,动不动就聊拓扑结构,这篇文章从数据结构到算法、到设计都给大家说清楚了,开源项目也有非常强的借鉴意义。

在这里插入图片描述

说在前面

本次推出 Android Architecture Components 系列文章,目前写好了四篇,主要是关于 lifecycle,livedata 的使用和源码分析,其余的 Navigation, Paging library,Room,WorkMannager 等春节结束之后会更新,欢迎关注我的公众号,有更新的话会第一时间会在公众号上面通知。

Android lifecycle 使用详解

Android LiveData 使用详解

Android lifecyle 源码解剖

Android livedata 源码解剖

github sample 地址: ArchiteComponentsSample

徐公码字,一位不羁的码农。


前言

前两篇博客,我们已经讲解了 lifecycle ,liveData, ViewModel 的使用,这一篇博客,让我们一起来看一下 lifecycle 的原理。


从自定义的 lifecycle 说起

首先我们先来复习一下,如果要自定义 lifecycle,我们要这样做。

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 CustomLifecycleActivity extends FragmentActivity implements LifecycleOwner {

private LifecycleRegistry mLifecycleRegistry;

private static final String TAG = "CustomLifecycleActivity";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_custom_lifecycle);
mLifecycleRegistry = new LifecycleRegistry(this);
mLifecycleRegistry.markState(Lifecycle.State.CREATED);
getLifecycle().addObserver(new GenericLifecycleObserver() {
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
Log.d(TAG, "onStateChanged: event = " + event);
}
});

}

@Override
protected void onStart() {
super.onStart();
mLifecycleRegistry.markState(Lifecycle.State.STARTED);
}

@Override
protected void onResume() {
super.onResume();
mLifecycleRegistry.markState(Lifecycle.State.RESUMED);
}

@Override
protected void onDestroy() {
super.onDestroy();
mLifecycleRegistry.markState(Lifecycle.State.DESTROYED);
}

@NonNull
@Override
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}
}

  1. 第一步:先实现 LifecycleOwner 接口,并返回 mLifecycleRegistry
  2. 第二步:在 Activity 生命周期变化的时候,调用 mLifecycleRegistry.markState() 方法标记相应的状态
  3. 如果想添加 observer,调用 addObserver 方法添加观察者,这样会在 activity 生命周期变化的时候,回调 observer 的 onchange 方法。

我们先来看一下 getLifecycle() 方法, getLifecycle() 它返回的是一个 Lifecycle 的实例,sdk 中默认的实现类为 LifecycleRegistry。

接下来,我们一起来看一下它的 observer 方法。

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
public void addObserver(@NonNull LifecycleObserver observer) {
// 判断是否是 DESTROYED,如果是将初始状态置为 DESTROYED,否则为 INITIALIZED
State initialState = mState == DESTROYED ? DESTROYED : INITIALIZED;
// ObserverWithState 包装
ObserverWithState statefulObserver = new ObserverWithState(observer, initialState);
// 将 observer 作为key,在缓存的 mObserverMap 中查找是否存在
ObserverWithState previous = mObserverMap.putIfAbsent(observer, statefulObserver);

// 存在,直接返回回去,证明该 observer 已经添加过了。否则,证明还没有添加过该 observer
if (previous != null) {
return;
}

LifecycleOwner lifecycleOwner = mLifecycleOwner.get();
if (lifecycleOwner == null) {
// it is null we should be destroyed. Fallback quickly
return;
}

// 这里 mAddingObserverCounter 为 0 ,mHandlingEvent 为 false
boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent;
State targetState = calculateTargetState(observer);
mAddingObserverCounter++;
while ((statefulObserver.mState.compareTo(targetState) < 0
&& mObserverMap.contains(observer))) {
pushParentState(statefulObserver.mState);
statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
popParentState();
// mState / subling may have been changed recalculate
targetState = calculateTargetState(observer);
}

if (!isReentrance) {
// we do sync only on the top level.
sync();
}
mAddingObserverCounter--;
}

在 addObserver 方法中,它主要干这几件事情

  1. 首先,先初始化状态, 判断当前 mState 是否是 DESTROYED,如果是将初始状态置为 DESTROYED,否则为 INITIALIZED,接着用 ObserverWithState 包装 observer 和 初始化状态 initialState
  2. 将 observer 作为 key,在缓存的 mObserverMap 中查找是否存在,如果存在,证明该 observer 已经添加过,直接返回回去,不必再进行处理。
  3. addObserver 方法中第 21 行 , isReentrance 一般情况下为 false,什么情况 为 true,暂时未想到,

接下来我们先来看 calculateTargetState 方法。

1
2
3
4
5
6
7
8
9
10
11
12
private State calculateTargetState(LifecycleObserver observer) {
// 取出 mObserverMap 的上一个 entry,previous
Entry<LifecycleObserver, ObserverWithState> previous = mObserverMap.ceil(observer);

// 如果不为空,获取它的状态
State siblingState = previous != null ? previous.getValue().mState : null;
// 判断 mParentStates 是否为 null,不为 null,去最后的一个状态,否则,为 null
State parentState = !mParentStates.isEmpty() ? mParentStates.get(mParentStates.size() - 1)
: null;
// 取最小的状态
return min(min(mState, siblingState), parentState);
}
  1. 首先,取出 mObserverMap 中上一个的 entry,该 LifecycleRegistry 实例如果是第一次调用 addObserver 实例的话,那么是 null,否则是上一个 observer 的 entry
  2. 根据 previous 是否为 null,设置 siblingState 的值
  3. 判断 mParentStates 是否为 null,不为 null,取 mParentStates 最后一次的状态
  4. 取 mState, siblingState 最小的状态 a,再取 a 与 parentState 的状态 b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public enum State {

DESTROYED,

INITIALIZED,

CREATED,

STARTED,

RESUMED;

public boolean isAtLeast(@NonNull State state) {
return compareTo(state) >= 0;
}
}

State 中,他们排序的顺序是 DESTROYED < INITIALIZED < CREATED < STARTED < RESUMED。

我们知道,我们在 activity 的 onCreate 方法中初始化 LifecycleRegistry,并标记它的状态为 CREATED。当我们第一次在 onCreate 方法调用 addObserver 的时候,在 calculateTargetState 方法中,若是首次调用 previous 为 null,则 siblingState,parentState 为 null, 而 mState 为 CREATED,所以最终的状态为 CREATED,即 State targetState = calculateTargetState(observer); 中 targetState 为 CREATED

1
2
// 取最小的状态
return min(min(mState, siblingState), parentState);

看完 calculateTargetState 方法,我们回过头再来看一下 addObserver 方法。

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
public void addObserver(@NonNull LifecycleObserver observer) {


// 省略若干行

// 这里 mAddingObserverCounter 为 0 ,mHandlingEvent 为 false
boolean isReentrance = mAddingObserverCounter != 0 || mHandlingEvent;
State targetState = calculateTargetState(observer);
mAddingObserverCounter++;
while ((statefulObserver.mState.compareTo(targetState) < 0
&& mObserverMap.contains(observer))) {
pushParentState(statefulObserver.mState);
statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));
popParentState();
// mState / subling may have been changed recalculate
targetState = calculateTargetState(observer);
}

if (!isReentrance) {
// we do sync only on the top level.
sync();
}
mAddingObserverCounter--;
}

这里 statefulObserver.mState 为 DESTROYED 或者 INITIALIZED,肯定比 CREATED 小。而 mObserverMap.contains(observer) 必定为 true,除非我们手动移除掉 mObserverMap。因而,会走进 while循环。

1
2
3
4
5
private void pushParentState(State state) {
mParentStates.add(state);
}

private ArrayList<State> mParentStates = new ArrayList<>();

pushParentState(statefulObserver.mState); 很简单,只是将 statefulObserver 的状态添加到 mParentStates 集合中。

继续往下走,接着会调用 statefulObserver.dispatchEvent(lifecycleOwner, upEvent(statefulObserver.mState));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static Event upEvent(State state) {
switch (state) {
case INITIALIZED:
case DESTROYED:
return ON_CREATE;
case CREATED:
return ON_START;
case STARTED:
return ON_RESUME;
case RESUMED:
throw new IllegalArgumentException();
}
throw new IllegalArgumentException("Unexpected state value " + state);
}

upEvent 方法也很简单,只是返回它的下一个 event。这里因为他们的 state为 INITIALIZED,所以它会返回 ON_CREATE。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void dispatchEvent(LifecycleOwner owner, Event event) {
State newState = getStateAfter(event);
mState = min(mState, newState);
mLifecycleObserver.onStateChanged(owner, event);
mState = newState;
}

static State getStateAfter(Event event) {
switch (event) {
case ON_CREATE:
case ON_STOP:
return CREATED;
case ON_START:
case ON_PAUSE:
return STARTED;
case ON_RESUME:
return RESUMED;
case ON_DESTROY:
return DESTROYED;
case ON_ANY:
break;
}
throw new IllegalArgumentException("Unexpected event value " + event);
}

这里 event 为 ON_CREATE,所以 newState 也为 CREATED。 mState = min(mState, newState); mState newState,两者状态相同,所以 mState 也为 CREATED。接着回调 mLifecycleObserver 的 onStateChanged 方法。所以,这里,会收到我们的 onCreate 事件,与我们的预想相符。

但是我们并没有在 onStart,onResume, onPause , onStop 和 onDestroy 方法中调用 mLifecycleRegistry.handleLifecycleEvent 方法,它又是怎样促发 Observer 的 onStateChanged 方法的。这里先不揭晓,我们先来看一下 26.1.0 以后的 AppCompatActivity,待会你就明白了,会感叹 google 真的牛逼!

从 26.1.0 以后 AppCompatActivity 的设计说起

我们知道,在 26.1.0 以后,如果我们要使用 lifecycle,我们只需要调用以下的方法即可。

SupportActivity

1
2
3
4
5
6
7
8
getLifecycle().addObserver(new GenericLifecycleObserver() {

@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
Log.d(TAG, "onStateChanged: event =" + event);
}
});

跟踪 getLifecycle() 方法,它会跳转到 SupportActivity 的 getLifecycle 方法 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class SupportActivity extends Activity implements LifecycleOwner, Component {

private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this);

protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ReportFragment.injectIfNeededIn(this);
}

@Override
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}

}

在 SupportActivity 中,它默认为我们初始化 mLifecycleRegistry,作为一个成员变量。接着,他在
onCreate 方法中调用了 ReportFragment.injectIfNeededIn(this); 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ReportFragment extends Fragment {
private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
+ ".LifecycleDispatcher.report_fragment_tag";

public static void injectIfNeededIn(Activity activity) {
// ProcessLifecycleOwner should always correctly work and some activities may not extend
// FragmentActivity from support lib, so we use framework fragments for activities
android.app.FragmentManager manager = activity.getFragmentManager();
if (manager.findFragmentByTag(REPORT_FRAGMENT_TAG) == null) {
manager.beginTransaction().add(new ReportFragment(), REPORT_FRAGMENT_TAG).commit();
// Hopefully, we are the first to make a transaction.
manager.executePendingTransactions();
}
}

在 injectIfNeededIn 方法中,它会判断我们是否已经添加 ReportFragment,没有的话,添加进去。

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
public class ReportFragment extends Fragment {
private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
+ ".LifecycleDispatcher.report_fragment_tag";

private ActivityInitializationListener mProcessListener;

private void dispatchCreate(ActivityInitializationListener listener) {
if (listener != null) {
listener.onCreate();
}
}

private void dispatchStart(ActivityInitializationListener listener) {
if (listener != null) {
listener.onStart();
}
}

private void dispatchResume(ActivityInitializationListener listener) {
if (listener != null) {
listener.onResume();
}
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
dispatchCreate(mProcessListener);
dispatch(Lifecycle.Event.ON_CREATE);
}

@Override
public void onStart() {
super.onStart();
dispatchStart(mProcessListener);
dispatch(Lifecycle.Event.ON_START);
}

@Override
public void onResume() {
super.onResume();
dispatchResume(mProcessListener);
dispatch(Lifecycle.Event.ON_RESUME);
}

@Override
public void onPause() {
super.onPause();
dispatch(Lifecycle.Event.ON_PAUSE);
}

@Override
public void onStop() {
super.onStop();
dispatch(Lifecycle.Event.ON_STOP);
}

@Override
public void onDestroy() {
super.onDestroy();
dispatch(Lifecycle.Event.ON_DESTROY);
// just want to be sure that we won't leak reference to an activity
mProcessListener = null;
}
}


然后,它在 onCreat ,onStart, onResume, onPause, onStop, onDestroy 方法中分别调用 dispatch 方法进行分发生命周期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

private void dispatch(Lifecycle.Event event) {
Activity activity = getActivity();
if (activity instanceof LifecycleRegistryOwner) {
((LifecycleRegistryOwner) activity).getLifecycle().handleLifecycleEvent(event);
return;
}

if (activity instanceof LifecycleOwner) {
Lifecycle lifecycle = ((LifecycleOwner) activity).getLifecycle();
if (lifecycle instanceof LifecycleRegistry) {
((LifecycleRegistry) lifecycle).handleLifecycleEvent(event);
}
}
}

在 dispatch 方法中,会先判断 activity 是不是实现了 LifecycleRegistryOwner ,如果是,直接分发,不过不是,判断是否实现 LifecycleOwner,获取它的 lifecycle,调用它 的 handleLifecycleEvent 进行分发。

1
2
3
public class SupportActivity extends Activity implements LifecycleOwner, Component {

private LifecycleRegistry mLifecycleRegistry = new LifecycleRegistry(this

而很明显,高版本的 SupportActivity 实现了 LifecycleOwner 接口,并写 LifecycleOwner.getLifecycle() 是 LifecycleRegistry

普通的 Activity

对于 26.1.0 以后的版本,你会发现,对于普通的 Activity,如果你想要使用 lifecycle,你只需要实现
LifecycleOwner 接口即可。当生命周期变化的时候,它也可以回调 Observer 的 onStateChanged 方法。

回到我们前面的问题:

我们并没有在 onStart,onResume, onPause , onStop 和 onDestroy 方法中调用 mLifecycleRegistry.handleLifecycleEvent 方法,它又是怎样促发 onStateChanged 方法的

我们猜想它也是通过 ReportFragment 实现的。但是在 Activity 的 onCreate 方法中,我们并没有发现它有添加 ReportFragment,我们在 As 全局搜一下,看哪些地方使用到 ReportFragment。如下图

从图中可以看到,有几个地方使用到他。我们先来看一下 LifecycleDispatcher

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
class LifecycleDispatcher {

private static final String REPORT_FRAGMENT_TAG = "android.arch.lifecycle"
+ ".LifecycleDispatcher.report_fragment_tag";

private static AtomicBoolean sInitialized = new AtomicBoolean(false);

static void init(Context context) {
if (sInitialized.getAndSet(true)) {
return;
}
// 在 init 方法中,监听全局 activity 的创建,从而来添加 fragment
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new DispatcherActivityCallback());
}

@SuppressWarnings("WeakerAccess")
@VisibleForTesting
static class DispatcherActivityCallback extends EmptyActivityLifecycleCallbacks {
private final FragmentCallback mFragmentCallback;

DispatcherActivityCallback() {
mFragmentCallback = new FragmentCallback();
}

@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
if (activity instanceof FragmentActivity) {
((FragmentActivity) activity).getSupportFragmentManager()
.registerFragmentLifecycleCallbacks(mFragmentCallback, true);
}
ReportFragment.injectIfNeededIn(activity);
}

@Override
public void onActivityStopped(Activity activity) {
if (activity instanceof FragmentActivity) {
markState((FragmentActivity) activity, CREATED);
}
}

@Override
public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
if (activity instanceof FragmentActivity) {
markState((FragmentActivity) activity, CREATED);
}
}
}

// 省略若干代码

}

可以看到,它 在 init 方法中,通过 context.getApplicationContext() .registerActivityLifecycleCallbacks 监听全局 activity 的创建,在 activity oncreate 的时候,调用 ReportFragment.injectIfNeededIn(activity) ,从而来添加 fragment,进而分发相应的事件。

那 LifecycleDispatcher 的 init 方法又是在哪里调用的呢? 我们全局搜索一下

1
2
3
4
5
6
7
public class ProcessLifecycleOwnerInitializer extends ContentProvider {
@Override
public boolean onCreate() {
LifecycleDispatcher.init(getContext());
ProcessLifecycleOwner.init(getContext());
return true;
}

可以看到它是在 ProcessLifecycleOwnerInitializer 的 onCreate 方法中调用的。而 ProcessLifecycleOwnerInitializer 是一个 ContentProvider。我们知道 ContentProvider 一般是在 AndroidManifest 中生命的。

果然,在 extensions-1.1.1.aar 中,我们惊喜地发现,它在 Manifest 里面注册了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.arch.lifecycle.extensions" >

<uses-sdk android:minSdkVersion="14" />

<application>
<provider
android:name="android.arch.lifecycle.ProcessLifecycleOwnerInitializer"
android:authorities="${applicationId}.lifecycle-trojan"
android:exported="false"
android:multiprocess="true" />
</application>

</manifest>

而 ContentProvider 的 onCreate 方法优先于 Application 的 onCreate 执行,所以在 Application 之前我们就调用了 ProcessLifecycleOwnerInitializer init 方法,监听了 Activity 的创建,当 Actiivty 创建的时候,会尝试为 Activity 添加 ReportFragment。而 ReportFragment 会在 Activity 生命周期变化的时候帮助我们分发生命周期。

ContentProvider 的 onCreate 方法优先于 Application 的 onCreate 执行,可以查看这一篇博客 Android系统中的Application和四大组件一些方法的启动顺序和一些坑


总结

ok,我们来梳理一下。

对于 26.1.0 以后的 SupportActivity

它在 Activity onCreate 的时候添加了 ReportFragment,这个 ReportFragment 相当于一个代理,它在 onActivityCreated 的时候 dispatch(Lifecycle.Event.ON_CREATE) 进行分发生命周期,onStart, onResume, onPause, onStop, onDestroy 的时候也是如此。而 在 dispatch 中 它调用了 LifecycleRegistry handleLifecycleEvent 的方法。而 LifecycleRegistry 方法中经过一系列处理,它又调用了 observer 的 onStateChange 方法,去通知相应的 observer。

对于普通的 Activity

它利用了 ContentProvide 的特征,它是在 Application onCreate 之前初始化的,他在 ProcessLifecycleOwnerInitializer oncreate 的时候监听 Activity 的创建,在 Activity 创建的时候,判断是否已经添加过 ReportFragment,没有的话,添加进去。这是一个很巧妙的设计,隐式初始化了 lifecycle。

用流程图表示如下:

该图片引用自 Android 架构组件(一)——Lifecycle

Lifecycle 设计借鉴

  1. 利用 ProcessLifecycleOwnerInitializer contentProvider 来隐式加载

想一下,如果 ProcessLifecycleOwnerInitializer 不利用 contentProvider 来隐式加载的话,对于 普通的 Activity,旧版本等,如果想使用 lifecycle,那必须在基类中,手动调用 ReportFragment.injectIfNeededIn(activity) 的方法。

  1. 利用 fragment 来分发生命周期

利用 fragment 来分发生命周期有两个优点

  • 将逻辑从 Activity 中剥离出来,减少耦合,方便复用
  • 可以做到在 Activity onCreate 之后才回调 observer 的 CREATED Event 事件。如果是通过 Application registerActivityLifecycleCallbacks 方法来分发生命周期的话,因为 ActivityLifecycleCallbacks 的 onActivityCreated 是在 Activity oncreate 之前调用的。

下一篇:Android livedata 源码解剖

推荐阅读:

java 代理模式详解
观察者设计模式 Vs 事件委托(java)
Android Fragment 的妙用 - 优雅地申请权限和处理 onActivityResult

说在前面

本次推出 Android Architecture Components 系列文章,目前写好了四篇,主要是关于 lifecycle,livedata 的使用和源码分析,其余的 Navigation, Paging library,Room,WorkMannager 等春节结束之后会更新,欢迎关注我的公众号,有更新的话会第一时间会在公众号上面通知。

Android lifecycle 使用详解

Android LiveData 使用详解

Android lifecyle 源码解剖

Android livedata 源码解剖

github sample 地址: ArchiteComponentsSample

在这里插入图片描述

Architecture Components

lifecycle 是 2107 年 google 大会推出来的,它属于 architecture compoment 里面的一个组件,它可以干什么用呢? 简单得来说,它可以用来检查 Activity 的生命周期,而不必强依赖 Activity。


为什么要引进 lifecycle

举一下我们最常用的 MVP 例子,没引进 lifecycle 之前,我们需要在 Activity 或者 Fragment 销毁的时候,即 onDestroy 的时候手动调用 onDestroy 方法,这里会带来一些问题,每一次在 Activity 或者 Fragment 销毁的烧开后都要调用 presenter.destory() 方法,这样的代码枯燥,毫无意义。

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 MyPresenter{
public MyPresenter() {
}

void create() {
//do something
}

void destroy() {
//do something
}
}

class MyActivity extends AppCompatActivity {
private MyPresenter presenter;

public void onCreate(...) {
presenter= new MyPresenter ();
presenter.create();
}

public void onDestroy() {
super.onDestroy();
presenter.destory();
}
}

当然我们也可以定义一些 IBasePresenter 的接口,在 BaseActivity 的时候调用 IBasePresenter 的 onDestroy 方法,这样也确实能做到。只不过稍微繁琐一点。

那如果是别的类的呢,比如 MediaCompoment,在 Activity 的时候,我们需要销毁一些资源,按照传统的方法,我们还是需要在 Activity onDestroy 的时候手动调用 onDestroy 方法。那有没有更好的方法呢?当然是有的,lifecycle 就可以解决这个问题。接下来,我们先来看一下 Lifycycle 的基本使用。


Lifycycle 的基本使用

  1. 引入相关的依赖包

Lifecycle 已经是稳定版,它包含在 support library 26.1.0 及之后的依赖包中,如果我们的项目基于这些依赖包,那么不需要额外的引用。

1
2
3
4
5
6
// ViewModel and LiveData
implementation "android.arch.lifecycle:extensions:1.1.0"
// alternatively, just ViewModel
implementation "android.arch.lifecycle:viewmodel:1.1.0"
// alternatively, just LiveData
implementation "android.arch.lifecycle:livedata:1.1.0"

support library在26.1.0 之前,lifecycle 并没有集成进去,需要我们引入另外的包。

1
implementation "android.arch.lifecycle:extensions:1.0.0-alpha4"
  1. 使用

这里同样分为几种情况

  1. support library 26.1.0 之后,且继承 FragmentActivity,那么我们直接调用 getLifecycle().addObserver 方法即可,当 Activity 的生命周期变化的时候,将会回调 onStateChanged 的方法,状态分别是一一对应的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MainActivity extends AppCompatActivity {

private static final String TAG = "MainActivity";

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

getLifecycle().addObserver(new GenericLifecycleObserver() {

@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
Log.d(TAG, "onStateChanged: event =" + event);
}
});
}

}
  1. support library 26.1.0 之后,不是继承 FragmentActivity,只是简单地继承 Actiivty
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 SimpleLifecycleActivity extends Activity implements LifecycleOwner {

private static final String TAG = "SimpleLifecycleActivity";
private LifecycleRegistry mLifecycleRegistry;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_simple_lifecycle);
mLifecycleRegistry = new LifecycleRegistry(this);
mLifecycleRegistry.markState(Lifecycle.State.CREATED);
getLifecycle().addObserver(new GenericLifecycleObserver() {

@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
Log.d(TAG, "onStateChanged: event =" + event);
}
});
}

@Override
protected void onStart() {
super.onStart();
mLifecycleRegistry.markState(Lifecycle.State.STARTED);
}

@NonNull
@Override
public Lifecycle getLifecycle() {
return mLifecycleRegistry;
}

}

  1. support library 26.1.0 之前

(现在的 support library 基本都在 26.1.0 之后了,这个可以忽略)

第一步:实现 LifecycleOwner 接口,并返回响应的 Lifecycle

1
2
3
4
5
6
7
8
9
public interface LifecycleOwner {
/**
* Returns the Lifecycle of the provider.
*
* @return The lifecycle of the provider.
*/
@NonNull
Lifecycle getLifecycle();
}

第二步:在 Activity 生命周期变化的时候,调用 mLifecycleRegistry.handleLifecycleEvent 方法,分发相应的生命周期。

第三步:调用 Lifecycle 的 addObserver 方法添加相应的 Observer。

代码如下

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

private LifecycleRegistry mRegistry;

private static final String TAG = "MainActivity";

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRegistry = new LifecycleRegistry(this);
mRegistry.markState(Lifecycle.State.CREATED);
getLifecycle().addObserver(new GenericLifecycleObserver() {
@Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
Log.d(TAG, "onStateChanged:event =" + event);
}

@Override
public Object getReceiver() {
return null;
}
});

}

@Override
protected void onStart() {
super.onStart();
mRegistry.markState(Lifecycle.State.STARTED);
mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START);
}

@Override
protected void onResume() {
super.onResume();
mRegistry.markState(Lifecycle.State.RESUMED);
mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME);
}

@Override
protected void onPause() {
super.onPause();
mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE);
}

@Override
protected void onStop() {
super.onStop();
mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP);
}

@Override
protected void onDestroy() {
super.onDestroy();
mRegistry.markState(Lifecycle.State.DESTROYED);
mRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY);
}

@Override
public Lifecycle getLifecycle() {
return mRegistry;
}
}


总结

我们回过头来看一下我们上面提出的问题?

MediaCompoment 在 Activity ondestroy 的时候,我们需要销毁一些资源,用传统的方法,我们需要在 Activity onDestroy 的时候手动调用 onDestroy 方法。这样会存在一个问题,调用者必须知道比较清楚得知道 MediaCompoment 的设计,否则可能会忘记调用 onDestroy 的方法。

那有没有一种方法, 当 Activity 生命周期变化的时候,MediaCompoment 自身能够检测到 Activity 的 生命周期变化,从而做相应的处理。

答案当然是有的,使用 lifycycle。

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
public class MediaCompoment {
private static final String TAG = "MediaCompoment";

private final LifecycleOwner mLifecycleOwner;

public MediaCompoment(LifecycleOwner lifecycleOwner) {
mLifecycleOwner = lifecycleOwner;
mLifecycleOwner.getLifecycle().addObserver(new GenericLifecycleObserver() {
@Override
public void onStateChanged(LifecycleOwner source, final Lifecycle.Event event) {
if (event == Lifecycle.Event.ON_CREATE) {
onCreate();
} else if (event == Lifecycle.Event.ON_START) {
onStart();
} else if (event == Lifecycle.Event.ON_RESUME) {
onResume();
} else if (event == Lifecycle.Event.ON_PAUSE) {
onPause();
} else if (event == Lifecycle.Event.ON_STOP) {
onStop();
} else if (event == Lifecycle.Event.ON_DESTROY) {
onDestroy();
}
}
});
}

public void onCreate() {
Log.d(TAG, "onCreate:");
}

public void onStart() {
Log.d(TAG, "onStart:");
}

public void onResume() {
Log.d(TAG, "onResume:");
}

public void onPause() {
Log.d(TAG, "onPause:");
}

public void onStop() {
Log.d(TAG, "onStop:");
}

public void onDestroy() {
Log.d(TAG, "onDestroy:");
}
}

小结:

  1. lifycycle 其实是用观察者模式实现的,当 Activity 生命周期变化的时候,通知相应的 Observers 即观察者。
  2. 使用 lifecycle,我们可以将释放资源的动作内聚在自身,减少与调用者之间的耦合。

下一篇博客:Android LiveData 使用详解

推荐阅读

Android 启动优化(一) - 有向无环图

Android 启动优化(二) - 拓扑排序的原理以及解题思路

Android 启动优化(三)- AnchorTask 开源了

Android 启动优化(四)- AnchorTask 是怎么实现的

Android 启动优化(五)- AnchorTask 1.0.0 版本正式发布了

Android 启动优化(六)- 深入理解布局优化

这几篇文章从 0 到 1,讲解 DAG 有向无环图是怎么实现的,以及在 Android 启动优化的应用。

推荐理由:现在挺多文章一谈到启动优化,动不动就聊拓扑结构,这篇文章从数据结构到算法、到设计都给大家说清楚了,开源项目也有非常强的借鉴意义。

在这里插入图片描述

简介

什么是 Hook

Hook 又叫“钩子”,它可以在事件传送的过程中截获并监控事件的传输,将自身的代码与系统方法进行融入。

这样当这些方法被调用时,也就可以执行我们自己的代码,这也是面向切面编程的思想(AOP)。

Hook 分类

1.根据Android开发模式,Native模式(C/C++)和Java模式(Java)区分,在Android平台上

  • Java层级的Hook;
  • Native层级的Hook;

2.根 Hook 对象与 Hook 后处理事件方式不同,Hook还分为:

  • 消息Hook;
  • API Hook;

3.针对Hook的不同进程上来说,还可以分为:

  • 全局Hook;
  • 单个进程Hook;

常见 Hook 框架

在Android开发中,有以下常见的一些Hook框架:

  1. Xposed

通过替换 /system/bin/app_process 程序控制 Zygote 进程,使得 app_process 在启动过程中会加载 XposedBridge.jar 这个 Jar 包,从而完成对 Zygote 进程及其创建的 Dalvik 虚拟机的劫持。

Xposed 在开机的时候完成对所有的 Hook Function 的劫持,在原 Function 执行的前后加上自定义代码。

  1. Cydia Substrate

Cydia Substrate 框架为苹果用户提供了越狱相关的服务框架,当然也推出了 Android 版 。Cydia Substrate 是一个代码修改平台,它可以修改任何进程的代码。

不管是用 Java 还是 C/C++(native代码)编写的,而 Xposed 只支持 Hook app_process 中的 Java 函数。

  1. Legend

Legend 是 Android 免 Root 环境下的一个 Apk Hook 框架,该框架代码设计简洁,通用性高,适合逆向工程时一些 Hook 场景。大部分的功能都放到了 Java 层,这样的兼容性就非常好。

原理是这样的,直接构造出新旧方法对应的虚拟机数据结构,然后替换信息写到内存中即可。

Hook 必须掌握的知识

  • 反射

如果你对反射还不是很熟悉的话,建议你先复习一下 java 反射的相关知识。有兴趣的,可以看一下我的这一篇博客 Java 反射机制详解

  • java 的动态代理

动态代理是指在运行时动态生成代理类,不需要我们像静态代理那个去手动写一个个的代理类。在 java 中,我们可以使用 InvocationHandler 实现动态代理,有兴趣的,可以查看我的这一篇博客 java 代理模式详解

本文的主要内容是讲解单个进程的 Hook,以及怎样 Hook。有兴趣的可以关注我的微信公众号:程序员徐公
在这里插入图片描述


Hook 使用实例

Hook 选择的关键点

  • Hook 的选择点:尽量静态变量和单例,因为一旦创建对象,它们不容易变化,非常容易定位。

  • Hook 过程:

    • 寻找 Hook 点,原则是尽量静态变量或者单例对象,尽量 Hook public 的对象和方法。
    • 选择合适的代理方式,如果是接口可以用动态代理。
    • 偷梁换柱——用代理对象替换原始对象。
  • Android 的 API 版本比较多,方法和类可能不一样,所以要做好 API 的兼容工作。

简单案例一: 使用 Hook 修改 View.OnClickListener 事件

首先,我们先分析 View.setOnClickListener 源码,找出合适的 Hook 点。可以看到 OnClickListener 对象被保存在了一个叫做 ListenerInfo 的内部类里,其中 mListenerInfo 是 View 的成员变量。ListeneInfo 里面保存了 View 的各种监听事件。因此,我们可以想办法 hook ListenerInfo 的 mOnClickListener 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void setOnClickListener(@Nullable OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}

static class ListenerInfo {

---

ListenerInfo getListenerInfo() {
if (mListenerInfo != null) {
return mListenerInfo;
}
mListenerInfo = new ListenerInfo();
return mListenerInfo;
}

---
}

接下来,让我们一起来看一下怎样 Hook View.OnClickListener 事件?

大概分为三步:

  • 第一步:获取 ListenerInfo 对象

从 View 的源代码,我们可以知道我们可以通过 getListenerInfo 方法获取,于是,我们利用反射得到 ListenerInfo 对象

  • 第二步:获取原始的 OnClickListener事件方法

从上面的分析,我们知道 OnClickListener 事件被保存在 ListenerInfo 里面,同理我们利用反射获取

  • 第三步:偷梁换柱,用 Hook代理类 替换原始的 OnClickListener
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void hookOnClickListener(View view) throws Exception {
// 第一步:反射得到 ListenerInfo 对象
Method getListenerInfo = View.class.getDeclaredMethod("getListenerInfo");
getListenerInfo.setAccessible(true);
Object listenerInfo = getListenerInfo.invoke(view);
// 第二步:得到原始的 OnClickListener事件方法
Class<?> listenerInfoClz = Class.forName("android.view.View$ListenerInfo");
Field mOnClickListener = listenerInfoClz.getDeclaredField("mOnClickListener");
mOnClickListener.setAccessible(true);
View.OnClickListener originOnClickListener = (View.OnClickListener) mOnClickListener.get(listenerInfo);
// 第三步:用 Hook代理类 替换原始的 OnClickListener
View.OnClickListener hookedOnClickListener = new HookedClickListenerProxy(originOnClickListener);
mOnClickListener.set(listenerInfo, hookedOnClickListener);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class HookedClickListenerProxy implements View.OnClickListener {

private View.OnClickListener origin;

public HookedClickListenerProxy(View.OnClickListener origin) {
this.origin = origin;
}

@Override
public void onClick(View v) {
Toast.makeText(v.getContext(), "Hook Click Listener", Toast.LENGTH_SHORT).show();
if (origin != null) {
origin.onClick(v);
}
}

}

执行以下代码,将会看到当我们点击该按钮的时候,会弹出 toast “Hook Click Listener”

1
2
3
4
5
6
7
mBtn1 = (Button) findViewById(R.id.btn_1);
mBtn1.setOnClickListener(this);
try {
HookHelper.hookOnClickListener(mBtn1);
} catch (Exception e) {
e.printStackTrace();
}

简单案例二: HooK Notification

发送消息到通知栏的核心代码如下:

1
2
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(id, builder.build());

跟踪 notify 方法发现最终会调用到 notifyAsUser 方法

1
2
3
4
5
public void notify(String tag, int id, Notification notification)
{
notifyAsUser(tag, id, notification, new UserHandle(UserHandle.myUserId()));
}

而在 notifyAsUser 方法中,我们惊喜地发现 service 是一个单例,因此,我们可以想方法 hook 住这个 service,而 notifyAsUser 最终会调用到 service 的 enqueueNotificationWithTag 方法。因此 hook 住 service 的 enqueueNotificationWithTag 方法即可

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
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
//
INotificationManager service = getService();
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
Notification.addFieldsFromContext(mContext, notification);
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
}
fixLegacySmallIcon(notification, pkg);
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
if (notification.getSmallIcon() == null) {
throw new IllegalArgumentException("Invalid notification (no valid small icon): "
+ notification);
}
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
copy, user.getIdentifier());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

private static INotificationManager sService;

static public INotificationManager getService()
{
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService("notification");
sService = INotificationManager.Stub.asInterface(b);
return sService;
}

综上,要 Hook Notification,大概需要三步:

  • 第一步:得到 NotificationManager 的 sService
  • 第二步:因为 sService 是接口,所以我们可以使用动态代理,获取动态代理对象
  • 第三步:偷梁换柱,使用动态代理对象 proxyNotiMng 替换系统的 sService

于是,我们可以写出如下的代码

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

public static void hookNotificationManager(final Context context) throws Exception {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

Method getService = NotificationManager.class.getDeclaredMethod("getService");
getService.setAccessible(true);
// 第一步:得到系统的 sService
final Object sOriginService = getService.invoke(notificationManager);

Class iNotiMngClz = Class.forName("android.app.INotificationManager");
// 第二步:得到我们的动态代理对象
Object proxyNotiMng = Proxy.newProxyInstance(context.getClass().getClassLoader(), new
Class[]{iNotiMngClz}, new InvocationHandler() {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d(TAG, "invoke(). method:" + method);
String name = method.getName();
Log.d(TAG, "invoke: name=" + name);
if (args != null && args.length > 0) {
for (Object arg : args) {
Log.d(TAG, "invoke: arg=" + arg);
}
}
Toast.makeText(context.getApplicationContext(), "检测到有人发通知了", Toast.LENGTH_SHORT).show();
// 操作交由 sOriginService 处理,不拦截通知
return method.invoke(sOriginService, args);
// 拦截通知,什么也不做
// return null;
// 或者是根据通知的 Tag 和 ID 进行筛选
}
});
// 第三步:偷梁换柱,使用 proxyNotiMng 替换系统的 sService
Field sServiceField = NotificationManager.class.getDeclaredField("sService");
sServiceField.setAccessible(true);
sServiceField.set(notificationManager, proxyNotiMng);

}



Hook 使用进阶

Hook ClipboardManager

第一种方法

从上面的 hook NotificationManager 例子中,我们可以得知 NotificationManager 中有一个静态变量 sService,这个变量是远端的 service。因此,我们尝试查找 ClipboardManager 中是不是也存在相同的类似静态变量。

查看它的源码发现它存在 mService 变量,该变量是在 ClipboardManager 构造函数中初始化的,而 ClipboardManager 的构造方法用 @hide 标记,表明该方法对调用者不可见。

而我们知道 ClipboardManager,NotificationManager 其实这些都是单例的,即系统只会创建一次。因此我们也可以认为
ClipboardManager 的 mService 是单例的。因此 mService 应该是可以考虑 hook 的一个点。

1
2
3
4
5
6
7
8
9
10
11
public class ClipboardManager extends android.text.ClipboardManager {
private final Context mContext;
private final IClipboard mService;

/** {@hide} */
public ClipboardManager(Context context, Handler handler) throws ServiceNotFoundException {
mContext = context;
mService = IClipboard.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.CLIPBOARD_SERVICE));
}
}

接下来,我们再来一个看一下 ClipboardManager 的相关方法 setPrimaryClip , getPrimaryClip

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public void setPrimaryClip(ClipData clip) {
try {
if (clip != null) {
clip.prepareToLeaveProcess(true);
}
mService.setPrimaryClip(clip, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

/**
* Returns the current primary clip on the clipboard.
*/
public ClipData getPrimaryClip() {
try {
return mService.getPrimaryClip(mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}

可以发现这些方法最终都会调用到 mService 的相关方法。因此,ClipboardManager 的 mService 确实是一个可以 hook 的一个点。

hook ClipboardManager.mService 的实现

大概需要三个步骤

  • 第一步:得到 ClipboardManager 的 mService
  • 第二步:初始化动态代理对象
  • 第三步:偷梁换柱,使用 proxyNotiMng 替换系统的 mService
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
public static void hookClipboardService(final Context context) throws Exception {
ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
Field mServiceFiled = ClipboardManager.class.getDeclaredField("mService");
mServiceFiled.setAccessible(true);
// 第一步:得到系统的 mService
final Object mService = mServiceFiled.get(clipboardManager);

// 第二步:初始化动态代理对象
Class aClass = Class.forName("android.content.IClipboard");
Object proxyInstance = Proxy.newProxyInstance(context.getClass().getClassLoader(), new
Class[]{aClass}, new InvocationHandler() {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d(TAG, "invoke(). method:" + method);
String name = method.getName();
if (args != null && args.length > 0) {
for (Object arg : args) {
Log.d(TAG, "invoke: arg=" + arg);
}
}
if ("setPrimaryClip".equals(name)) {
Object arg = args[0];
if (arg instanceof ClipData) {
ClipData clipData = (ClipData) arg;
int itemCount = clipData.getItemCount();
for (int i = 0; i < itemCount; i++) {
ClipData.Item item = clipData.getItemAt(i);
Log.i(TAG, "invoke: item=" + item);
}
}
Toast.makeText(context, "检测到有人设置粘贴板内容", Toast.LENGTH_SHORT).show();
} else if ("getPrimaryClip".equals(name)) {
Toast.makeText(context, "检测到有人要获取粘贴板的内容", Toast.LENGTH_SHORT).show();
}
// 操作交由 sOriginService 处理,不拦截通知
return method.invoke(mService, args);

}
});

// 第三步:偷梁换柱,使用 proxyNotiMng 替换系统的 mService
Field sServiceField = ClipboardManager.class.getDeclaredField("mService");
sServiceField.setAccessible(true);
sServiceField.set(clipboardManager, proxyInstance);

}


第二种方法

对 Android 源码有基本了解的人都知道,Android 中的各种 Manager 都是通过 ServiceManager 获取的。因此,我们可以通过 ServiceManager hook 所有系统 Manager,ClipboardManager 当然也不例外。

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


/**
* Returns a reference to a service with the given name.
*
* @param name the name of the service to get
* @return a reference to the service, or <code>null</code> if the service doesn't exist
*/
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return getIServiceManager().getService(name);
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
}

老套路

  • 第一步:通过反射获取剪切板服务的远程Binder对象,这里我们可以通过 ServiceManager getService 方法获得
  • 第二步:创建我们的动态代理对象,动态代理原来的Binder对象
  • 第三步:偷梁换柱,把我们的动态代理对象设置进去
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public static void hookClipboardService() throws Exception {

//通过反射获取剪切板服务的远程Binder对象
Class serviceManager = Class.forName("android.os.ServiceManager");
Method getServiceMethod = serviceManager.getMethod("getService", String.class);
IBinder remoteBinder = (IBinder) getServiceMethod.invoke(null, Context.CLIPBOARD_SERVICE);

//新建一个我们需要的Binder,动态代理原来的Binder对象
IBinder hookBinder = (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),
new Class[]{IBinder.class}, new ClipboardHookRemoteBinderHandler(remoteBinder));

//通过反射获取ServiceManger存储Binder对象的缓存集合,把我们新建的代理Binder放进缓存
Field sCacheField = serviceManager.getDeclaredField("sCache");
sCacheField.setAccessible(true);
Map<String, IBinder> sCache = (Map<String, IBinder>) sCacheField.get(null);
sCache.put(Context.CLIPBOARD_SERVICE, hookBinder);

}


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 class ClipboardHookRemoteBinderHandler implements InvocationHandler {

private IBinder remoteBinder;
private Class iInterface;
private Class stubClass;

public ClipboardHookRemoteBinderHandler(IBinder remoteBinder) {
this.remoteBinder = remoteBinder;
try {
this.iInterface = Class.forName("android.content.IClipboard");
this.stubClass = Class.forName("android.content.IClipboard$Stub");
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d("RemoteBinderHandler", method.getName() + "() is invoked");
if ("queryLocalInterface".equals(method.getName())) {
//这里不能拦截具体的服务的方法,因为这是一个远程的Binder,还没有转化为本地Binder对象
//所以先拦截我们所知的queryLocalInterface方法,返回一个本地Binder对象的代理
return Proxy.newProxyInstance(remoteBinder.getClass().getClassLoader(),
new Class[]{this.iInterface},
new ClipboardHookLocalBinderHandler(remoteBinder, stubClass));
}

return method.invoke(remoteBinder, args);
}
}

Hook Activity

关于怎样 hook activity,以及怎样启动没有在 AndroidManifet 注册的 activity,可以查看我的这一篇博客。

Android Hook Activity 的几种姿势

源码下载地址HookDemo


推荐阅读

Android 启动优化(一) - 有向无环图

Android 启动优化(二) - 拓扑排序的原理以及解题思路

Android 启动优化(三)- AnchorTask 开源了

Android 启动优化(四)- AnchorTask 是怎么实现的

Android 启动优化(五)- AnchorTask 1.0.0 版本正式发布了

Android 启动优化(六)- 深入理解布局优化

这几篇文章从 0 到 1,讲解 DAG 有向无环图是怎么实现的,以及在 Android 启动优化的应用。

推荐理由:现在挺多文章一谈到启动优化,动不动就聊拓扑结构,这篇文章从数据结构到算法、到设计都给大家说清楚了,开源项目也有非常强的借鉴意义。

在这里插入图片描述

使用CoordinatorLayout打造各种炫酷的效果

自定义Behavior —— 仿知乎,FloatActionButton隐藏与展示

NestedScrolling 机制深入解析

一步步带你读懂 CoordinatorLayout 源码

自定义 Behavior -仿新浪微博发现页的实现

ViewPager,ScrollView 嵌套ViewPager滑动冲突解决

自定义 behavior - 完美仿 QQ 浏览器首页,美团商家详情页

重磅消息:小编我开始运营自己的公众号了, 目前从事于 Android 开发,除了分享 Android开发相关知识,还有职场心得,面试经验,学习心得,人生感悟等等。希望通过该公众号,让你看到程序猿不一样的一面,我们不只会敲代码,我们还会。。。。。。

有兴趣的话可以关注我的公众号 徐公码字(stormjun94),或者拿起你的手机扫一扫,期待你的参与

Android 技术人

效果图

我们先来看一下新浪微博发现页的效果:

接下来我们在来看一下我们仿照新浪微博实现的效果

仿新浪微博效果图

实现思路分析

我们这里先定义两种状态,open 和 close 状态。

  • open 状态指 Tab+ViewPager 还没有滑动到顶部的时候,header 还 没有被完全移除屏幕的时候
  • close 状态指 Tab+ViewPager 滑动到顶部的时候,Header 被移除屏幕的时候

从效果图,我们可以看到 在 open 状态下,我们向上滑动 ViewPager 里面的 RecyclerView 的 时候,RecyclerView 并不会向上移动(RecyclerView 的滑动事件交给 外部的容器处理,被被全部消费掉了),而是整个布局(指 Header + Tab +ViewPager)会向上偏移 。当 Tab 滑动到顶部的时候,我们向上滑动 ViewPager 里面的 RecyclerView 的时候,RecyclerView 可以正常向上滑动,即此时外部容器没有拦截滑动事件

同时我们可以看到在 open 状态的时候,我们是不支持下拉刷新的,这个比较容易实现,监听页面的状态,如果是 open 状态,我们设置 SwipeRefreshLayout setEnabled 为 false,这样不会 拦截事件,在页面 close 的时候,设置 SwipeRefreshLayout setEnabled 为 TRUE,这样就可以支持下拉刷新了。

基于上面的分析,我们这里可以把整个效果划分为两个部分,第一部分为 Header,第二部分为 Tab+ViewPager。下文统一把第一部分称为 Header,第二部分称为 Content 。

需要实现的效果为:在页面状态为 open 的时候,向上滑动 Header 的时候,整体向上偏移,ViewPager 里面的 RecyclerView 向上滑动的时候,消费其滑动事件,并整体向上移动。在页面状态为 close 的时候,不消耗 RecyclerView 的 滑动事件。

在上一篇博客 一步步带你读懂 CoordinatorLayout 源码 中,我们有提到在 CoordinatorLayout中,我们可以通过 给子 View 自定义 Behavior 来处理事件。它是一个容器,实现了 NestedScrollingParent 接口。它并不会直接处理事件,而是会尽可能地交给子 View 的 Behavior 进行处理。因此,为了减少依赖,我们把这两部分的关系定义为 Content 依赖于 Header。Header 移动的时候,Content 跟着 移动。所以,我们在处理滑动事件的时候,只需要处理好 Header 部分的 Behavior 就oK了,Content 部分的 Behavior 不需要处理滑动事件,只需依赖于 Header ,跟着做相应的移动即可。


Header 部分的实现

Header 部分实现的两个关键点在于

  1. 在页面状态为 open 的时候,ViewPager 里面的 RecyclerView 向上滑动的时候,消费其滑动事件,并整体向上移动。在页面状态为 close 的时候,不消耗 RecyclerView 的 滑动事件
  2. 在页面状态为 open 的时候,向上滑动 Header 的时候,整体向上偏移。

第一个关键点的实现

这里区分页面状态是 open 还是 close 状态是通过 Header 是否移除屏幕来区分的,即 child.getTranslationY() == getHeaderOffsetRange() 。

1
2
3
4
5
private boolean isClosed(View child) {
boolean isClosed = child.getTranslationY() == getHeaderOffsetRange();
return isClosed;
}

NestedScrolling 机制深入解析博客中,我们对 NestedScrolling 机制做了如下的总结。

  • 在 Action_Down 的时候,Scrolling child 会调用 startNestedScroll 方法,通过 childHelper 回调 Scrolling Parent 的 startNestedScroll 方法。
  • 在 Action_move 的时候,Scrolling Child 要开始滑动的时候,会调用dispatchNestedPreScroll 方法,通过 ChildHelper 询问 Scrolling Parent 是否要先于 Child 进行 滑动,若需要的话,会调用 Parent 的 onNestedPreScroll 方法,协同 Child 一起进行滑动
  • 当 ScrollingChild 滑动完成的时候,会调用 dispatchNestedScroll 方法,通过 ChildHelper 询问 Scrolling Parent 是否需要进行滑动,需要的话,会 调用 Parent 的 onNestedScroll 方法
  • 在 Action_down,Action_move 的时候,会调用 Scrolling Child 的stopNestedScroll ,通过 ChildHelper 询问 Scrolling parent 的 stopNestedScroll 方法。
  • 如果需要处理 Fling 动作,我们可以通过 VelocityTrackerCompat 获得相应的速度,并在 Action_up 的时候,调用 dispatchNestedPreFling 方法,通过 ChildHelper 询问 Parent 是否需要先于 child 进行 Fling 动作
    在 Child 处理完 Fling 动作时候,如果 Scrolling Parent 还需要处理 Fling 动作,我们可以调用 dispatchNestedFling 方法,通过 ChildHelper ,调用 Parent 的 onNestedFling 方法

而 RecyclerView 也是 Scrolling Child (实现了 NestedScrollingChild 接口),RecyclerView 在开始滑动的 时候会先调用 CoordinatorLayout 的 startNestedScroll 方法,而 CoordinatorLayout 会 调用子 View 的 Behavior 的 startNestedScroll 方法。并且只有 boolean startNestedScroll 返回 TRUE 的 时候,才会调用接下里 Behavior 中的 onNestedPreScroll 和 onNestedScroll 方法。

所以,我们在 WeiboHeaderPagerBehavior 的 onStartNestedScroll 方法可以这样写,可以确保 只拦截垂直方向上的滚动事件,且当前状态是打开的并且还可以继续向上收缩的时候还会拦截

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View
directTargetChild, View target, int nestedScrollAxes) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "onStartNestedScroll: nestedScrollAxes=" + nestedScrollAxes);
}

boolean canScroll = canScroll(child, 0);
//拦截垂直方向上的滚动事件且当前状态是打开的并且还可以继续向上收缩
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0 && canScroll &&
!isClosed(child);

}


拦截事件之后,我们需要在 RecyclerView 滑动之前消耗事件,并且移动 Header,让其向上偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target,
int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
//dy>0 scroll up;dy<0,scroll down
Log.i(TAG, "onNestedPreScroll: dy=" + dy);
float halfOfDis = dy;
// 不能滑动了,直接给 Header 设置 终值,防止出错
if (!canScroll(child, halfOfDis)) {
child.setTranslationY(halfOfDis > 0 ? getHeaderOffsetRange() : 0);
} else {
child.setTranslationY(child.getTranslationY() - halfOfDis);
}
//consumed all scroll behavior after we started Nested Scrolling
consumed[1] = dy;
}


当然,我们也需要处理 Fling 事件,在页面没有完全关闭的 时候,消费所有 fling 事件。

1
2
3
4
5
6
7
8
@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target,
float velocityX, float velocityY) {
// consumed the flinging behavior until Closed
return !isClosed(child);
}


至于滑动到顶部的动画,我是通过 mOverScroller + FlingRunnable 来实现的 。完整代码如下。

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
public class WeiboHeaderPagerBehavior extends ViewOffsetBehavior {
private static final String TAG = "UcNewsHeaderPager";
public static final int STATE_OPENED = 0;
public static final int STATE_CLOSED = 1;
public static final int DURATION_SHORT = 300;
public static final int DURATION_LONG = 600;

private int mCurState = STATE_OPENED;
private OnPagerStateListener mPagerStateListener;

private OverScroller mOverScroller;

private WeakReference<CoordinatorLayout> mParent;
private WeakReference<View> mChild;

public void setPagerStateListener(OnPagerStateListener pagerStateListener) {
mPagerStateListener = pagerStateListener;
}

public WeiboHeaderPagerBehavior() {
init();
}

public WeiboHeaderPagerBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

private void init() {
mOverScroller = new OverScroller(BaseAPP.getAppContext());
}

@Override
protected void layoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
super.layoutChild(parent, child, layoutDirection);
mParent = new WeakReference<CoordinatorLayout>(parent);
mChild = new WeakReference<View>(child);
}

@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View
directTargetChild, View target, int nestedScrollAxes) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "onStartNestedScroll: nestedScrollAxes=" + nestedScrollAxes);
}

boolean canScroll = canScroll(child, 0);
//拦截垂直方向上的滚动事件且当前状态是打开的并且还可以继续向上收缩
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0 && canScroll &&
!isClosed(child);

}

@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target,
float velocityX, float velocityY) {
// consumed the flinging behavior until Closed

boolean coumsed = !isClosed(child);
Log.i(TAG, "onNestedPreFling: coumsed=" +coumsed);
return coumsed;
}

@Override
public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target,
float velocityX, float velocityY, boolean consumed) {
Log.i(TAG, "onNestedFling: velocityY=" +velocityY);
return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY,
consumed);

}

private boolean isClosed(View child) {
boolean isClosed = child.getTranslationY() == getHeaderOffsetRange();
return isClosed;
}

public boolean isClosed() {
return mCurState == STATE_CLOSED;
}

private void changeState(int newState) {
if (mCurState != newState) {
mCurState = newState;
if (mCurState == STATE_OPENED) {
if (mPagerStateListener != null) {
mPagerStateListener.onPagerOpened();
}

} else {
if (mPagerStateListener != null) {
mPagerStateListener.onPagerClosed();
}

}
}

}

// 表示 Header TransLationY 的值是否达到我们指定的阀值, headerOffsetRange,到达了,返回 false,
// 否则,返回 true。注意 TransLationY 是负数。
private boolean canScroll(View child, float pendingDy) {
int pendingTranslationY = (int) (child.getTranslationY() - pendingDy);
int headerOffsetRange = getHeaderOffsetRange();
if (pendingTranslationY >= headerOffsetRange && pendingTranslationY <= 0) {
return true;
}
return false;
}



@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, final View child, MotionEvent
ev) {

boolean closed = isClosed();
Log.i(TAG, "onInterceptTouchEvent: closed=" + closed);
if (ev.getAction() == MotionEvent.ACTION_UP && !closed) {
handleActionUp(parent,child);
}

return super.onInterceptTouchEvent(parent, child, ev);
}

@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target,
int dx, int dy, int[] consumed) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
//dy>0 scroll up;dy<0,scroll down
Log.i(TAG, "onNestedPreScroll: dy=" + dy);
float halfOfDis = dy;
// 不能滑动了,直接给 Header 设置 终值,防止出错
if (!canScroll(child, halfOfDis)) {
child.setTranslationY(halfOfDis > 0 ? getHeaderOffsetRange() : 0);
} else {
child.setTranslationY(child.getTranslationY() - halfOfDis);
}
//consumed all scroll behavior after we started Nested Scrolling
consumed[1] = dy;
}

// 需要注意的是 Header 我们是通过 setTranslationY 来移出屏幕的,所以这个值是负数
private int getHeaderOffsetRange() {
return BaseAPP.getInstance().getResources().getDimensionPixelOffset(R.dimen
.weibo_header_offset);
}

private void handleActionUp(CoordinatorLayout parent, final View child) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "handleActionUp: ");
}
if (mFlingRunnable != null) {
child.removeCallbacks(mFlingRunnable);
mFlingRunnable = null;
}
mFlingRunnable = new FlingRunnable(parent, child);
if (child.getTranslationY() < getHeaderOffsetRange() / 6.0f) {
mFlingRunnable.scrollToClosed(DURATION_SHORT);
} else {
mFlingRunnable.scrollToOpen(DURATION_SHORT);
}

}

private void onFlingFinished(CoordinatorLayout coordinatorLayout, View layout) {
changeState(isClosed(layout) ? STATE_CLOSED : STATE_OPENED);
}

public void openPager() {
openPager(DURATION_LONG);
}

/**
* @param duration open animation duration
*/
public void openPager(int duration) {
View child = mChild.get();
CoordinatorLayout parent = mParent.get();
if (isClosed() && child != null) {
if (mFlingRunnable != null) {
child.removeCallbacks(mFlingRunnable);
mFlingRunnable = null;
}
mFlingRunnable = new FlingRunnable(parent, child);
mFlingRunnable.scrollToOpen(duration);
}
}

public void closePager() {
closePager(DURATION_LONG);
}

/**
* @param duration close animation duration
*/
public void closePager(int duration) {
View child = mChild.get();
CoordinatorLayout parent = mParent.get();
if (!isClosed()) {
if (mFlingRunnable != null) {
child.removeCallbacks(mFlingRunnable);
mFlingRunnable = null;
}
mFlingRunnable = new FlingRunnable(parent, child);
mFlingRunnable.scrollToClosed(duration);
}
}

private FlingRunnable mFlingRunnable;

/**
* For animation , Why not use {@link android.view.ViewPropertyAnimator } to play animation
* is of the
* other {@link CoordinatorLayout.Behavior} that depend on this could not receiving the
* correct result of
* {@link View#getTranslationY()} after animation finished for whatever reason that i don't know
*/
private class FlingRunnable implements Runnable {
private final CoordinatorLayout mParent;
private final View mLayout;

FlingRunnable(CoordinatorLayout parent, View layout) {
mParent = parent;
mLayout = layout;
}

public void scrollToClosed(int duration) {
float curTranslationY = ViewCompat.getTranslationY(mLayout);
float dy = getHeaderOffsetRange() - curTranslationY;
if (BuildConfig.DEBUG) {
Log.d(TAG, "scrollToClosed:offest:" + getHeaderOffsetRange());
Log.d(TAG, "scrollToClosed: cur0:" + curTranslationY + ",end0:" + dy);
Log.d(TAG, "scrollToClosed: cur:" + Math.round(curTranslationY) + ",end:" + Math
.round(dy));
Log.d(TAG, "scrollToClosed: cur1:" + (int) (curTranslationY) + ",end:" + (int) dy);
}
mOverScroller.startScroll(0, Math.round(curTranslationY - 0.1f), 0, Math.round(dy +
0.1f), duration);
start();
}

public void scrollToOpen(int duration) {
float curTranslationY = ViewCompat.getTranslationY(mLayout);
mOverScroller.startScroll(0, (int) curTranslationY, 0, (int) -curTranslationY,
duration);
start();
}

private void start() {
if (mOverScroller.computeScrollOffset()) {
mFlingRunnable = new FlingRunnable(mParent, mLayout);
ViewCompat.postOnAnimation(mLayout, mFlingRunnable);
} else {
onFlingFinished(mParent, mLayout);
}
}

@Override
public void run() {
if (mLayout != null && mOverScroller != null) {
if (mOverScroller.computeScrollOffset()) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "run: " + mOverScroller.getCurrY());
}
ViewCompat.setTranslationY(mLayout, mOverScroller.getCurrY());
ViewCompat.postOnAnimation(mLayout, this);
} else {
onFlingFinished(mParent, mLayout);
}
}
}
}

/**
* callback for HeaderPager 's state
*/
public interface OnPagerStateListener {
/**
* do callback when pager closed
*/
void onPagerClosed();

/**
* do callback when pager opened
*/
void onPagerOpened();
}

}

第二个关键点的实现

在页面状态为 open 的时候,向上滑动 Header 的时候,整体向上偏移。

在第一个关键点的实现上,我们是通过自定义 Behavior 来处理 ViewPager 里面 RecyclerView 的移动的,那我们要怎样监听整个 Header 的滑动了。

那就是重写 LinearLayout,将滑动事件交给 ScrollingParent(这里是CoordinatorLayout) 去处理,CoordinatorLayout 再交给子 View 的 behavior 去处理。

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
public class NestedLinearLayout extends LinearLayout implements NestedScrollingChild {

private static final String TAG = "NestedLinearLayout";

private final int[] offset = new int[2];
private final int[] consumed = new int[2];

private NestedScrollingChildHelper mScrollingChildHelper;
private int lastY;

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

public NestedLinearLayout(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}

public NestedLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initData();
}

private void initData() {
if (mScrollingChildHelper == null) {
mScrollingChildHelper = new NestedScrollingChildHelper(this);
mScrollingChildHelper.setNestedScrollingEnabled(true);
}
}

@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_DOWN:
lastY = (int) event.getRawY();
// 当开始滑动的时候,告诉父view
startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL
| ViewCompat.SCROLL_AXIS_VERTICAL);
break;
case MotionEvent.ACTION_MOVE:

return true;
}
return super.onInterceptTouchEvent(event);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()){
case MotionEvent.ACTION_MOVE:
Log.i(TAG, "onTouchEvent: ACTION_MOVE=");
int y = (int) (event.getRawY());
int dy =lastY- y;
lastY = y;
Log.i(TAG, "onTouchEvent: lastY=" + lastY);
Log.i(TAG, "onTouchEvent: dy=" + dy);
// dy < 0 下拉, dy>0 赏花
if (dy >0) { // 上滑的时候才交给父类去处理
if (startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) // 如果找到了支持嵌套滚动的父类
&& dispatchNestedPreScroll(0, dy, consumed, offset)) {//
// 父类进行了一部分滚动
}
}else{
if (startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) // 如果找到了支持嵌套滚动的父类
&& dispatchNestedScroll(0, 0, 0,dy, offset)) {//
// 父类进行了一部分滚动
}
}
break;
}
return true;
}



private NestedScrollingChildHelper getScrollingChildHelper() {
return mScrollingChildHelper;
}

// 接口实现--------------------------------------------------

@Override
public void setNestedScrollingEnabled(boolean enabled) {
getScrollingChildHelper().setNestedScrollingEnabled(enabled);
}

@Override
public boolean isNestedScrollingEnabled() {
return getScrollingChildHelper().isNestedScrollingEnabled();
}

@Override
public boolean startNestedScroll(int axes) {
return getScrollingChildHelper().startNestedScroll(axes);
}

@Override
public void stopNestedScroll() {
getScrollingChildHelper().stopNestedScroll();
}

@Override
public boolean hasNestedScrollingParent() {
return getScrollingChildHelper().hasNestedScrollingParent();
}

@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed,
int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) {
return getScrollingChildHelper().dispatchNestedScroll(dxConsumed,
dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
}

@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed,
int[] offsetInWindow) {
return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy,
consumed, offsetInWindow);
}

@Override
public boolean dispatchNestedFling(float velocityX, float velocityY,
boolean consumed) {
return getScrollingChildHelper().dispatchNestedFling(velocityX,
velocityY, consumed);
}

@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
return getScrollingChildHelper().dispatchNestedPreFling(velocityX,
velocityY);
}
}


Content 部分的实现

Content 部分的实现也主要有两个关键点

  • 整体置于 Header 之下
  • Content 跟着 Header 移动。即 Header 位置发生变化的时候,Content 也需要随着调整位置。

第一个关键点的实现

整体置于 Header 之下。这个我们可以参考 APPBarLayout 的 behavior,它是这样处理的。

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
/**
* Copy from Android design library
* <p/>
* Created by xujun
*/
public abstract class HeaderScrollingViewBehavior extends ViewOffsetBehavior<View> {
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();

private int mVerticalLayoutGap = 0;
private int mOverlayTop;

public HeaderScrollingViewBehavior() {
}

public HeaderScrollingViewBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
final int childLpHeight = child.getLayoutParams().height;
if (childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT || childLpHeight == ViewGroup.LayoutParams.WRAP_CONTENT) {
// If the menu's height is set to match_parent/wrap_content then measure it
// with the maximum visible height

final List<View> dependencies = parent.getDependencies(child);
final View header = findFirstDependency(dependencies);
if (header != null) {
if (ViewCompat.getFitsSystemWindows(header) && !ViewCompat.getFitsSystemWindows(child)) {
// If the header is fitting system windows then we need to also,
// otherwise we'll get CoL's compatible measuring
ViewCompat.setFitsSystemWindows(child, true);

if (ViewCompat.getFitsSystemWindows(child)) {
// If the set succeeded, trigger a new layout and return true
child.requestLayout();
return true;
}
}

if (ViewCompat.isLaidOut(header)) {
int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec);
if (availableHeight == 0) {
// If the measure spec doesn't specify a size, use the current height
availableHeight = parent.getHeight();
}

final int height = availableHeight - header.getMeasuredHeight() + getScrollRange(header);
final int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(height,
childLpHeight == ViewGroup.LayoutParams.MATCH_PARENT ? View.MeasureSpec.EXACTLY : View.MeasureSpec.AT_MOST);

// Now measure the scrolling view with the correct height
parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed, heightMeasureSpec, heightUsed);

return true;
}
}
}
return false;
}

@Override
protected void layoutChild(final CoordinatorLayout parent, final View child, final int layoutDirection) {
final List<View> dependencies = parent.getDependencies(child);
final View header = findFirstDependency(dependencies);

if (header != null) {
final CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
final Rect available = mTempRect1;
available.set(parent.getPaddingLeft() + lp.leftMargin, header.getBottom() + lp.topMargin,
parent.getWidth() - parent.getPaddingRight() - lp.rightMargin,
parent.getHeight() + header.getBottom() - parent.getPaddingBottom() - lp.bottomMargin);

final Rect out = mTempRect2;
GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(), child.getMeasuredHeight(), available, out, layoutDirection);

final int overlap = getOverlapPixelsForOffset(header);

child.layout(out.left, out.top - overlap, out.right, out.bottom - overlap);
mVerticalLayoutGap = out.top - header.getBottom();
} else {
// If we don't have a dependency, let super handle it
super.layoutChild(parent, child, layoutDirection);
mVerticalLayoutGap = 0;
}
}

float getOverlapRatioForOffset(final View header) {
return 1f;
}

final int getOverlapPixelsForOffset(final View header) {
return mOverlayTop == 0
? 0
: MathUtils.constrain(Math.round(getOverlapRatioForOffset(header) * mOverlayTop),
0, mOverlayTop);

}

private static int resolveGravity(int gravity) {
return gravity == Gravity.NO_GRAVITY ? GravityCompat.START | Gravity.TOP : gravity;
}

protected abstract View findFirstDependency(List<View> views);

protected int getScrollRange(View v) {
return v.getMeasuredHeight();
}

/**
* The gap between the top of the scrolling view and the bottom of the header layout in pixels.
*/
final int getVerticalLayoutGap() {
return mVerticalLayoutGap;
}

/**
* Set the distance that this view should overlap any {@link AppBarLayout}.
*
* @param overlayTop the distance in px
*/
public final void setOverlayTop(int overlayTop) {
mOverlayTop = overlayTop;
}

/**
* Returns the distance that this view should overlap any {@link AppBarLayout}.
*/
public final int getOverlayTop() {
return mOverlayTop;
}

}

这个基类的代码还是很好理解的,因为之前就说过了,正常来说被依赖的 View 会优先于依赖它的 View 处理,所以需要依赖的 View 可以在 measure/layout 的时候,找到依赖的 View 并获取到它的测量/布局的信息,这里的处理就是依靠着这种关系来实现的.

我们的实现类,需要重写的除了抽象方法 findFirstDependency 外,还需要重写 getScrollRange,我们把 Header
的 Id id_weibo_header 定义在 ids.xml 资源文件内,方便依赖的判断.

至于缩放的高度,根据 结果图 得知是 0,得出如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private int getFinalHeight() {
Resources resources = BaseAPP.getInstance().getResources();

return 0;
}

@Override
protected int getScrollRange(View v) {
if (isDependOn(v)) {
return Math.max(0, v.getMeasuredHeight() - getFinalHeight());
} else {
return super.getScrollRange(v);
}
}

第二个关键点的实现:

Content 跟着 Header 移动。即 Header 位置发生变化的时候,Content 也需要随着调整位置。

主要的逻辑就是 在 layoutDependsOn 方法里面,判断 dependcy 是不是 HeaderView ,是的话,返回TRUE,这样在 Header 位置发生变化的时候,会回调 onDependentViewChanged 方法,在该方法里面,做相应的偏移。TranslationY 是根据比例算出来的 translationY = (int) (-dependencyTranslationY / (getHeaderOffsetRange() * 1.0f) * getScrollRange(dependency));

完整代码如下:

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
public class WeiboContentBehavior extends HeaderScrollingViewBehavior {
private static final String TAG = "WeiboContentBehavior";

public WeiboContentBehavior() {
}

public WeiboContentBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}

@Override
public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
return isDependOn(dependency);
}

@Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
if (BuildConfig.DEBUG) {
Log.d(TAG, "onDependentViewChanged");
}
offsetChildAsNeeded(parent, child, dependency);
return false;
}

private void offsetChildAsNeeded(CoordinatorLayout parent, View child, View dependency) {
float dependencyTranslationY = dependency.getTranslationY();
int translationY = (int) (-dependencyTranslationY / (getHeaderOffsetRange() * 1.0f) *
getScrollRange(dependency));
Log.i(TAG, "offsetChildAsNeeded: translationY=" + translationY);
child.setTranslationY(translationY);

}

@Override
protected View findFirstDependency(List<View> views) {
for (int i = 0, z = views.size(); i < z; i++) {
View view = views.get(i);
if (isDependOn(view)) return view;
}
return null;
}

@Override
protected int getScrollRange(View v) {
if (isDependOn(v)) {
return Math.max(0, v.getMeasuredHeight() - getFinalHeight());
} else {
return super.getScrollRange(v);
}
}

private int getHeaderOffsetRange() {
return BaseAPP.getInstance().getResources().getDimensionPixelOffset(R.dimen
.weibo_header_offset);
}

private int getFinalHeight() {
Resources resources = BaseAPP.getInstance().getResources();

return 0;
}

private boolean isDependOn(View dependency) {
return dependency != null && dependency.getId() == R.id.id_weibo_header;
}
}


题外话

  • NestedScrolling 机制,对比传统的事件分发机制真的很强大。这种仿新浪微博发现页效果, 如果用传统的事件分发机制来做,估计很难实现,处理起来会有一大堆坑。
  • 看完了这种仿新浪微博发现页的效果,你是不是学到了什么?如果让你 模仿 仿 QQ 浏览器首页效果,你能实现话。

最后,特别感谢写这篇博客 自定义Behavior的艺术探索-仿UC浏览器主页 的开发者,没有这篇博客作为参考,这种效果我很大几率是实现 不了的。大家觉得效果还不错的话,顺手到 github 上面给我 star,谢谢。github 地址


参考文章:

自定义Behavior的艺术探索-仿UC浏览器主页

github 地址

最后的最后,卖一下广告,欢迎大家关注我的微信公众号 徐公码字,扫一扫下方二维码或搜索微信号 stormjun94,即可关注。 目前专注于 Android 开发,主要分享 Android开发相关知识和一些相关的优秀文章,包括个人总结,职场经验等。

文章首发地址CSDN:

岁月如水,时间飞逝,转眼间,已经到了年尾,即将引来新的一年,我要赶紧抓住16年的尾巴,写篇文章记录一下我16年的点点滴滴。篇章大概如下,学习&工作室篇,实习篇,盛夏六月, 博客篇,秋招篇,情感篇,展望未来。

学习&工作室篇

春节弹指一瞬间,转眼间已经到了正月18,迷恋着春节家人朋友团聚时候喜悦的气氛,我依依不舍地乘着大巴回到了学校,开始新的学期。在大学的时光里,没有想高三时光一样,三点一线。在大学里,多的是自由。每天,我往返于工作室和宿舍之间,那时每天只想着能争取多点时间 学习自己感兴趣的东西————也就是我现在所从事的职业Android开发。每天为了挤出一个多小时的时间来学习,尝试过中午不睡觉,坚持了两个多星期。结果是中午不睡,晚上崩溃———下午不睡觉,下午学习的时候还是精神蓬勃的,到了晚上,睡意就来了,经常打瞌睡。结果呢,相信你也猜到了,学习的东西反而少了,效率下降了好多。

原因,浅而明显,第一,一个人的精力是有限的,我们要注意劳逸结合;第二,以前我都是有午睡的习惯的,突然改变了习惯,肯定要有一段适应期。

至于说到劳逸结合,高中的时候就深有体会,大学写编程的时候更是深深刻在心里。有时候,写编程,在调bug的时候,在哪里捣鼓了几个小时,终究是被它折服了,被它弄得心浮气躁。这时候不烦放下手头的工作,出去走走,感受一下大自然,放松一下头脑,接着回来工作,许多时候你会发现bug一下子就解决了。这个时候你通常我会感慨,我擦,我是一个傻逼,这么简单的问题竟然弄了这么久,心里头不禁也涌上来一股满足感————那是一种付出辛苦努力而得到的满足。

有许多人说,写编程会让一个人性格变得烦躁。哈哈,有时候确实会,不过,有时候我更想说的是,写编程往往是我们变得更加耐心和细心。每一次我们在跟bug作斗争的时候,我们的耐心正在一点点培养。

许多人说程序员活像闷葫芦,钱多话少死得早。怎么说呢,这句话还是有一点道理的,首先钱多呢,这个就不必详讲了,相对大多数打工族来说,程序猿的工资相对来是还是比较高的。话少呢,确实也有一定的道理,因为我们整天面对的是电脑,比较少与人沟通交流,久而久之,语言表达能力肯定会退化不少的,有时候在与人交谈中,也不知道谁聊什么话题好,这就给了大家一种印象——话少。至于“死得早”,我们知道程序猿加班相对比较多,尤其是项目要上线的时候,经常会加班,而且工作强度相对来说有比较强。确实,如果你不注意锻炼的话,真的对身体伤害很大的。但只要你注意一下,每个星期坚持两三次锻炼,也是照样精神饱满的。

实习篇

说起实习的那段时间,那真的是一段艰辛岁月。每天实习完回到宿舍,有时候身心俱疲,根本就提不起精神来继续学习,我也因此颓废了一段时间,每天回到宿舍后,就开始看电影,看电视剧——后面我调整了自己的状态,在实习完回来的时候继续学习。

如果你问我那段时间累不累?我可以很肯定地告诉你,累成狗。但是我从未后悔过,因为一段岁月过得很充实,正如我们高三备考的那段岁月——只为心中的那一个目标。

盛夏六月

每天的五六月份,都是我们学校的毕业季。送走了一拨人,又即将引来新的一拨人,注入新的血液。在这段时间,对我感触最深的是,应该是我二哥和我社团的几个师兄和师姐牌毕业照的时候,他们说大学时光飞快,要好好珍惜接下来的大学时光。

有时候也在想,一年后的自己会是怎样的呢?

真的很感谢他们,曾今他们在我大学最迷茫的时候引导了我,为我指点迷津。

博客篇

我正式写博客的时候应该是四月底五月初的时候,像大多数人一样,刚开始写博客的时候完全没有思路,写出来的文章条理性差,访问量也很少。我记忆比较深的一篇博客是我在写这篇博客的时候:
二分查找的相关算法题 ,那时候些博客写到深夜12点多,就发布了出去,第二天醒来,访问量竟然超过一千了。一千的访问量对于经常写博客或者有一定知名度的博客来说,根本就是小菜一碟,算不了什么。但对于我之前几篇博客都是几十最多一百多的访问量的人来说,这无疑是意义非凡的。这意味着对我这篇博客质量的认可,正如我们付出的努力得到认可的喜悦一样。

为什么写博客呢

(当然我不是在说我写博客有多了不起,我只是在分享一下自己的经历而已———— 一些人或许会这样想,坚持写博客有什么了不起的,网上一大堆人在写博客,怎么不见他们在说写博客辛苦,或者吹捧。有一些腹黑的人更恶劣的,甚至会骂你,谴责你,不懂得谦虚,骄傲自大。

对于第一类人,分情况讨论一下,如果是那种整天无所事事的人,那我会嗤之以鼻,如果是那种很努力的人,整天辛苦奋斗的人,那我们写博客确实算不了什么。

而对于那些动不动就站在道德制高点的那些人,我真的不知道说写什么说。这种人我们真的不必跟他们太较真,较真你就输了。

总之,说了这么久,只想表达这样的意思,坚持走自己认为正确的路,世界那么大,让别人说去吧。

这里分享一个故事,是几天前发生的,是Android开发者 StormhZhang 的故事,故事概要是这样的,StromZhang 在他的公众号推送了一篇广告,结果有一些人就说他作为一名技术总监,还发广告,差不差这点钱,进而有提升到道德方面,有一些人更过分,甚至流言谩骂。殊不知他做技术分享帮助了多少人?这里就不过多介绍了,欲知详情,请自行搜索。

哈哈,扯蛋了这么久,终于来说我能够坚持下来写博客的原因呢?原因其实很简单,对于经常写博客的人,我相信他们都有一个共同点,写着写着就爱上博客了。即使说没有写博客,也喜欢用笔记将自己认为有价值的东西记录下来,just so
simple。当然,写博客有几个好处,锻炼自己的写作能力,提高自己的思维,更难能可贵的是,你能够在写博客的时候遇到一些志同道合的朋友。目前我还没有遇到,期待img,说一下我的一个经历,之前有一个技术疑问一直解决不了,后面在写相关博客的时候,在博客的最后提了出来,后面有热心的网友帮忙解答了,那时候真的很感动。

秋招篇

说起我的秋招之旅,可能对于身边的人来说,我相对是比较轻松的。可是对于许多大神来说,差的还不是一截半截。记得我经常说过一句话,比上不足,比下有余。

八月中旬的时候,秋招的号角正式吹响了。刚开始是一些BAT之类的公司内推,筛选简历或者笔试,很遗憾,我全部都没有通过。九月中旬的时候,BAT,网易,CVTE等这些知名企业开始校招了,很遗憾笔试也是全部没有通过。一方面是今年校招缩水了,招的人很少,一方面可能自己的笔试成绩也不是十分突出。那时候,心底是有点慌的,因为校招开始了半个多月,竟然一个笔试都没有通过,面试也没有。

于是,我自己独自一人来到腾讯面试的地方——喜来登大酒店,想去霸面。刚开始,想趁着他们在面试的时候跟着他们上去,可是还是被挡在电梯外面了。于是就去霸面区交了简历,后面想“趁水摸鱼” 坐上电梯,直接去找面试官,跟他说想霸面。可是还是被挡在第一外面了。于是就没有继续找机会坐上电梯去了。结果的最后,就是在里面空坐了一天,霸面fail,一天就这样 get over。其实,那时候如果真的下定决心要上去的话,机会还是很大的,等到有房可上去的时候,跟他们一起上去就好了。之所以当时没有那样做,可能自己还没有足够的信心。可是去之前是信心满满要去霸面的,可到现场遇到一点小阻碍却退却了,也许这就是我性格的一个弱点吧。

到了九月底的时候,也开始面试了,陆续收到了美图,久邦数码,步步高等公司的offer,最终签了美图公司,在十月初的时候也结束了我的秋招之旅。

在秋招,对于面试,我也没有一些很好的技巧。对于技术岗位的,我只能说三分口才,七分实力。对于搞技术的人,千万不要忽略语言表达能力方面的培养与提高,一方面在面试的时候你会吃很大亏,另一方面对你以后人生的发展也是很不利的。我在表达这方面就吃过挺多亏的,现在表达能力还是有待提高。

情感篇

说了这么久,来稍微说一点轻松一点的东西呢?那就是说情感方面的呢,其实我的情感篇真的没什么可说的,大学到现在也没谈过恋爱,可能是一直没有遇到合适的人吧,或者是我的情商有点低吧。谁说得清楚呢?

至于亲情方面,我想说的是,有空就多回家看看吧。对于父母来说,子女经常回家就是最好的礼物呢,比得多钱财万贯。

展望未来

旧的一年即将过去了,新的一年即将到来。在新的一年,大概有以下计划

  1. 在毕业前来一次说走你就走的旅行(不过到时候实习不知道有没有时间,尽量争取吧)
  2. CSDN争取申请到博客专家号,现在是准博客专家

截张图记录一下我现在博客的访问量

17年,即将到来的新的一年,希望家人朋友身体健康,实习,工作顺利。最后的最后,为了青春和热血,再次拼搏加油,致我的青春,青春万岁。

文章首发地址CSDN: