react-native如何熱更新(android)

RN的熱更新和weex的熱更新不同,weex是可以直接訪問遠(yuǎn)程服務(wù)器的,有新版本的時(shí)候直接更換服務(wù)器上的文件就行,但是RN不行,RN的熱更新是需要更換本地文件,所以RN的熱更新需要更多的判斷。

原理

RN熱更新的原理是根據(jù)MainAppliction中new ReactNativeHost下的getJSBundleFile方法,它默認(rèn)返回index.android.bundle.js的文件路徑,我們需要做的就是去替換它。所以它的步驟就是: 判斷是否熱更新 -> 下載zip包(zip包減少帶寬) -> 解壓 -> 完事。熱更新就是這么的簡(jiǎn)單。

   @Nullable
   @Override
   protected String getJSBundleFile() {
     // 判斷權(quán)限
     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();
     }

   }

判斷是否熱更新

我們需要在進(jìn)入app頁(yè)面時(shí)發(fā)送網(wǎng)絡(luò)請(qǐng)求去進(jìn)行熱更新判斷,判斷熱更新的條件有兩個(gè):一,本地原生版本大于或等于網(wǎng)絡(luò)請(qǐng)求的原生版本。二,本地?zé)岣掳姹拘∮诰W(wǎng)絡(luò)請(qǐng)求的熱更新版本。這兩個(gè)條件同時(shí)成立則進(jìn)行熱更新下載。第一個(gè)條件是為了限制不讓原生和熱更新同時(shí)觸發(fā),事實(shí)上原生更新是包括熱更新的。本地的原生版本可以通過修改AndroidManifest.xml的meta-data來判斷。而本地的熱更新版本是不能這樣的,所以我在bundle.zip中加入一個(gè)version.txt來控制熱更新版本,每次熱更新后version.txt都加一,在android中讀取這個(gè)文件來獲取本地?zé)岣掳姹荆缓笤偃ズ途W(wǎng)絡(luò)請(qǐng)求的比對(duì)就能判斷是否需要熱更新了。


流程圖

下載

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

下載這里需要提到的一個(gè)點(diǎn)是: https的下載需要對(duì)其ssl認(rèn)證,反正也是第一次碰到,網(wǎng)上找的一個(gè)方法.

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];
            // 讀?。ㄗ止?jié))字節(jié)到緩沖區(qū)
            while ((len = inZip.read(buffer)) != -1) {
                // 從緩沖區(qū)(0)位置寫入(字節(jié))字節(jié)
                out.write(buffer, 0, len);
                out.flush();
            }
            out.close();
        }
    }
    inZip.close();
}

問題

android6.0以上(包括6)都需要?jiǎng)討B(tài)授權(quán),熱更新需要的權(quán)限是

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

動(dòng)態(tài)授權(quán)會(huì)導(dǎo)致的一個(gè)問題是:當(dāng)你安裝完app后,此時(shí)你沒有給app授權(quán),但是已經(jīng)檢測(cè)到需要熱更新,下載完后解壓就需要android.permission.WRITE_EXTERNAL_STORAGE這個(gè)權(quán)限,沒有的話會(huì)crash,所以需要判斷的是沒有這個(gè)權(quán)限就不讓他下載。

// 判斷是否有權(quán)限
if (permission.check(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
    new UpdateChecker().check(this);
}

動(dòng)態(tài)授權(quán)還有一個(gè)問題是:你的app已經(jīng)熱更新過,現(xiàn)在你把它卸載掉,再重新裝的時(shí)候,雖然不會(huì)觸發(fā)熱更新,但是此時(shí)你手機(jī)上已經(jīng)有熱更新過的文件夾,這時(shí)app會(huì)直接去訪問熱更新的文件夾,又是因?yàn)闆]有授權(quán),所以報(bào)錯(cuò),crash。這里我們一樣需要做個(gè)判斷是否有權(quán)限。

   @Nullable
   @Override
   protected String getJSBundleFile() {
     // 判斷權(quán)限
     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();
     }

   }

都是淚?。。?!這幾個(gè)坑。

后期優(yōu)化

1.熱更新的過程開一個(gè)子線程去解決。

  1. 增量更新,不需要每次都去下載一個(gè)完整的bundle.zip,雖然不大,但是能省則省嘛
  2. bundle.zip中的version.txt自動(dòng)加一。

github地址

個(gè)人博客

簡(jiǎn)書地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容