1、從Android到React Native開發(一、入門)
2、從Android到React Native開發(二、通訊與模組實現)
3、從Android到React Native開發(三、自定義原生控制元件支援)
作為失蹤人口,本篇是對前三篇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上傳。