1、從 Android 到 React Native 開發(一、入門)
2、從 Android 到 React Native 開發(二、通信與模塊實現)
3、從 Android 到 React Native 開發(三、自定義原生控件支持)
4、從 Android 到 React Native 開發(四、打包流程和發佈爲 Maven庫 )
作爲失蹤人口,本篇是對前三篇 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')
二、創建
看過系列篇章二的應該知道,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。當然,如何合併,合併的時機這些都是需要處理的點。而這時候, 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的流程如下圖所示,有興趣的可以深入瞭解: 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上傳。