目录
搭建服务提供下载资源
资源下载
重新指定RN资源加载路径并加载相关资源
ReactNative在Android环境中运行时候会先将RN相关资源打包并合并到android应用的assets目录,相关资源包内容如下(包括但不限于一下资源,这里是demo,比较简单):
Drawable-x包下面主要是一些图片的资源
raw包配置相关资源
index.androd.bindle是js相关代码打包后的特殊格式文件,其中包含应用相关信息等,也只android应用主要加载的目标。
当apk运行起来后,需要加载RN相关资源的时候应用默认会去assets目录下找,这个目录是固定不变,而且从云端下拉的资源也无法放入,但云端下啦的资源可以放在固定的目录下,所以本地热加载RN资源的重点是重新指定资源加载路径,并让android应用将这些资源加载运行起来。
思路:
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包
首先需要重写一些方法,主要是在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 getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List 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;
}
验证成功