reactnative中文网上的热更新看了一遍,感觉更像是只介绍了完全用RN开发的项目使用的说明,混合开发的热更新使用介绍并没有找到。于是百度到了这篇文章# React Native 热更新实现。原文的版本有点老,里面用反射来获取的方法现在是直接可以调用的,故下面的代码对原文代码做了些许修改。下面的代码是没有调用远程下载,所以相关代码没有验证是否可行,这个以后写了再更新,先用adb push模拟下载。
ps:如何得到bundle文件和图片资源呢?
参考reactnative中文网的打包教程https://reactnative.cn/docs/0.51/integration-with-existing-apps.html#content。就可以得到bundle文件和图片资源。具体命令如下
react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/bonus/src/main/assets/index.android.bundle --assets-dest android/bonus/src/main/res/
(1)--entry 入口js文件,android系统就是index.android.js,ios系统就是index.ios.js
(2)--bundle-output 生成的bundle文件路径
(3)--platform 平台
(4)--assets-dest 图片资源的输出目录
(5)--dev 是否为开发版本,打正式版的安装包时我们将其赋值为false
React Native所有的js文件都打包在一个jsbundle文件中,发布时也是打包到app里面,一般是放到asset目录.猜想是不是可以从远程下载jsbundle文件覆盖asset的jsbundle. 查资料发现asset目录是只读的,该想法行不通.
发现调用了setJSBundleFile方法, 而且该方法是public的, 也就是可以通过这个方法指定jsbundle文件。可以设置了jsbundle文件, 那我们就可以把jsbundle文件放到sdcard, 经过测试发现, 确实可以读取sdcard jsbundle.
后面继续实现项目时发现, 动态更新后, 本地图片始终不显示, 远程图片可以.
接下来查看React Native, jsbundle 源码和查看资料, 终于寻的一点蛛丝马迹, 大概的意思如下:
1、如果bundle在sd卡
【 比如bundle在file://sdcard/react_native_update/index.android.bundle 那么图片目录在file://sdcard/react_native_update/drawable-mdpi】
2、如果你的bundle在assets里,图片资源要放到res文件夹里,例如res/drawable-mdpi
为了验证以上的说法,我临时用adb推送了打包好的bundle和drawable-mdpi文件夹(里面代码中用到的图片) adb推送命令参考这篇https://yq.aliyun.com/articles/13413 果然加载出来了本地图片。
远程下载压缩后的bundle和drawable-mdpi
将yasuo.zip放到服务端,下载后解压缩到sdcard文件夹下,参考这篇博客http://blog.csdn.net/u013718120/article/details/55096393
以下是全部代码:
//解压Zip
public class RefreshUpdateUtils {
/**
* 解压
*/
public static void decompression() {
try {
ZipInputStream inZip = null;
inZip = new ZipInputStream(new FileInputStream(FileConstant.zipPath));
ZipEntry zipEntry;
String szName;
try {
while((zipEntry = inZip.getNextEntry()) != null) {
szName = zipEntry.getName();
if(zipEntry.isDirectory()) {
szName = szName.substring(0,szName.length()-1);
File folder = new File(FileConstant.JS_BUNDLE_REACT_UPDATE_PATH + File.separator + szName);
folder.mkdirs();
}else{
File file1 = new File(FileConstant.JS_BUNDLE_REACT_UPDATE_PATH + File.separator + szName);
boolean s = file1.createNewFile();
FileOutputStream fos = new FileOutputStream(file1);
int len;
byte[] buffer = new byte[1024];
while((len = inZip.read(buffer)) != -1) {
fos.write(buffer, 0 , len);
fos.flush();
}
fos.close();
}
}
} catch (IOException e) {
e.printStackTrace();
}
inZip.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//常量
public class FileConstant {
public static final String zipPath=Environment.getExternalStorageDirectory().toString()+ File.separator +"yasuo.zip";
public static final String path = Environment.getExternalStorageDirectory().toString()+ File.separator + "index.android.bundle";
public static final String JS_BUNDLE_REACT_UPDATE_PATH = Environment.getExternalStorageDirectory().toString();
public static final String JS_BUNDLE_REMOTE_URL = "https://raw.githubusercontent.com/hubcarl/smart-react-native-app/debug/app/src/main/assets/index.android.bundle";
public static final String JS_BUNDLE_LOCAL_FILE = "index.android.bundle";
}
public class UpdateReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
private static final String TAG = "UpdateReactActivity";
private ReactInstanceManager mReactInstanceManager;
private ReactRootView mReactRootView;
private CompleteReceiver mDownloadCompleteReceiver;
private long mDownloadId;
private File zipfile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
iniReactRootView(true);
// initDownloadManager();
// updateJSBundle(true);
}
// 如果bundle在sd卡【 比如bundle在file://sdcard/react_native_update/index.android.bundle 那么图片目录在file://sdcard/react_native_update/drawable-mdpi】
// 如果你的bundle在assets里,图片资源要放到res文件夹里,例如res/drawable-mdpi
private void iniReactRootView(boolean isRelease) {
Log.i("ReactNativeJS",">>>react react start:"+System.currentTimeMillis());
File file = new File(FileConstant.path);
if (isRelease && file != null && file.exists()) {
mReactInstanceManager = ReactInstanceManager.builder()
.setCurrentActivity(this)
.setApplication(getApplication())
.setJSMainModuleName(FileConstant.JS_BUNDLE_LOCAL_FILE)
.addPackage(new MainReactPackage())
.addPackage(new MyReactPackage())
.setInitialLifecycleState(LifecycleState.RESUMED)
.setJSBundleFile(FileConstant.path)
.build();
Log.i(TAG, "load bundle from local cache");
}
else {
mReactInstanceManager = ReactInstanceManager.builder()
.setCurrentActivity(this)
.setApplication(getApplication())
.setJSMainModuleName(FileConstant.JS_BUNDLE_LOCAL_FILE)
.addPackage(new MainReactPackage())
.addPackage(new MyReactPackage())
.setInitialLifecycleState(LifecycleState.RESUMED)
.setBundleAssetName(FileConstant.JS_BUNDLE_LOCAL_FILE)
.build();
Log.i(TAG, "load bundle from asset");
}
mReactRootView = new ReactRootView(this);
mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);
setContentView(mReactRootView);
Log.i("ReactNativeJS", ">>>react react end:"+System.currentTimeMillis());
}
private void updateJSBundle() {
zipfile = new File(FileConstant.path);
if (zipfile != null && zipfile.exists()) {
Log.i(TAG, "new bundle exists !");
zipfile.delete();
Log.i(TAG, "js bundle file delete success");
}
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(FileConstant.JS_BUNDLE_REMOTE_URL));
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
//下面的代码片段是在外部存储中指定一个任意的保存位置的方法:
request.setDestinationUri(Uri.parse("file://" + FileConstant.path));
//获取DownloadManager
DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
//将下载请求加入下载队列,加入下载队列后会给该任务返回一个long型的id,通过该id可以取消任务,重启任务、获取下载的文件等等
mDownloadId = dm.enqueue(request);
Log.i(TAG, "start download remote js bundle file");
}
private void initDownloadManager() {
mDownloadCompleteReceiver = new CompleteReceiver();
registerReceiver(mDownloadCompleteReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
private class CompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
long completeDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (completeDownloadId == mDownloadId) {
// 1.解压
RefreshUpdateUtils.decompression();
zipfile.delete();
//通过监听下载成功事件, 然后调用onJSBundleLoadedFromServer接口就可以看到立即更新的效果.
onJSBundleLoadedFromServer();
}
}
}
//通过监听下载成功事件, 然后调用onJSBundleLoadedFromServer接口就可以看到立即更新的效果.
private void onJSBundleLoadedFromServer() {
final File file = new File(FileConstant.path);
if (file == null || !file.exists()) {
Log.i(TAG, "js bundle file download error, check URL or network state");
return;
}
Log.i(TAG, "js bundle file file success, reload js bundle");
Toast.makeText(UpdateReactActivity.this, "download bundle complete", Toast.LENGTH_SHORT).show();
//为了在运行中重新加载bundle文件,查看ReactInstanceManager的源码,找到如下方法:
mReactInstanceManager.recreateReactContextInBackground();
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
mReactInstanceManager.showDevOptionsDialog();
return true;
}
return super.onKeyUp(keyCode, event);
}
@Override
protected void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause(this);
}
}
@Override
protected void onResume() {
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy();
}
}
}
网友评论