美文网首页
ReactNative和Android混合开发热更新实现

ReactNative和Android混合开发热更新实现

作者: zzl93 | 来源:发表于2018-02-07 16:01 被阅读1110次

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

yasuo.png

以下是全部代码:

//解压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();
       }
   }
}

相关文章

网友评论

      本文标题:ReactNative和Android混合开发热更新实现

      本文链接:https://www.haomeiwen.com/subject/difazxtx.html