從Android到React Native開發(四、打包流程解析和釋出為Maven庫)

戀貓de小郭發表於2018-06-13

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 (-_^)。    

從Android到React Native開發(四、打包流程解析和釋出為Maven庫)

 通過前幾篇,你已經對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')引用模組,最後在ApplicationgetPackages()方法新增模組註冊。所以這裡我們明確了一點,專案引用的原生模組都是通過本地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

圖片來自http://www.huahuaxie.com/fat-aar-implementation-analysis

 最後我們可以先在rn-library執行../gradlew assembleRelease,讓react指令碼生成我們需要的資原始檔,然後再引用publish.gradle釋出aar到maven即可。

從Android到React Native開發(四、打包流程解析和釋出為Maven庫)

四、最後

 如何,最終實現過程其實並不複雜,總結起來:

  • 建立一個android.library
  • 新增本地dependencies依賴
  • apply react.gradle 、 fat-aar.gradle、publish.gradle
  • 在library通過../gradlew assembleRelease打包,然後通過maven-publish執行publish上傳。
Over(~ ̄▽ ̄)~

資源推薦:

哦嘞嘞

相關文章