1.基礎知識
- react native bundle包生成命令:
react-native bundle --entry-file index.js --bundle-output ./bundle/index.android.jsbundle --platform android --assets-dest ./bundle --dev false
//entry-file JS文件的路徑
//bundle-output JSbundle文件的生成目錄
//platform 平臺 Android或iOS
//assets-dest 資源文件的生成目錄
//dev 開發(fā)模式
-
資源文件的拷貝生成:
android:資源文件會被拷貝生成到drawable文件夾下,它的文件路徑會變成目錄名+下劃線的格式來命名原文件。如src/assets/ABCD.png會變成src_assets_ABCD.png。
Android資源文件命名
iOS:資源文件會被拷貝生成到assets文件夾下,而資源文件則會保存原有的文件路徑。如src/assets/ABCD.png bundle之后,會生成為assets/src/assets/ABCD.png

3.react native中引用資源的方式:
比如圖片組件加載圖片或者Web組件加載HTML文件,一般是使用require命令加載對應資源的相對路徑。

2.RN的資源打包機制
rn中打包的腳本代碼在react-native/local-cli/bundle這個文件夾中,打包的邏輯代碼再buildBundle.js這個文件中。從這個方法入手,一步一步跟蹤可以發(fā)現(xiàn)。最終依賴的是metro這個Facebook專為RN打包的打包工具(https://github.com/facebook/metro)。(舊版RN代碼中是使用的內置的Packager,這里用facebook最新的RN代碼和metro代碼分析)

RN中引用的資源文件會被解析成模塊依賴,在node-haste(Facebook出的靜態(tài)資源管理包https://www.npmjs.com/package/node-haste)解析到這些資源后,會轉換成資源模塊AssetModule。具體步驟如下:
首先通過查找資源匹配來判斷是否是資源模塊,默認的資源文件后綴在defaults.js文件中。有圖片,視頻,音頻等等文件格式。

然后通過metro中一系列方法調用,最終調用到Assets文件中的getAssetData方法,生成資源模塊。

現(xiàn)在可以用我們之前打好的jsbundle文件,查看一下打好的資源模塊是什么樣的:

3.RN的資源加載機制
以圖片資源的加載為例,通過斷點可以看出獲取資源從最初的require方法為入口,后來是通過AssetRegistry.js這個類,來注冊和獲取圖片資源數(shù)據(jù)。通過一個數(shù)組來維護項目中所使用的本地圖片資源。

//封裝的圖片資源數(shù)據(jù)
export type PackagerAsset = {
+__packager_asset: boolean,
+fileSystemLocation: string,
+httpServerLocation: string,
+width: ?number,
+height: ?number,
+scales: Array<number>,
+hash: string,
+name: string,
+type: string,
};
const assets: Array<PackagerAsset> = [];
//注冊圖片資源
function registerAsset(asset: PackagerAsset): number {
// `push` returns new array length, so the first asset will
// get id 1 (not 0) to make the value truthy
return assets.push(asset);
}
//通過ID獲取圖片資源
function getAssetByID(assetId: number): PackagerAsset {
return assets[assetId - 1];
}
圖片資源數(shù)據(jù)中是資源的相對路徑,在圖片組件中并不是能直接使用的Uri。在這之間有一次資源的解析。查找圖片組件的源碼Image.ios.js或Image.android.js,發(fā)現(xiàn)都通過resolveAssetSouce方法處理了。最終在defaultAsset方法中能清楚的看到圖片加載可以分為來自網(wǎng)絡,資源或是本地文件系統(tǒng)中。

function resolveAssetSource(source: any): ?ResolvedAssetSource {
if (typeof source === 'object') {//加載{uri:XXXX}這種網(wǎng)絡資源
return source;
}
const asset = AssetRegistry.getAssetByID(source);//獲取資源ID
if (!asset) {
return null;
}
const resolver = new AssetSourceResolver(
getDevServerURL(),
getScriptURL(),
asset,
);
if (_customSourceTransformer) {//自定義資源解析
return _customSourceTransformer(resolver);
}
return resolver.defaultAsset();//默認資源解析方法
}
//AssetSourceResolver.js
class AssetSourceResolver {
serverUrl: ?string;
// where the jsbundle is being run from
jsbundleUrl: ?string;
// the asset to resolve
asset: PackagerAsset;
constructor(serverUrl: ?string, jsbundleUrl: ?string, asset: PackagerAsset) {
this.serverUrl = serverUrl;
this.jsbundleUrl = jsbundleUrl;
this.asset = asset;
}
isLoadedFromServer(): boolean {
return !!this.serverUrl;
}
isLoadedFromFileSystem(): boolean {
return !!(this.jsbundleUrl && this.jsbundleUrl.startsWith('file://'));
}
defaultAsset(): ResolvedAssetSource {
if (this.isLoadedFromServer()) {//加載來自網(wǎng)絡的資源,jsbundle資源來自網(wǎng)絡,例如debug模式
return this.assetServerURL();
}
if (Platform.OS === 'android') {
return this.isLoadedFromFileSystem()//加載來自文件系統(tǒng)的資源
? this.drawableFolderInBundle()
: this.resourceIdentifierWithoutScale();//加載來自Asset資源中的文件
} else {
return this.scaledAssetURLNearBundle();
}
}
