react-native 热更新(android)

react-native代码分为android部分,js部分,图片资源文件
其中android部分代码是不支持热更新的,支持更新的只有js代码和图片资源文件
react项目生成app时,所有js文件都被压缩到index.android.bundle文件中,该文件和图片资源都位于assets目录下,app启动时,MainActivity首先加载index.android.bundle,转换为对应的原生试图显示到rootView
所以,react热更新就是在app启动时从服务器下载新的index.android.bundle和图片资源,然后加载新的index.android.bundle文件

更新index.android.bundle

react-native提供了getJSBundleFile()来修改index.android.bundle文件的加载路径,复写该方法,返回本地更新后的index.android.bundle文件路径

0.29及以后版本:在你的MainApplication中增加如下代码:

    import cn.reactnative.modules.update.UpdateContext;    public class MainApplication extends Application implements ReactApplication {        public static final String JS_BUNDLE_LOCAL_PATH = Environment.getExternalStorageDirectory().toString() + File.separator + "patches/index.android.bundle";        private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {            @Override            protected String getJSBundleFile() {              File file = new File(JS_BUNDLE_LOCAL_PATH);              if(file != null && file.exists()){                return JS_BUNDLE_LOCAL_PATH;              }else{                return super.getJSBundleFile();              }        }        // ... 其它代码         }    }

0.28及以前版本:在你的MainActivity中增加如下代码:

    // ... 其它代码    import cn.reactnative.modules.update.UpdateContext;    public class MainActivity extends ReactActivity {        public static final String JS_BUNDLE_LOCAL_PATH = Environment.getExternalStorageDirectory().toString() + File.separator + "patches/index.android.bundle";        @Override        protected String getJSBundleFile() {            File file = new File(JS_BUNDLE_LOCAL_PATH);              if(file != null && file.exists()){                return JS_BUNDLE_LOCAL_PATH;              }else{                return super.getJSBundleFile();              }        }        // ... 其它代码    }

index.android.bundle文件路径变更后,图片默认从该文件同目录下加载,所以图片资源要和index.android.bundle文件一起打包下载到手机SD存储目录

生成index.android.bundle

  1. 项目目录下新建bundle目录
  2. 执行react-native bundle –entry-file index.android.js –bundle-output ./bundle/index.android.bundle –platform android –assets-dest ./bundle –dev false命令生成index.android.bundle文件和图片资源到bundle目录下
  3. 将index.android.bundle文件和图片资源压缩并上传至服务器供app启动时更新使用

react-native 热更新(android)_第1张图片

下载压缩包到手机

复写MainActivity中onCreate方法,下载包含index.android.bundle文件和图片资源的压缩包到手机本地存储并解压,存储目录和getJSBundleFile()方法的返回值一致,这样,下次启动app时,就会加载更新后的index.android.bundle文件和图片资源

增量更新

由于生成的index.android.bundle文件最少也有几百KB,加上所有的图片资源,导致更新的压缩包体积较大,更新时间较长,且会浪费用户较多流量,所以必须采用增量更新的方法,即只下载js变更部分和新增或修改的图片资源

index.android.bundle文件增量更新

初始项目发布时,生成并保留一份index.android.bundle文件文件
有版本更新时,生成新的index.android.bundle文件,使用google-diff-match-patch对比两个文件,并生成差异补丁文件
手机app下载补丁文件,再使用google-diff-match-patch和assets目录下的初始版本合并,生成新的index.android.bundle文件

图片资源增量更新

1.修改图片加载的js源码:
RemoteUpdateDemo\node_modules\react-native\Libraries\Image\AssetSourceResolver.js
修改isLoadedFromFileSystem方法(是否从index.android.bundle文件目录下加载图片)

isLoadedFromFileSystem(): boolean {    var imgFolder = getAssetPathInDrawableFolder(this.asset);    var imgName = imgFolder.substr(imgFolder.indexOf("/")+1);    var isPatchImg = PatchImages.indexOf("|"+imgName+"|") > -1;    return !!this.bundlePath && isPatchImg;}

其中PatchImages是自定义字符串,其中包含所有更新和修改的图片名称

//补丁更新图片名称,中间和两边用|分割var PatchImages = "|test1.png|test2.png|";

注意:生成bundle目录时,图片资源都会放在统一目录下(drawable-mdpi),如果引用图片包含其它路径,例如require(“./img/test1.png”),图片在img目录下,则图片加载时会自动将img目录转换为图片名称,加载”img_test1.png”图片,此时更新图片名称需要修改为”img_test1.png”

下面贴上MainActivity代码示例:

public class MainActivity extends ReactActivity {private static final String TAG = "MainReactActivity";public static final String JS_Patch_REMOTE_URL = "http://1.filesys.applinzi.com/patches.zip";public static final String JS_BUNDLE_LOCAL_FILE = "index.android.bundle";public static final String JS_BUNDLE_LOCAL_PATH = Environment.getExternalStorageDirectory().toString() + File.separator + "patches/" + JS_BUNDLE_LOCAL_FILE;public static final String JS_PATCH_LOCAL_FILE = Environment.getExternalStorageDirectory().toString() + File.separator + "patches/patches.pat";public static final String JS_PATCH_LOCAL_FOLDER = Environment.getExternalStorageDirectory().toString() + File.separator + "patches";public static final String JS_PATCH_LOCAL_PATH = Environment.getExternalStorageDirectory().toString() + File.separator + "patches.zip";private CompleteReceiver mDownloadCompleteReceiver;private long mDownloadId;/** * Returns the name of the main component registered from JavaScript. * This is used to schedule rendering of the component. */@Overrideprotected String getMainComponentName() {    return "RemoteUpdateDemo";}@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    initDownloadManager();    updateBundle();}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){            onJSBundleLoadedFromServer();        }    }};private void initDownloadManager() {    mDownloadCompleteReceiver = new CompleteReceiver();    registerReceiver(mDownloadCompleteReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));}private void updateBundle() {    // Should add version check here, if bundle file    // is the newest , we do not need to update    File file = new File(JS_BUNDLE_LOCAL_PATH);    if(file != null && file.exists()){        Log.i(TAG, "newest bundle exists !");        return;    }    //Toast.makeText(BaseReactActivity.this, "Start downloading update", Toast.LENGTH_SHORT).show();    DownloadManager.Request request = new DownloadManager.Request(Uri.parse(JS_Patch_REMOTE_URL));    request.setDestinationUri(Uri.parse("file://" + JS_PATCH_LOCAL_PATH));    DownloadManager dm = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);    mDownloadId = dm.enqueue(request);    Log.i(TAG, "start download remote bundle");}private void onJSBundleLoadedFromServer() {    File file = new File(JS_PATCH_LOCAL_PATH);    if(file == null || !file.exists()){        Log.i(TAG, "download error, check URL or network state");        return;    }    try {        ZipInputStream inZip = new ZipInputStream(new FileInputStream(JS_PATCH_LOCAL_PATH));        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(JS_PATCH_LOCAL_FOLDER+File.separator+szName);                    folder.mkdirs();                }else{                    File file1 = new File(JS_PATCH_LOCAL_FOLDER+File.separator+szName);                    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();    }    String bundle1 = getJsBundleFromAssets();    String pathesStr = getStringFromText(JS_PATCH_LOCAL_FILE);    diff_match_patch dmp = new diff_match_patch();    LinkedList pathes = (LinkedList) dmp.patch_fromText(pathesStr);    Object[] bundle2 = dmp.patch_apply(pathes, bundle1);    try{        Writer w = new FileWriter(JS_BUNDLE_LOCAL_PATH);        String newJsBundle = (String) bundle2[0];        w.write(newJsBundle);        w.close();        File patchFile = new File(JS_PATCH_LOCAL_PATH);        patchFile.delete();    }catch (Exception e){        e.getMessage();    }}private String getJsBundleFromAssets(){    String result = "";    try {        InputStream is = getAssets().open(JS_BUNDLE_LOCAL_FILE);        int size = is.available();        byte[] buffer = new byte[size];        is.read(buffer);        is.close();        result = new String(buffer,"UTF-8");        Log.i(TAG, "get JS_BUNDLE_LOCAL_FILE success!!!");    } catch (IOException e) {        e.printStackTrace();    }    return result;}private String getStringFromText(String path){    String result = "";    try {        Reader reader = new FileReader(path);        int ch = reader.read();        StringBuffer sb = new StringBuffer();        while(ch!=-1){            sb.append((char) ch);            ch = reader.read();        }        reader.close();        result = sb.toString();        Log.i(TAG, "get JS_BUNDLE_LOCAL_FILE success!!!"+path);    } catch (IOException e) {        e.printStackTrace();    }    return result;}}

更多相关文章

  1. Android模拟 HTTP multipart/form-data 请求协议信息实现图片上
  2. Android android:scaleType属性 图片按比例缩放
  3. Android Launcher2源码分析主布局文件
  4. Android 相机拍照获取图片并保存到指定位置。
  5. 安装APK文件到Android模拟器
  6. Android 上传图片到后台的一直方式Base64的String形式
  7. android富文本 加载带图片的html
  8. Android开发笔记——以Volley图片加载、缓存、请求及展示为例理

随机推荐

  1. Android轻松实现语音功能
  2. TabWidget/TabHost的两种使用方法
  3. Android(安卓)OTA本地自动升级实现
  4. Android 中配置adb环境变量
  5. Android中menu详解
  6. android selector下的设置背景属性值
  7. 使用GDB进行Android Native调试
  8. Android使用WebView从相册/拍照中添加图
  9. root 后的android 无线传屏(服务器端与客
  10. android沉浸式状态栏底部背景用图片代替