react-native如何热更新(android)

作者: brotherwang | 来源:发表于2018-01-22 16:11 被阅读231次

    RN的热更新和weex的热更新不同,weex是可以直接访问远程服务器的,有新版本的时候直接更换服务器上的文件就行,但是RN不行,RN的热更新是需要更换本地文件,所以RN的热更新需要更多的判断。

    原理

    RN热更新的原理是根据MainAppliction中new ReactNativeHost下的getJSBundleFile方法,它默认返回index.android.bundle.js的文件路径,我们需要做的就是去替换它。所以它的步骤就是: 判断是否热更新 -> 下载zip包(zip包减少带宽) -> 解压 -> 完事。热更新就是这么的简单。

       @Nullable
       @Override
       protected String getJSBundleFile() {
         // 判断权限
         if (!new RuntimePermissions(context).check(Manifest.permission.WRITE_EXTERNAL_STORAGE))
           return super.getJSBundleFile();
    
         File file = new File (FilePath.BUNDLE_PATH);
         if(file != null && file.exists()) {
           return FilePath.BUNDLE_PATH;
         } else {
           return super.getJSBundleFile();
         }
    
       }
    
    

    判断是否热更新

    我们需要在进入app页面时发送网络请求去进行热更新判断,判断热更新的条件有两个:一,本地原生版本大于或等于网络请求的原生版本。二,本地热更新版本小于网络请求的热更新版本。这两个条件同时成立则进行热更新下载。第一个条件是为了限制不让原生和热更新同时触发,事实上原生更新是包括热更新的。本地的原生版本可以通过修改AndroidManifest.xml的meta-data来判断。而本地的热更新版本是不能这样的,所以我在bundle.zip中加入一个version.txt来控制热更新版本,每次热更新后version.txt都加一,在android中读取这个文件来获取本地热更新版本,然后再去和网络请求的比对就能判断是否需要热更新了。


    流程图

    下载

        public static void downloadFile(final Context mContext, String url) throws Exception {
        AsyncHttpClient client = new AsyncHttpClient(getSchemeRegistry());
        client.get(mContext, url, new AsyncHttpResponseHandler() {
            @Override
            public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
                BufferedOutputStream bos = null;
                FileOutputStream fos = null;
                File file = null;
                try {
                    file = new File(FilePath.ZIP_LOCAL_PATH);
                    fos = new FileOutputStream(file);
                    bos = new BufferedOutputStream(fos);
                    bos.write(responseBody);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                File zipFile = new File(FilePath.ZIP_LOCAL_PATH);
                File file_unzip_path = new File(FilePath.LOCAL_PATH);
                if (!file_unzip_path.exists()) {
                    file_unzip_path.mkdir();
                }
                try {
                    UnZipFolder(zipFile, FilePath.LOCAL_PATH);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
            @Override
            public void onFailure(int statusCode, Header[] headers, byte[] binaryData, Throwable error) {
                Toast.makeText(mContext, "下载失败", Toast.LENGTH_LONG).show();
            }
        });
    }
    
    

    下载这里需要提到的一个点是: https的下载需要对其ssl认证,反正也是第一次碰到,网上找的一个方法.

    public static SchemeRegistry getSchemeRegistry() {
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);
            SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
            sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
            HttpParams params = new BasicHttpParams();
            HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
            HttpProtocolParams.setContentCharset(params, HTTP.UTF_8);
            SchemeRegistry registry = new SchemeRegistry();
            registry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
            registry.register(new Scheme("https", sf, 443));
            return registry;
        } catch (Exception e) {
            return null;
        }
    }
    
    

    解压

    public static void UnZipFolder(File f, String outPathString) throws Exception {
        ZipInputStream inZip = new ZipInputStream(new FileInputStream(f));
        ZipEntry zipEntry;
        String  szName = "";
        while ((zipEntry = inZip.getNextEntry()) != null) {
            szName = zipEntry.getName();
            if (zipEntry.isDirectory()) {
                //获取部件的文件夹名
                szName = szName.substring(0, szName.length() - 1);
                File folder = new File(outPathString + File.separator + szName);
                folder.mkdirs();
            } else {
                Log.e(TAG,outPathString + File.separator + szName);
                File file = new File(outPathString + File.separator + szName);
                if (!file.exists()){
                    Log.e(TAG, "Create the file:" + outPathString + File.separator + szName);
                    file.getParentFile().mkdirs();
                    file.createNewFile();
                }
                // 获取文件的输出流
                FileOutputStream out = new FileOutputStream(file);
                int len;
                byte[] buffer = new byte[1024];
                // 读取(字节)字节到缓冲区
                while ((len = inZip.read(buffer)) != -1) {
                    // 从缓冲区(0)位置写入(字节)字节
                    out.write(buffer, 0, len);
                    out.flush();
                }
                out.close();
            }
        }
        inZip.close();
    }
    

    问题

    android6.0以上(包括6)都需要动态授权,热更新需要的权限是

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    
    

    动态授权会导致的一个问题是:当你安装完app后,此时你没有给app授权,但是已经检测到需要热更新,下载完后解压就需要android.permission.WRITE_EXTERNAL_STORAGE这个权限,没有的话会crash,所以需要判断的是没有这个权限就不让他下载。

    // 判断是否有权限
    if (permission.check(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
        new UpdateChecker().check(this);
    }
    
    

    动态授权还有一个问题是:你的app已经热更新过,现在你把它卸载掉,再重新装的时候,虽然不会触发热更新,但是此时你手机上已经有热更新过的文件夹,这时app会直接去访问热更新的文件夹,又是因为没有授权,所以报错,crash。这里我们一样需要做个判断是否有权限。

       @Nullable
       @Override
       protected String getJSBundleFile() {
         // 判断权限
         if (!new RuntimePermissions(context).check(Manifest.permission.WRITE_EXTERNAL_STORAGE))
           return super.getJSBundleFile();
    
         File file = new File (FilePath.BUNDLE_PATH);
         if(file != null && file.exists()) {
           return FilePath.BUNDLE_PATH;
         } else {
           return super.getJSBundleFile();
         }
    
       }
    
    

    都是泪啊!!!这几个坑。

    后期优化

    1.热更新的过程开一个子线程去解决。

    1. 增量更新,不需要每次都去下载一个完整的bundle.zip,虽然不大,但是能省则省嘛
    2. bundle.zip中的version.txt自动加一。

    github地址

    个人博客

    简书地址

    相关文章

      网友评论

        本文标题:react-native如何热更新(android)

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