原生 Android 项目集成 React Native

创建一个 React Native 项目并写一个纯的 React Native 应用可以参考官方指南

iOS 项目集成 React Native 可以参考 原生 iOS 项目集成 React Native

本文主要介绍原生 Android 项目集成 React Native 并用于部分页面开发的流程。开发环境为 macOS 10.12、Android Studio 2.2.1、React Native 0.35.0。而官方给出的 植入原生 Android 应用指南 只对应到 0.28 版本。最新版(当前为 0.35)的集成方案稍微有些变动。

1. 创建/修改 Android 项目

用 Android Studio 创建一个 Android 项目,注意 Minimum SDK 要设置为 API 16 或以上,因为 React Native 要求 Android 4.1 及以上的环境。

如果现有 Android 项目且 Minimum API 小于16则修改 Minimum SDK 到16,注意部分 API 变化。

2. 添加 package.json

在 Android 项目根目录新建文件 package.json,内容如下(参考 react-native init 生成的 package.json 文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"name": "react-native-sample",
"version": "0.0.1",
"description": "sample of react native embedding android",
"main": "index.android.js",
"private": true,
"scripts": {
"start": "node node_modules/react-native/local-cli/cli.js start"
},
"author": "danke77",
"license": "ISC",
"dependencies": {
"react": "^15.3.2",
"react-native": "^0.35.0"
},
"devDependencies": {
}
}

执行 npm install 就可以安装 dependencies 下的 npm 组件了。

这个时候在 Android 项目根目录就生成了 node_modules/ 文件夹,里面就是一些用到的组件。

.gitignore 中添加

1
2
3
# node.js
# node_modules/
npm-debug.log

执行 react-native upgrade 可以更新已有组件。

3. 添加 index.android.js

在 Android 项目根目录创建目录 js/,js 相关的代码就放在这个文件夹下。

js/ 下添加 App.js,内容如下

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
import React, { Component } from 'react'
import { View, Text, StyleSheet } from 'react-native'
export default class extends Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.text}>
Hello React Native!
</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#ffffff'
},
text: {
fontSize: 20,
color: '#333333'
}
})

在 Android 项目根目录新建文件 index.android.js,内容如下

1
2
3
4
import { AppRegistry } from 'react-native'
import App from './js/App'
AppRegistry.registerComponent('navigation', () => App)

这里的 navigation 一般会根据模块功能命名,后面还会用到。

当然也可以把 App.js 的内容写在 index.android.js 里,但这样写更清晰一些,尤其是项目大了文件多的情况。

4. Android 项目添加依赖

4.1 project 级别的 build.gralde

Android 默认的依赖包的源 jcenter() 不包含最新版的 React Native,新版的 React Native 都只在 npm 里发布,因此要依赖 node_modules/ 下的东西。

在 Android 项目根目录下的 build.gradle 文件添加如下内容

1
2
3
4
5
6
7
8
allprojects {
repositories {
maven {
// All of React Native (JS, Android binaries) is installed from npm
url "$rootDir/node_modules/react-native/android"
}
}
}

4.2 module 级别的 build.gradle

在 Android 项目 app 目录下的 build.gradle 文件添加如下内容

1
2
3
4
5
6
7
8
9
defaultConfig {
// ...
ndk {
abiFilters "armeabi-v7a", "x86"
}
}
// react native
compile 'com.facebook.react:react-native:0.35.0' // From node_modules

这里版本号要和 package.json 里的一致。

5. React Native 相关的 Activity 和 Application

5.1 Activity

创建一个继承自 com.facebook.react.ReactActivity 的 Activity

1
2
3
4
5
6
7
8
9
10
11
public class HelloReactActivity extends ReactActivity {
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
*/
@Override
protected String getMainComponentName() {
return "navigation";
}
}

重写 getMainComponentName() 方法,返回的字符串必须和前面的 AppRegistry.registerComponent('navigation', () => App) 里的 navigation 对应,表示该 Activity 会显示对应组件里的内容。

5.2 Application

Application 需要实现 com.facebook.react.ReactApplication 接口,并实现其方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
protected boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new OtherReactPackage()
// ...
);
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}

getUseDeveloperSupport() 表示是否启动开发者模式。

getPackages() 是引用的模块列表,默认需要添加 MainReactPackage,如果需要在 js 里调用原生 Java 模块,需要添加自定义的模块(如 OtherReactPackage)。

新版这两个方法是写在 Application 里的,旧版都是些在 Activity 里的。

5.3 AndroidManifest.xml

AndroidManifest.xml 里需要添加自己创建的 Activity 和 React Native 提供的 DevSettingsActivity,还需要添加两个权限。

1
2
3
4
5
6
7
8
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application ... >
...
<activity android:name=".HelloReactActivity" />
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
</application>

HelloReactActivity 是自己创建的页面,DevSettingsActivity 是开发调试需要用到的设置页面。

android.permission.INTERNET 用于开发调试,android.permission.SYSTEM_ALERT_WINDOW 用于显示悬浮窗。

6. 启动服务

debug 模式下需要在 package.json 所在目录下执行 npm start,它等效于 package.json 里的 node node_modules/react-native/local-cli/cli.js start,相当于启动一个本地服务。

Terminal 显示如下表示服务已正常启动

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
> react-native-module@0.0.1 start /Users/danke77/Projects/react-native/HelloReactNative
> node node_modules/react-native/local-cli/cli.js start
Scanning 581 folders for symlinks in /Users/danke77/Projects/react-native/HelloReactNative/node_modules (17ms)
┌────────────────────────────────────────────────────────────────────────────┐
│ Running packager on port 8081. │
│ │
│ Keep this packager running while developing on any JS projects. Feel │
│ free to close this tab and run your own packager instance if you │
│ prefer. │
│ │
│ https://github.com/facebook/react-native │
│ │
└────────────────────────────────────────────────────────────────────────────┘
Looking for JS files in
/Users/danke77/Projects/react-native/HelloReactNative
[2016-10-17 17:06:48] <START> Building Dependency Graph
[2016-10-17 17:06:48] <START> Crawling File System
[Hot Module Replacement] Server listening on /hot
React packager ready.
[2016-10-17 17:06:49] <END> Crawling File System (966ms)
[2016-10-17 17:06:49] <START> Building in-memory fs for JavaScript
[2016-10-17 17:06:49] <END> Building in-memory fs for JavaScript (260ms)
[2016-10-17 17:06:49] <START> Building in-memory fs for Assets
[2016-10-17 17:06:50] <END> Building in-memory fs for Assets (138ms)
[2016-10-17 17:06:50] <START> Building Haste Map
[2016-10-17 17:06:50] <START> Building (deprecated) Asset Map
[2016-10-17 17:06:50] <END> Building (deprecated) Asset Map (104ms)
[2016-10-17 17:06:50] <END> Building Haste Map (428ms)
[2016-10-17 17:06:50] <END> Building Dependency Graph (1825ms)

7. 开发调试

构建 Android 项目,打开应用,切换到 HelloReactActivity 页面,通过摇一摇开启调试菜单,选择 Dev Settings 进入 DevSettingsActivity

Dev Settings

设置 Debug server host & port for device 为本机 IP 地址,添加端口号

Debug server host & port for device

返回到 HelloReactActivity 页面,摇一摇选择 Reload,接下来就可以开始调试了。

每次修改 js 代码只需 Reload 即可,无需重新构建整个 Android 项目,修改 Java 代码需要重新构建。

8. 发布正式包

8.1 js bundle

React Native 的开发版需要有启动一个本地服务随时发送更新后的 js bundle 文件。如果要打正式包,需要把 js bundle 文件保存到 Android 项目的 assets/ 目录下。这样,正式包就不需要本地服务支持了。可参考官方文档

app/src/main/ 下创建 assets/ 文件夹,执行以下命令将 js bundle 保存到资源目录下

1
$ react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output app/src/main/assets/index.android.bundle --assets-dest app/src/main/res/

app/src/main/assets/ 下就会生成 index.android.bundle 文件。

开发模式调试的时候 js 代码会立即生效,无需执行以上命令,但每次正式打包的时候如果改了 js 代码都必须先执行以上命令。

8.2 Proguard

集成 React Native 之后如果不加相关的混淆规则,打 release 包的时候就会报错。

参考 官方例子的混淆文件

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
# React Native
# Keep our interfaces so they can be used by other ProGuard rules.
# See http://sourceforge.net/p/proguard/bugs/466/
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
# Do not strip any method/class that is annotated with @DoNotStrip
-keep @com.facebook.proguard.annotations.DoNotStrip class *
-keep @com.facebook.common.internal.DoNotStrip class *
-keepclassmembers class * {
@com.facebook.proguard.annotations.DoNotStrip *;
@com.facebook.common.internal.DoNotStrip *;
}
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
void set*(***);
*** get*();
}
-keep class com.facebook.react.** {*; }
-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
-keep class * extends com.facebook.react.bridge.NativeModule { *; }
-keepclassmembers,includedescriptorclasses class * { native <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.UIProp <fields>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; }
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
-dontwarn com.facebook.react.**
# okhttp
-keepattributes Signature
-keepattributes *Annotation*
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn okhttp3.**
# okio
-keep class sun.misc.Unsafe { *; }
-dontwarn java.nio.file.*
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-dontwarn okio.**

完成以上两步后就可以通过 ./gradlew assembleRelease 打正式包了。

本文是 慌不要慌 原创,发表于 https://danke77.github.io/,请阅读原文支持原创 https://danke77.github.io/2016/10/17/react-native-embedding-android/,版权归作者所有,转载请注明出处。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!



Node 开发环境搭建和基本使用

1. 搭建 Node 开发环境

我们需要安装和管理多个 Node 版本,可使用的 Node 版本管理工具有 nnvm,选择一个即可,不要两个同时使用。

安装完成 Node 版本管理工具后,即可安装和使用 Node

2. npm

npm 用于自动管理包的依赖

2.1 初始化一个 Node 项目

1
$ npm init

会生成 package.json 文件,它定义了项目的各种元信息及项目的依赖,因此项目在部署时,不必将 node_modules 目录上传到服务器,把 node_modules 加到 .gitignore 即可,只需执行

1
$ npm install

则 npm 会自动读取 package.json 中的依赖并安装在项目的 node_modules

2.2 安装 PACKAGE

不将依赖写入 package.json

1
$ npm install PACKAGE_NAME

将依赖写入 package.json

1
$ npm install PACKAGE_NAME --save

可同时安装多个 PACKAGE

1
$ npm install PACKAGE_NAME PACKAGE_NAME2 --save

默认从 npm 官方安装,如果指定镜像可加参数 --registry=https://registry.npm.taobao.org

1
$ npm install PACKAGE_NAME --registry=https://registry.npm.taobao.org --save

2.3 执行某个 js 文件

1
$ node app.js

本文是 慌不要慌 原创,发表于 https://danke77.github.io/,请阅读原文支持原创 https://danke77.github.io/2016/08/21/node-develop-environment/,版权归作者所有,转载请注明出处。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!



基于 Hexo 主题搭建博客

1. 开发环境

  1. 安装 node
  2. 安装 git
  3. 注册并配置 Github

2. Hexo Configuration

官网 有详细的介绍,步骤如下

2.1 安装

1
npm install hexo-cli -g

2.2 初始化

执行 init 命令初始化 hexo 到指定目录

1
hexo init <folder>

或在指定目录下,执行

1
hexo init

2.3 生成静态页面

init 目录下,执行如下命令,生成静态页面到 hexo/public/ 目录

1
hexo generate

可简写成

1
hexo g

2.4 本地启动

1
hexo server

可简写成

1
hexo s

启动本地服务,进行预览,根据提示在浏览器输入 http://0.0.0.0:4000/ 即可看到效果。

2.5 部署

1
hexo deploy

可简写成

1
hexo d

可与 hexo g 合并为

1
hexo d -g

部署前执行 hexo clean

2.6 新建文章

执行 new 命令,生成指定名称的文章到 hexo/source/_posts/postName.md

1
hexo new [layout] "postName"

可简写成

1
hexo n [layout] "postName"

layout 是可选参数,默认为 post

scaffolds 目录下可以查看并修改 layout,也可以自定义新的 layout

注意:所有文件的 : 后面都必须有一个空格

2.7 新建页面

1
hexo new page "pageName"

3. Hexo Themes

init 的目录下执行以下命令即可安装对应的主题

1
git clone https://github.com/iissnan/hexo-theme-light.git themes/light

安装完成后,在全局配置文件 _config.yml 修改主题为安装的主题即可

1
theme: light

修改 hexo/themes/light/_config.yml 即可编辑主题。

更新主题

1
2
cd themes/light
git pull

本文是 慌不要慌 原创,发表于 https://danke77.github.io/,请阅读原文支持原创 https://danke77.github.io/2016/08/10/hexo-your-blog/,版权归作者所有,转载请注明出处。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!



Retrofit 源码分析

最近非常流行 Retrofit+RxJava+OkHttp 这一整套的网络请求和异步操作的开源框架,从 Jake Wharton 的 Github 主页也能看出其趋势。

Retrofit+RxJava+OkHttp

本文主要介绍 Retrofit 的基本原理,基于 2.1.0 版本的源码。

1. 基本用法

1.1 定义 API 接口

Retrofit 的使用非常简单,先来看一个 官网 上的示例代码。

1
2
3
4
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}

官方的解释是

Retrofit turns your HTTP API into a Java interface.

首先定义了一个 API 接口 GitHubService,包含 HTTP 请求的方法 GET 和参数 user,及成功后的返回类型 List<Repo>,方法和参数由注解声明,非常清晰。

1.2 创建 Retrofit 对象并生成 API 实例

1
2
3
4
5
6
Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
.build();
GitHubService service = retrofit.create(GitHubService.class);

然后创建一个 Retrofit 对象,这里采用 Builder 模式,传入 baseUrlConverterFactory 等参数,后面会讲到。

通过 Retrofit 对象用动态代理的方式生成我们需要的 API 实例 GitHubService

1.3 API 实例去请求服务

1
Call<List<Repo>> repoCall = service.listRepos("danke77");

用生成的 API 实例调用相应的方法,默认返回 Call<T>,然后调用 Call#execute 方法同步或调用 Call#enqueue 方法异步请求 HTTP。

1
List<Repo> repos = repoCall.execute().body();

请求返回的数据直接转化成了 List<Repo>,非常方便。

如果要使用 RxJava,在创建 Retrofit 对象时要调用 addCallAdapterFactory(RxJavaCallAdapterFactory.create()),则返回类型会从 Call<T> 转换成 Observable<T>

2. Retrofit

2.1 build

先看一下 Retrofit.Builder 类里的成员变量。

1
2
3
4
5
6
7
private Platform platform;
private okhttp3.Call.Factory callFactory;
private HttpUrl baseUrl;
private List<Converter.Factory> converterFactories = new ArrayList<>();
private List<CallAdapter.Factory> adapterFactories = new ArrayList<>();
private Executor callbackExecutor;
private boolean validateEagerly;

Platform 提供了3个平台:Java8AndroidIOS,全都继承自 Platform,初始化时静态方法 Platform#findPlatform 会自动识别属于哪一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static Platform findPlatform() {
try {
Class.forName("android.os.Build");
if (Build.VERSION.SDK_INT != 0) {
return new Android();
}
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName("java.util.Optional");
return new Java8();
} catch (ClassNotFoundException ignored) {
}
try {
Class.forName("org.robovm.apple.foundation.NSObject");
return new IOS();
} catch (ClassNotFoundException ignored) {
}
return new Platform();
}

通过 Retrofit.Builder#clientRetrofit.Builder#callFactory 可以自定义 OkHttpClient

1
2
3
4
5
6
7
8
public Builder client(OkHttpClient client) {
return callFactory(checkNotNull(client, "client == null"));
}
public Builder callFactory(okhttp3.Call.Factory factory) {
this.callFactory = checkNotNull(factory, "factory == null");
return this;
}

如果不指定 callFactory,则默认使用 OkHttpClient

1
2
3
4
okhttp3.Call.Factory callFactory = this.callFactory;
if (callFactory == null) {
callFactory = new OkHttpClient();
}

converterFactoriesadapterFactories 提供了2个工厂列表,用于用户自定义数据转换和类型转换,后面会详细说明。

最后调用 Retrofit.Builder#build 创建一个 Retrofit 对象。

1
2
3
4
5
6
public Retrofit build() {
// ... configured values
return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories,
callbackExecutor, validateEagerly);
}

2.2 create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public <T> T create(final Class<T> service) {
Utils.validateServiceInterface(service);
if (validateEagerly) {
eagerlyValidateMethods(service);
}
return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() {
private final Platform platform = Platform.get();
@Override public Object invoke(Object proxy, Method method, Object... args)
throws Throwable {
// If the method is a method from Object then defer to normal invocation.
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, args);
}
if (platform.isDefaultMethod(method)) {
return platform.invokeDefaultMethod(method, service, proxy, args);
}
ServiceMethod serviceMethod = loadServiceMethod(method);
OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args);
return serviceMethod.callAdapter.adapt(okHttpCall);
}
});
}

首先调用 Utils.validateServiceInterface 去判断 service 是否是一个 Interface 且没有继承其他 Interface,否则抛非法参数异常。

1
2
3
4
5
6
7
8
static <T> void validateServiceInterface(Class<T> service) {
if (!service.isInterface()) {
throw new IllegalArgumentException("API declarations must be interfaces.");
}
if (service.getInterfaces().length > 0) {
throw new IllegalArgumentException("API interfaces must not extend other interfaces.");
}
}

如果 validateEagerlytrue,则会调用 eagerlyValidateMethods 方法,会去预加载 service 中的所有方法,默认为 false

1
2
3
4
5
6
7
8
private void eagerlyValidateMethods(Class<?> service) {
Platform platform = Platform.get();
for (Method method : service.getDeclaredMethods()) {
if (!platform.isDefaultMethod(method)) {
loadServiceMethod(method);
}
}
}

然后就是通过动态代理生成 service。前两个 if 分支分别判断是否是 Object 的方法及 default 方法,后者除了 Java8 其他都是 false

再看 loadServiceMethod

1
2
3
4
5
6
7
8
9
10
11
ServiceMethod loadServiceMethod(Method method) {
ServiceMethod result;
synchronized (serviceMethodCache) {
result = serviceMethodCache.get(method);
if (result == null) {
result = new ServiceMethod.Builder(this, method).build();
serviceMethodCache.put(method, result);
}
}
return result;
}

通过 serviceMethodCache 实现缓存机制,同一个 service 的同一个方法只会创建一次,生成 ServiceMethod 同时存入 Cache

ServiceMethod 和需要的参数生成一个 OkHttpCall 对象。然后用 CallAdapter 将生成的 OkHttpCall 转换为我们需要的返回类型,这个后面会说到。

3. ServiceMethod

Adapts an invocation of an interface method into an HTTP call.

ServiceMethod 的作用就是把一个 API 方法转换为一个 HTTP 调用。

Retrofit#loadServiceMethod 方法中可以看出一个 API 方法对应一个 ServiceMethod

GitHubService为例

1
2
3
4
public interface GitHubService {
@GET("users/{user}/repos")
Call<List<Repo>> listRepos(@Path("user") String user);
}

@GET("users/{user}/repos")MethodAnnotations@Path("user") String userParameterAnnotationsCall<List<Repo>>CallAdapter

3.1 ServiceMethod.Builder

先看下 ServiceMethod.Builder 的构造函数

1
2
3
4
5
6
7
public Builder(Retrofit retrofit, Method method) {
this.retrofit = retrofit;
this.method = method;
this.methodAnnotations = method.getAnnotations();
this.parameterTypes = method.getGenericParameterTypes();
this.parameterAnnotationsArray = method.getParameterAnnotations();
}

传入 retrofitmethod 对象,通过 method 获取到方法注解 methodAnnotations、参数类型 parameterTypes 和参数注解 parameterAnnotationsArrayparameterAnnotationsArray是一个二维数组 Annotation[][],因为一个参数可以有多个注解。

关键看 ServiceMethod.Builder#build 方法,会生成一个 ServiceMethod 对象,接下来按顺序解析。

3.2 createCallAdapter

callAdapter = createCallAdapter() 会遍历 adapterFactories,通过 API 方法的 annotationsreturnType 取到第一个符合条件的 CallAdapter

1
2
3
4
5
6
for (int i = start, count = adapterFactories.size(); i < count; i++) {
CallAdapter<?> adapter = adapterFactories.get(i).get(returnType, annotations, this);
if (adapter != null) {
return adapter;
}
}

然后取到 responseType = callAdapter.responseType() 并进行判断,如果是 retrofit2.Response<T>okhttp3.Response 则抛异常,目前支持的有默认的 retrofit2.Call<T>,RxJava 的 rx.Observable,Java8 的 java.util.concurrent.CompletableFuture 和 Guava 的 com.google.common.util.concurrent.ListenableFuture

3.3 createResponseConverter

responseConverter = createResponseConverter() 会遍历 converterFactories,通过 API 方法的 annotationsresponseType 取到第一个符合条件的 Converter<ResponseBody, T>

1
2
3
4
5
6
7
for (int i = start, count = converterFactories.size(); i < count; i++) {
Converter<ResponseBody, ?> converter =
converterFactories.get(i).responseBodyConverter(type, annotations, this);
if (converter != null) {
return (Converter<ResponseBody, T>) converter;
}
}

3.4 parseMethodAnnotation

1
2
3
for (Annotation annotation : methodAnnotations) {
parseMethodAnnotation(annotation);
}

遍历 API 方法的所有 annotations,如实例中的 @GET("users/{user}/repos"),根据所属类型和值解析成 HTTP 请求需要的数据。

在 package retrofit2.http 下包含了所有的 Method AnnotationParameter Anootation

DELETEGETHEADPATHCPOSTPUTOPTIONSHTTP 类型的 annotaion 会调用 parseHttpMethodAndPath,生成 httpMethodhasBodyrelativeUrlrelativeUrlParamNames

retrofit2.http.Headers 类型的 annotaion 会调用 parseHeaders,生成 headers

MultipartFormUrlEncoded 类型的 annotaion 分别生成 isMultipartisFormEncoded,两者互斥,不能同时为 true,否则会抛异常。

然后对生成的部分参数检查,httpMethod 不能为空,如果 hasBodyfalse,则 isMultipartisFormEncoded 必须也为 false,否则会抛异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (httpMethod == null) {
throw methodError("HTTP method annotation is required (e.g., @GET, @POST, etc.).");
}
if (!hasBody) {
if (isMultipart) {
throw methodError(
"Multipart can only be specified on HTTP methods with request body (e.g., @POST).");
}
if (isFormEncoded) {
throw methodError("FormUrlEncoded can only be specified on HTTP methods with "
+ "request body (e.g., @POST).");
}
}

3.5 parseParameterAnnotation

1
2
3
4
5
6
7
int parameterCount = parameterAnnotationsArray.length;
parameterHandlers = new ParameterHandler<?>[parameterCount];
for (int p = 0; p < parameterCount; p++) {
...
Annotation[] parameterAnnotations = parameterAnnotationsArray[p];
parameterHandlers[p] = parseParameter(p, parameterType, parameterAnnotations);
}

遍历每个参数的 parameterAnnotations,如实例中的 @Path("user") String user,根据所属类型和值解析成对应的 ParameterHandler,每个 Parameter Anootation 类型都有对应的 ParameterHandler,且每个参数只能有一个 ParameterHandler

可以看下 parseParameter 的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private ParameterHandler<?> parseParameter(
int p, Type parameterType, Annotation[] annotations) {
ParameterHandler<?> result = null;
for (Annotation annotation : annotations) {
ParameterHandler<?> annotationAction = parseParameterAnnotation(
p, parameterType, annotations, annotation);
if (annotationAction == null) {
continue;
}
if (result != null) {
throw parameterError(p, "Multiple Retrofit annotations found, only one allowed.");
}
result = annotationAction;
}
if (result == null) {
throw parameterError(p, "No Retrofit annotation found.");
}
return result;
}

关键在 parseParameterAnnotation,根据 annotation 的类型并做参数校验,会生成不同的 ParameterHandler,如 RelativeUrl

1
2
3
4
5
6
7
8
if (type == HttpUrl.class
|| type == String.class
|| type == URI.class
|| (type instanceof Class && "android.net.Uri".equals(((Class<?>) type).getName()))) {
return new ParameterHandler.RelativeUrl();
} else {
throw parameterError(p, "@Url must be okhttp3.HttpUrl, String, java.net.URI, or android.net.Uri type.");
}

由于 Retrofit 不依赖 Android SDK,判断 type 时无法获取到 android.net.Uri.class,因此采用了 "android.net.Uri".equals(((Class<?>) type).getName()) 的技巧。

每个 Parameter Anootation 都会对应一个 ParameterHandler,如 static final class Path<T> extends ParameterHandler<T>,它们都实现了 ParameterHandler<T>

ParameterAnnotation ? extends ParameterHandler
Url RelativeUrl
Path Path
Query Query
QueryMap QueryMap
Header Header
HeaderMap HeaderMap
Field Field
FieldMap FieldMap
Part Part
PartMap PartMap
Body Body

每种 ParameterHandler 都通过 Converter<F, T> 将我们的传参类型转化成 RequestBuilder 需要的类型,并设置其参数。举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
static final class Query<T> extends ParameterHandler<T> {
private final String name;
private final Converter<T, String> valueConverter;
private final boolean encoded;
Query(String name, Converter<T, String> valueConverter, boolean encoded) {
this.name = checkNotNull(name, "name == null");
this.valueConverter = valueConverter;
this.encoded = encoded;
}
@Override void apply(RequestBuilder builder, T value) throws IOException {
if (value == null) return; // Skip null values.
builder.addQueryParam(name, valueConverter.convert(value), encoded);
}
}

valueConverter.convert 将我们的传参类型 T 转换成 String,并设置到 RequestBuilder 中。其他的配置也是按同样的方式。

4. OkHttpCall

OkHttpCall 实现了 retrofit2.Call<T>,看下它的构造函数,传入 ServiceMethod 和请求参数。

1
2
3
4
OkHttpCall(ServiceMethod<T> serviceMethod, Object[] args) {
this.serviceMethod = serviceMethod;
this.args = args;
}

4.1 createRawCall

先看下 OkHttpCall#createRawCall

1
2
3
4
5
6
7
8
private okhttp3.Call createRawCall() throws IOException {
Request request = serviceMethod.toRequest(args);
okhttp3.Call call = serviceMethod.callFactory.newCall(request);
if (call == null) {
throw new NullPointerException("Call.Factory returned null.");
}
return call;
}

将已经生成的 serviceMethod 通过 toRequest(args) 转成一个 okhttp3.Request 对象。再用创建 Retrofit 时指定的 okhttp3.Call.Factory 创建一个 okhttp3.Call,这里如果不指定 okhttp3.Call.Factory,则默认是 okhttp3.OkHttpClient

ServiceMethod#toRequest 方法中,用 method 相关的配置生成一个 retrofit2.RequestBuilder 后,再用之前准备好的 parameterHandlers 处理每一个参数,最后生成一个 okhttp3.Request

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/** Builds an HTTP request from method arguments. */
Request toRequest(Object... args) throws IOException {
RequestBuilder requestBuilder = new RequestBuilder(httpMethod, baseUrl, relativeUrl, headers, contentType, hasBody, isFormEncoded, isMultipart);
@SuppressWarnings("unchecked") // It is an error to invoke a method with the wrong arg types.
ParameterHandler<Object>[] handlers = (ParameterHandler<Object>[]) parameterHandlers;
// ... 校验
for (int p = 0; p < argumentCount; p++) {
handlers[p].apply(requestBuilder, args[p]);
}
return requestBuilder.build();
}

4.2 execute

okhttp3.Call#execute 用于同步请求 HTTP,线程会被阻塞,请求成功后返回我们指定的数据类型。

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
@Override public Response<T> execute() throws IOException {
okhttp3.Call call;
synchronized (this) {
if (executed) throw new IllegalStateException("Already executed.");
executed = true;
if (creationFailure != null) {
if (creationFailure instanceof IOException) {
throw (IOException) creationFailure;
} else {
throw (RuntimeException) creationFailure;
}
}
call = rawCall;
if (call == null) {
try {
call = rawCall = createRawCall();
} catch (IOException | RuntimeException e) {
creationFailure = e;
throw e;
}
}
}
if (canceled) {
call.cancel();
}
return parseResponse(call.execute());
}

首先检查 okhttp3.Call 是否已被执行。

一个 okhttp3.Call 只能被执行一次,可以调用 OkHttpCall#clone 重新创建一个新的相同配置的 HTTP 请求。

1
2
3
@Override public OkHttpCall<T> clone() {
return new OkHttpCall<>(serviceMethod, args);
}

然后检查并创建 okhttp3.Call,调用 okhttp3.Call#execute 执行同步请求。

最后调用 parsePesponse 将返回的 okhttp3.Response 解析成我们需要的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Response<T> parseResponse(okhttp3.Response rawResponse) throws IOException {
ResponseBody rawBody = rawResponse.body();
// Remove the body's source (the only stateful object) so we can pass the response along.
rawResponse = rawResponse.newBuilder()
.body(new NoContentResponseBody(rawBody.contentType(), rawBody.contentLength()))
.build();
int code = rawResponse.code();
// ... 状态码检查
ExceptionCatchingRequestBody catchingBody = new ExceptionCatchingRequestBody(rawBody);
try {
T body = serviceMethod.toResponse(catchingBody);
return Response.success(body, rawResponse);
} catch (RuntimeException e) {
// If the underlying source threw an exception, propagate that rather than indicating it was
// a runtime exception.
catchingBody.throwIfCaught();
throw e;
}
}

okhttp3.Response 进行一些状态码检查后调用 ServiceMethod#toResponse 生成我们需要的数据类型。这里就用到了我们之前准备好的 responseConverter。最后封装成一个 retrofit2.Response<T>,包含了原始的 rawResponse、我们需要的 bodyerrorBody,我们取需要的数据。

4.3 enqueue

okhttp3.Call#enqueueokhttp3.Call#execute 流程类似,异步请求 HTTP,然后将回调都交给 retrofit2.Callback<T> 处理。

看下 retrofit2.Callback#onFailure 的注释

Invoked when a network exception occurred talking to the server or when an unexpected exception occurred creating the request or processing the response.

这里 retrofit2.Callback#onFailure 除了处理网络异常外,还会处理创建网络请求和解析数据的异常,在回调中处理,而不是直接 crash,这点做的非常好。

  1. createRawCall 抛出的异常

    1
    2
    3
    4
    5
    try {
    call = rawCall = createRawCall();
    } catch (Throwable t) {
    failure = creationFailure = t;
    }

    如果出异常,直接回调,不执行接下来 enqueue 方法。

  2. 执行 okhttp3.Call#enqueueokhttp3.Callback 抛出的网络请求异常
  3. 网络请求成功后在 okhttp3.Callback#onResponseparseResponse 时抛出的异常

    1
    2
    3
    4
    5
    6
    try {
    response = parseResponse(rawResponse);
    } catch (Throwable e) {
    callFailure(e);
    return;
    }

5. CallAdapter

前面已经简单介绍过 ServiceMethod#createCallAdapter,它会从 adapterFactories 中找到第一个符合条件的 CallAdapter.Factory

5.1 retrofit-adapters

先来看 Retrofit 提供的 retrofit-adapters 模块,目前供我们选择使用的有 guavajava8rxjava,分别对应的 CallAdapter.FactoryGuavaCallAdapterFactoryJava8CallAdapterFactoryRxJavaCallAdapterFactory

Retrofit#build 时,除了我们自己添加的 CallAdapter.Factory, 还会添加两个默认的 CallAdapter.FactoryExecutorCallAdapterFactoryDefaultCallAdapterFactory

1
2
3
// Make a defensive copy of the adapters and add the default Call adapter.
List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories);
adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor));

Retrofit 源码中有大量类似 List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories); 的例子,不直接使用成员变量,而是将成员变量重新拷贝给一个新的临时变量,这样虽然多申请了4个字节内存,但如果以后将成员变量改成入参,就可以不用改代码直接使用了,是一种好的编码习惯。

5.2 default

再看默认的 CallAdapter.Factory

1
2
3
4
5
6
CallAdapter.Factory defaultCallAdapterFactory(Executor callbackExecutor) {
if (callbackExecutor != null) {
return new ExecutorCallAdapterFactory(callbackExecutor);
}
return DefaultCallAdapterFactory.INSTANCE;
}

DefaultCallAdapterFactoryExecutorCallAdapterFactory 返回的类型都是 retrofit2.Call<R>

DefaultCallAdapterFactory 中,CallAdapter#adapt 什么都不做,直接返回 retrofit2.Call<R>。而 ExecutorCallAdapterFactoryCallAdapter#adapt 则返回 ExecutorCallbackCall<T>,它实现了 retrofit2.Call<R>,会传入一个 Executor,同步调用不变,异步调用时会在指定的 Executor 上执行。

5.3 adapt

看下 CallAdapteradapt 方法

1
2
3
4
/**
* Returns an instance of {@code T} which delegates to {@code call}.
*/
<R> T adapt(Call<R> call);

它的作用就是把 retrofit2.Call<R> 转换成我们需要的 T

CallAdapters ? extends CallAdapter.Factory T
guava retrofit2.adapter.guava.GuavaCallAdapterFactory com.google.common.util.concurrent.ListenableFuture
java8 retrofit2.adapter.java8.Java8CallAdapterFactory java.util.concurrent.CompletableFuture
rxjava retrofit2.adapter.rxjava.RxJavaCallAdapterFactory rx.Observable
default retrofit2.ExecutorCallAdapterFactory retrofit2.Call
default retrofit2.DefaultCallAdapterFactory retrofit2.Call

List<CallAdapter.Factory> 中添加的顺序是我们指定的一个或多个 CallAdapter.Factory,默认的 ExecutorCallAdapterFactoryDefaultCallAdapterFactory,查找时按顺序查找。

6. Converter

Converter 的作用就是将 HTTP 请求返回的数据格式转换成我们需要的对象,或将我们提供的对象转换成 HTTP 请求需要的数据格式。

1
2
3
public interface Converter<F, T> {
T convert(F value) throws IOException;
}

接口非常清晰,将 F 转换成 T

6.1 Factory

来看 Converter.Factory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Factory {
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return null;
}
public Converter<?, RequestBody> requestBodyConverter(Type type,
Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) {
return null;
}
public Converter<?, String> stringConverter(Type type, Annotation[] annotations, Retrofit retrofit) {
return null;
}
}
  1. responseBodyConverter 用于将 HTTP 请求返回的 response 转换成我们指定的类型。
  2. requestBodyConverter 用于将 BodyPartPartMap 3种类型的参数转换成 HTTP 的请求体。
  3. stringConverter 用于将 FieldFieldMapHeaderPathQueryQueryMap 这几种类型的参数转换成 String

6.2 retrofit-converters

CallAdapter 类似,Retrofit 也提供了默认的 Converter.Factory——BuiltInConverters,只能处理基本的 ResponseBodyRequestBodyString 类型。

在 Retrofit 提供的 retrofit-converters 模块,供我们选择的有 GsonJacksonMoshiProtocol BuffersXMLScalarWire,我们常用的有 GsonConverterFactory

需要注意的是,Jake Wharton 在他的一篇演讲 Simple HTTP with Retrofit 2 中说道

I want to stress that the order matters. This is the order in which we’re going to ask each one whether or not it can handle a type. What I have written above is actually wrong. If we ever specify a proto, it’s going to be encoded as JSON, which will try and deserialize the response buddy as JSON. That’s obviously not what we want. We will have to flip these because we want to check protocol buffers first, and then JSON through GSON.

说的就是 Retrofit.Builder#addConverterFactory 的顺序非常重要,先添加的 Converter.Factory 会先用来解析,而 Gson 非常强大,如果第一个添加 GsonConverterFactory,则其他想要转换的类型如 Protocol Buffers 就不会执行,因此建议将 GsonConverterFactory 作为最后一个添加

7. 总结

Retrofit 的源码还是很难的,反反复复看了很多遍,除了其原理和流程外,从中也学到了一些技巧和设计模式。在写这篇文章的同时又把思路和流程重新理了一遍,对自己帮助还是非常大的。关于源码最大的感受就是各个类之间传对象调用感觉非常乱,也增加了理解的难度,如 RetrofitServiceMethod 之间就互相依赖。

本文是 慌不要慌 原创,发表于 https://danke77.github.io/,请阅读原文支持原创 https://danke77.github.io/2016/08/06/retrofit-source-analysis/,版权归作者所有,转载请注明出处。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!