目錄
ReactNative在Android環境中運行時候會先將RN相關資源打包併合併到android應用的assets目錄,相關資源包內容如下(包括但不限於一下資源,這裏是demo,比較簡單):
Drawable-x包下面主要是一些圖片的資源
raw包配置相關資源
index.androd.bindle是js相關代碼打包後的特殊格式文件,其中包含應用相關信息等,也只android應用主要加載的目標。
當apk運行起來後,需要加載RN相關資源的時候應用默認會去assets目錄下找,這個目錄是固定不變,而且從雲端下拉的資源也無法放入,但云端下啦的資源可以放在固定的目錄下,所以本地熱加載RN資源的重點是重新指定資源加載路徑,並讓android應用將這些資源加載運行起來。
思路:
- 搭建本地服務器,事先準備好RN打包好的bundle.zip資源包;
- 下拉bundle.zip資源包並解壓到指定目錄;
- 重寫React native相關代碼指定資源包加載路徑和加載邏輯
搭建服務提供下載資源
mac上相對簡單,主要通過python,實現在桌面建立文件見並將bundle.zip包放入,命令行
1)$ cd /Users/xxxx/Desktop/server
2)$ python -m SimpleHTTPServer 8900
3)驗證服務可用,瀏覽器輸入:http://0.0.0.0:8900/,檢測可以下載
4)但是通過android模擬器是無法建立鏈接的需要修改爲http://10.0.2.2:8900/bundle.zip
資源下載
相關代碼結構如下,
下載的zip包存放路徑是android的根目錄的bundles包
重新指定RN資源加載路徑並加載相關資源
首先需要重寫一些方法,主要是在Application中進行,整體代碼如下:
public class MainApplication extends Application implements ReactApplication {
public static Context appContext;
private static MainApplication instance;
private ReactInstanceManager mReactInstanceManager;
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return false;//在Debug模式下,會去加載JS Server服務的bundle。在Release模式下會去加載本地的bundle
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
packages.add(new UpgradePackage());
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
@Nullable
@Override
protected String getJSBundleFile() {
String path = getBundlePath();
File file = new File(path);
if (file != null && file.exists()) {
return path;
}
return null;
}
@Override
protected ReactInstanceManager createReactInstanceManager() {
ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
.setApplication(getApplication())
.setJSMainModulePath(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setRedBoxHandler(getRedBoxHandler())
.setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
.setUIImplementationProvider(getUIImplementationProvider())
.setJSIModulesPackage(getJSIModulePackage())
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE);
for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}
String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
mReactInstanceManager = builder.build();
return mReactInstanceManager;
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
instance = this;
appContext = getApplicationContext();
SoLoader.init(this, false);
}
/**
* 獲取Application實例
*/
public static MainApplication getInstance() {
return instance;
}
/**
* 獲取包名
*/
public String getAppPackageName() {
return this.getPackageName();
}
public ReactContext getReactContext(){
return mReactNativeHost.getReactInstanceManager().getCurrentReactContext();
}
public String getBundlePath() {
return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "bundles/index.android.bundle";
}
}
最主要的是構建:mReactNativeHost,其中有兩個重要的實現方法,getJSBundleFile()和createReactInstanceManager()
getJSBundleFile就是用來指定資源加載路徑的,也就是Android應用加載RN資源包的路徑;
createReactInstanceManager是用來創建ReactInstanceManager的,而該類就是用來重新加載RN資源的,具體加載代碼如下:
/**
* 重新加載
*/
private void reloadJSBundle() {
File file = new File(MainApplication.getInstance().getBundlePath());
if (file == null || !file.exists()) {
Log.i(TAG, "download error, check URL or network state");
return;
}
Log.i(TAG, "download success, reload js bundle");
Toast.makeText(this, "Downloading complete", Toast.LENGTH_SHORT).show();
Field jsBundleField;
try {
ReactInstanceManager reactInstanceManager = MainApplication.getInstance().getReactNativeHost().getReactInstanceManager();
Class<?> RIManagerClazz = reactInstanceManager.getClass();
jsBundleField = RIManagerClazz.getDeclaredField("mBundleLoader");
jsBundleField.setAccessible(true);
jsBundleField.set(reactInstanceManager, JSBundleLoader.createFileLoader(file.getPath()));
if (!reactInstanceManager.hasStartedCreatingInitialContext()) {
reactInstanceManager.createReactContextInBackground();
} else {
reactInstanceManager.recreateReactContextInBackground();
}
reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener(){
@Override
public void onReactContextInitialized(ReactContext context) {
reactInstanceManager.removeReactInstanceEventListener(this);
try {
CatalystInstance catalystInstance = reactInstanceManager.getCurrentReactContext().getCatalystInstance();
Method method = CatalystInstanceImpl.class.getDeclaredMethod("loadScriptFromFile", String.class, String.class, boolean.class);
method.setAccessible(true);
method.invoke(catalystInstance, file.getPath(), file.getPath(), false);
} catch (Exception e) {
e.printStackTrace();
}
}
});
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
注意上面使用的ReactInstanceManager 實例便是先前在Application中重寫的createReactInstanceManager方法返回的。
ReactInstanceManager reactInstanceManager = MainApplication.getInstance().getReactNativeHost().getReactInstanceManager();
上面工作基本工作就緒後運行驗證,可能不注意修改getUseDeveloperSupport()返回值,默認是返回BuildConfig.DEBUG,這樣運行不會成功的,因爲在debug模式下,會去加載JS Server服務的bundle,在release模式下會去加載本地的bundle,返回值false表示release模式
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
如果運行如下圖,就是不成功的,加載的是JS Server服務器中的代碼
修改返回值爲false,重新運行:
@Override
public boolean getUseDeveloperSupport() {
return false;
}
驗證成功