1, 从 Android 到 React Native 开发 (一, 入门) http://www.jianshu.com/p/97692b1c451d
2, 从 Android 到 React Native 开发 (二, 通信与模块实现) http://www.jianshu.com/p/bec040926db8
3, 从 Android 到 React Native 开发 (三, 自定义原生控件支持) http://www.jianshu.com/p/a488674d55b3
作为失踪人口, 本篇是对前三篇 React Native 文章的番外补充, 主要实现把 React Native 项目, 打包为完整 aar 库发布到 maven, 提供库支持的功能, 算是小众化的需求吧, 不过通过本篇你可以了解:
React Native 的资源的打包流程.
React Native 原生依赖结构.
本地多 aar 文件的合并实现.
进一步的 Gradle 脚本理解.
如何发布一个 React Native 的 Maven 库.
OK,Let't do it (-_^).
通过前几篇, 你已经对 React Native 的项目结构, 通信交互方式有了一定了解, 不了解也没关系 ((_)?), 我们知道, 发布一个 maven 库, 首先你要先有一个 lib 模块.
你需要在项目的 android 目录下, 即 app 这个 module 的同级目录下, 创建一个 Android Library 的 module:rn-library .(当然你也可以修改 app 下的
apply plugin: "com.android.application"
为
apply plugin: 'com.android.library'
, 再屏蔽 applicationId).
一, 引用
使用过 React Native 的应该知道, 依赖的库都是通过 npm install 安装, 安装后的所有源码存在于 node_modules 文件夹中, 如果依赖的库需要原生代码的支持, 需要通过 react-native link 实现原生代码模块的引用注册.
而手动针对 Android 添加过 link 的应该熟悉, react-native link 实际上是通过脚本, 在 setting.gradle 文件中引入模块在 node_modules 原生路径, 然后在 app 的 module 的 build.gradle 中, 通过
compile project(':react-native-fs')
引用模块, 最后在 Application 的 getPackages() 方法添加模块注册. 所以这里我们明确了一点, 项目引用的原生模块都是通过本地 project module 的引用.(这很重要 ())
- setting.gradle :
- // 在 setting 中指定模块的位置
- include ':react-native-fs'
- project(':react-native-fs').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fs/android')
二, 创建
看过系列篇章二 http://www.jianshu.com/p/bec040926db8 的应该知道, React Native 项目其实是通过
ReactInstanceManager
, 实现对 js 的 bundle 文件加载的. 所以要呈现一个 React Native 页面, 我们可以通过
ReactInstanceManager
, 在任意自定义 Activity 或者 Fragment 中, 实现页面的显示渲染 (当然你也可以直接继承 ReactActivity). 这里只列关键点点代码, 即
ReactInstanceManager
的创建和加载, 如下发代码 (更多可见篇章二):
- mReactInstanceManager = ReactInstanceManager.builder()
- // 设置加载文件, 这里从 assets 中加载打包好的 js bundle
- .setBundleAssetName("index.android.bundle")
- // 异常输出
- .setNativeModuleCallExceptionHandler(new NativeModuleCallExceptionHandler() {
- @Override
- public void handleException(Exception e) {
- e.printStackTrace();
- }
- })
- // 增加主模块注册, 必须
- .addPackage(new MainReactPackage())
- // 增加使用你的第三方模块注册
- .addPackage(new RNFSPackage())
- // 通 Application 中指定的 getJSMainModuleName
- .setJSMainModulePath("index")
- // 是否支持开发者模式
- .setUseDeveloperSupport(false)
- // 初始化生命周期
- .setInitialLifecycleState(LifecycleState.RESUMED)
- // 设置 Application
- .setApplication(getActivity().getApplication())
- .build();
- //js 代码中注册的的 Component 名字 AppRegistry.registerComponent('AppModule', () => App);
- String moduleName = "AppModule";
- // 通过页面中已经声明好 ReactRootView, 启动
- mReactRootView.startReactApplication(mReactInstanceManager, moduleName, null);
1,bundle 文件
从上方代码可以看出, 我们直接加载 assets 目录下的 bundle 文件
index.android.bundle
(当然你可以从本地或者网络加载 jsbundle 文件也是可以), 它的生成和拷贝是通过 react-native 目录下的 react.gradle 脚本实现的. 这个脚本会读取一些配置路径, 然后执行命令行打包和拷贝需要的资源, 所以和 app 的 build.gradle 文件一样, 在 rn-library 的 build.gradle 文件顶部增加引入即可, 打包后, 默认生成的 bundle 文为即为
index.android.bundle
文件..
- // 引入 react 脚本
- apply from: "../../node_modules/react-native/react.gradle"
2, 资源文件
这里有一个需要额外关注的点: 根据 node_nodules/react-native/local-cli/bundle / 目录下的 assetPathUtils.js 文件中,
getAndroidResourceIdentifier
方法的源码可知, js 文件中引用本地的静态资源文件, 如果存在多级目录, 是会被 Encode 处理的, 其中 / 会被替换为_, 数字会被屏蔽, assets_会被屏蔽.
- function getAndroidResourceIdentifier(asset: PackagerAsset) {
- var folderPath = getBasePath(asset);
- return (folderPath + '/' + asset.name)
- .toLowerCase()
- .replace(/\//g, '_') // Encode folder structure in file name
- .replace(/([^a-z0-9_])/g, '') // Remove illegal chars
- .replace(/^assets_/, ''); // Remove"assets_" prefix
- }
所以, 比如放在 React Native 项目根目录下的 img/pic/logo.png 的资源, 其实编译时, 会被重命名后, 拷贝 merged 到对应的是 drawable 目录下, 比如 drawable-xxhdpi 下的 img_pic_logo.png. 这一切都是由 react native 中的脚本执行的. 不过默认情况下, 生成拷贝的 bundle 文件和 resources 资源路径, 是无法被打包到 aar 中的. 所以如下代码所示, 我们需要配置生成的资源自动添加到 aar 文件中.
- // 默认路径
- //jsBundleDirRelease: "$buildDir/intermediates/assets/release
- //resourcesDirRelease: "$buildDir/intermediates/res/merged/release
- // 修改为
- project.ext.react = [
- jsBundleDirRelease: "$buildDir/intermediates/bundles/release/assets",
- resourcesDirRelease: "$buildDir/intermediates/bundles/release/res",
- ]
三, 打包
上面说过: React Native 项目引用的原生模块, 都是通过本地 project module 的引用. 那么默认的 maven 发布方式, 只会发布指定 module 的 aar 文件, 对于引用的其他 module 模块, 这些 dependencies 列在了与 aar 文件同目录的. pom 文件中, 并不会打包仅 aar, 而明显 React Native 的这些第三方支持包, 并不是 Maven 库.
这时候, 就需要通过 gradle 脚本, 手动对依赖的 module 模块, 实现 aar 文件内容的合并. aar 文件本身和 Apk 一样, 其实是一个 zip 压缩文件, 其中包含文件如下所示:
- /** 主要文件 **/
- classes.jar
- R.txt
- AndroidManifest.xml
- res/
- /** 其他文件 **/
- proguard.txt
- libs/
- jni/
- ...
这里所谓的合并, 就是就是将所有需要的 aar 文件内容, 拷贝到一起, 然后合并一个 aar. 当然, 如何合并, 合并的时机这些都是需要处理的点. 而这时候, https://github.com/CarGuo/android-fat-aar 脚本, 刚好满足的我们的需求. 通过引入
apply from: 'fat-aar.gradle'
的脚本, 对需要合并模块引用修改为
embedded project(':react-native-fs')
依赖即可:
- dependencies {
- embedded project(':react-native-fs')
- compile fileTree(dir: "libs", include: ["*.jar"])
- compile "com.android.support:appcompat-v7:23.0.1"
- embedded "com.facebook.react:react-native:+" // From node_modules
- }
从脚本代码中可以知道, 这里的 embedded 实际上是一个 configuration 类, 而这个 configurations 对应的是一个 ConfigurationContainer,ConfigurationContainer 包含有 dependencies, 如下代码所示, 最终还是使用 compile 引用, 但是这个过程中, 我们通过 embedded 统计到哪些包需要合并发布.
- configurations {
- embedded
- }
- dependencies {
- compile configurations.embedded
- }
因此我们可以根据 build 目录下的一些文件, 动态的 embedded 的 module 进行文件拷贝和合并, 如
$build_dir/intermediates/exploded-aar
目录下, 对每个需要合并的 module 的 res 文件夹, libs 文件夹, jars 文件夹, assets 文件夹等的拷贝. 合并 aar 的流程如下图所示, 有兴趣的可以深入了解: http://www.huahuaxie.com/fat-aar-implementation-analysis/ .
最后我们可以先在 rn-library 执行
../gradlew assembleRelease
, 让 react 脚本生成我们需要的资源文件, 然后再引用 publish.gradle 发布 aar 到 maven 即可.
四, 最后
如何, 最终实现过程其实并不复杂, 总结起来:
创建一个 android.library
添加本地 dependencies 依赖
apply react.gradle , fat-aar.gradle,publish.gradle
在 library 通过../gradlew assembleRelease 打包, 然后通过 maven-publish 执行 publish 上传.
Over(~)~
资源推荐:
android-fat-aar 脚本 https://github.com/CarGuo/android-fat-aar
demo 地址 https://github.com/CarGuo/LearnProject
超完整 React Native 学习项目 https://github.com/CarGuo/GSYGithubAPP
来源: https://juejin.im/post/5b2116466fb9a01e3128359f