react-native App更新方案

前言

用react-native(一下簡(jiǎn)稱RN)開發(fā)的app的更新方案有很多,其中比較火的是熱更新方案,有官方推薦的pushy和微軟的code-push
文檔很詳細(xì),接入也比較簡(jiǎn)單
這里主要介紹一種最傳統(tǒng)的更新方案,也是很多原生開發(fā)在使用的方案——全量更新

全量更新

顧名思義,即每次更新通過http去下載新版本包去然后去做一個(gè)覆蓋安裝,這種做法在更新迭代中會(huì)避免很多不必要的麻煩,而且在這個(gè)5G都要到來的時(shí)代,網(wǎng)絡(luò)資源大小的限制也顯得不那么重要。

步驟

獲取當(dāng)前APP版本號(hào)

react-native是不提供獲取版本號(hào)模塊的,所以我們需要自己去寫一個(gè)原生模塊去獲取當(dāng)前app的版本號(hào)。
傳送門:原生模塊

新建一個(gè)模塊文件夾

新建一個(gè)文件夾在android/app/src/main/java/com/your-app-name/下,這里我取名叫appinfo

新建一個(gè)類

在appinfo新建一個(gè)類為AppInfoModule.java

    package com.your-app-name.appinfo;

    import com.facebook.react.bridge.NativeModule;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.bridge.ReactContext;
    import com.facebook.react.bridge.ReactContextBaseJavaModule;
    import com.facebook.react.bridge.ReactMethod;
    import com.facebook.react.bridge.Callback;

    import com.facebook.react.uimanager.IllegalViewOperationException;
    import android.content.pm.PackageInfo;
    import android.content.pm.PackageManager;

    import java.util.Map;
    import java.util.HashMap;

    public class AppInfoModule extends ReactContextBaseJavaModule {

        public AppInfoModule(ReactApplicationContext reactContext) {
            super(reactContext);
        }

        @Override
        public String getName() {
            return "AppInfo";     //后面需要調(diào)用的模塊名
        }

        @ReactMethod
        public void getVersion(Callback successCallback) {    //后面需要調(diào)用的方法名,不需要傳參
            try {
                PackageInfo info = getPackageInfo();
                if(info != null){
                    successCallback.invoke(info.versionName);    //這里我是一回調(diào)函數(shù)的形式返回,也可以用promise的方式
                } else {
                    successCallback.invoke("");
                }
            } catch (IllegalViewOperationException e){
                successCallback.invoke("");
            }
        }
        
        private PackageInfo getPackageInfo(){
            PackageManager manager = getReactApplicationContext().getPackageManager();
            PackageInfo info = null;
            try{
                info = manager.getPackageInfo(getReactApplicationContext().getPackageName(),0);
                return info;
            } catch (Exception e){
                e.printStackTrace();
            } finally {
                return info;
            }
        }
    }

注冊(cè)模塊

用于在JavaScript 中訪問,新建一個(gè)AppInfoPackage.java

    package com.your-app-name.appinfo;

    import com.facebook.react.ReactPackage;
    import com.facebook.react.bridge.NativeModule;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.uimanager.ViewManager;
    import java.util.ArrayList;
    import java.util.Collections;
    import java.util.List;

    public class AppInfoPackage implements ReactPackage {

        @Override
            public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
                return Collections.emptyList();
        }

        @Override
        public List<NativeModule> createNativeModules(
                ReactApplicationContext reactContext) {
            List<NativeModule> modules = new ArrayList<>();
            modules.add(new AppInfoModule(reactContext));     //你的模塊
            return modules;
        }

    }

暴露方法

暴露該模塊,修改android/app/src/main/java/com/your-app-name/MainApplication.java文件的getPackages方法

    import com.your-app-name.appinfo.AppInfoPackage;
    ......
    @Override
    protected List<ReactPackage> getPackages() {
        @SuppressWarnings("UnnecessaryLocalVariable")
        List<ReactPackage> packages = new PackageList(this).getPackages();
        packages.add(new AppInfoPackage());    //導(dǎo)入注冊(cè)好的模塊
        return packages;
    }
    ......

JavaScript調(diào)用

    NativeModules.AppInfo.getVersion((version) => {
        console.log(version);
        //output 1.1.0
    });

版本校驗(yàn)

寫一個(gè)版本比較接口,把獲取到的版本號(hào)提交上去,以此來判斷是否需要更新

提供一個(gè)php的版本比較方法:

    private function compareVersion($v1,$v2) 
    {
        if(empty($v2)){
            return false;
        }
        $l1  = explode('.',$v1);
        $l2  = explode('.',$v2);
        $len = count($l1) > count($l2) ? count($l1): count($l2);
        for ($i = 0; $i < $len; $i++) {
            $n1 = $l1[$i] ? $l1[$i] : 0;
            $n2 = $l2[$i] ? $l2[$i] : 0;
            if ($n1 > $n2) {
                return true;
            } else if ($n1 < $n2) {
                return false;
            }
        }
        return false;
    }

下載新版本

版本校驗(yàn)需要更新,得到更新的地址后去下載保存到本機(jī)

獲取讀取存儲(chǔ)的權(quán)限

    ......
    (async () => {
      try {
        const permissions = [
          PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
          PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE
        ];

        const granteds = await PermissionsAndroid.requestMultiple(permissions);
        if (granteds["android.permission.ACCESS_FINE_LOCATION"] === "granted" && granteds["android.permission.READ_EXTERNAL_STORAGE"] === "granted" && granteds["android.permission.WRITE_EXTERNAL_STORAGE"] === "granted") {
        } else {
          ToastAndroid.show('授權(quán)被拒絕', ToastAndroid.LONG)
        }
      } catch (err) {
        ToastAndroid.show(err.toString(), ToastAndroid.LONG);
      }
    })();

react-native-fs下載

接入文檔react-native-fs

    import RNFS from 'react-native-fs';

    const downloadDest = `${RNFS.DownloadDirectoryPath}/app.apk`;    

    const options = {
      fromUrl: UPDATEURL,    //更新包的地址
      toFile: downloadDest,  //下載后保存的地址
      background: true,
      begin: (res) => {
      }
    };
    const ret = RNFS.downloadFile(options);
    ret.promise.then(res => {
        // 下載成功
    }).catch(err => {
      ToastAndroid.show(err, ToastAndroid.LONG);
    });

安裝

下載成功后,考慮到用戶體驗(yàn),還得有一個(gè)自動(dòng)安裝吧~
當(dāng)然去打開指定路徑的文件也是需要我們自己寫原生模塊的。

按照上面的流程,我新建了update模塊和UpdateModule.java類
這里流程很上面的獲取版本號(hào)一致,我直接貼UpdateModule.java的代碼

    package com.your-app-name.update;

    import com.facebook.react.bridge.NativeModule;
    import com.facebook.react.bridge.ReactApplicationContext;
    import com.facebook.react.bridge.ReactContext;
    import com.facebook.react.bridge.ReactContextBaseJavaModule;
    import com.facebook.react.bridge.ReactMethod;
    import com.facebook.react.bridge.Callback;

    import android.content.Intent;
    import android.net.Uri;
    import android.os.Build;
    //使用FileProvider需要一定的配置
    import androidx.core.content.FileProvider;

    import java.io.File;
    import java.util.Map;
    import java.util.HashMap;

    public class UpdateModule extends ReactContextBaseJavaModule {
    private ReactApplicationContext context;
        public UpdateModule(ReactApplicationContext reactContext) {
        super(reactContext);
            context = reactContext;
        }

        @Override
        public String getName() {
            return "Update";
        }

        @ReactMethod
        public void installApp(final String path, Callback successCallback) {
            try {
                if (Build.VERSION.SDK_INT >= 24) {    //android 7.0以后,處于安全考慮必須使用FileProvider打開文件
                    Uri contentUri = FileProvider.getUriForFile(context, "com.your-app-name.fileprovider", new File(path));

                    Intent intent = new Intent(Intent.ACTION_VIEW)
                    .setDataAndType(contentUri, "application/vnd.android.package-archive");

                    intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

                    context.startActivity(intent);
                } else {
                    Intent intent = new Intent(Intent.ACTION_VIEW).setDataAndType(Uri.parse("file://" + path), "application/vnd.android.package-archive").setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                    context.startActivity(intent);
                }
                successCallback.invoke("安裝成功");
            } catch (Exception e) {
                successCallback.invoke("安裝失敗");
            }
        }
    }

更新下邏輯代碼,下載成功后自動(dòng)安裝

    ......
    ret.promise.then(res => {
        // 下載成功
      NativeModules.Update.installApp(downloadDest, (res) => {
          // 安裝成功
      });
    }).catch(err => {
      ToastAndroid.show(err, ToastAndroid.LONG);
    });
    ......
?著作權(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)容