react-native App更新方案

前言

用react-native(一下简称RN)开发的app的更新方案有很多,其中比较火的是热更新方案,有官方推荐的pushy和微软的code-push
文档很详细,接入也比较简单
这里主要介绍一种最传统的更新方案,也是很多原生开发在使用的方案——全量更新

全量更新

顾名思义,即每次更新通过http去下载新版本包去然后去做一个覆盖安装,这种做法在更新迭代中会避免很多不必要的麻烦,而且在这个5G都要到来的时代,网络资源大小的限制也显得不那么重要。

步骤

获取当前APP版本号

react-native是不提供获取版本号模块的,所以我们需要自己去写一个原生模块去获取当前app的版本号。
传送门:原生模块

新建一个模块文件夹

新建一个文件夹在android/app/src/main/java/com/your-app-name/下,这里我取名叫appinfo

新建一个类

在appinfo新建一个类为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";     //后面需要调用的模块名
        }

        @ReactMethod
        public void getVersion(Callback successCallback) {    //后面需要调用的方法名,不需要传参
            try {
                PackageInfo info = getPackageInfo();
                if(info != null){
                    successCallback.invoke(info.versionName);    //这里我是一回调函数的形式返回,也可以用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;
            }
        }
    }

注册模块

用于在JavaScript 中访问,新建一个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 createViewManagers(ReactApplicationContext reactContext) {
                return Collections.emptyList();
        }

        @Override
        public List createNativeModules(
                ReactApplicationContext reactContext) {
            List 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 getPackages() {
        @SuppressWarnings("UnnecessaryLocalVariable")
        List packages = new PackageList(this).getPackages();
        packages.add(new AppInfoPackage());    //导入注册好的模块
        return packages;
    }
    ......

JavaScript调用

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

版本校验

写一个版本比较接口,把获取到的版本号提交上去,以此来判断是否需要更新

提供一个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;
    }

下载新版本

版本校验需要更新,得到更新的地址后去下载保存到本机

获取读取存储的权限

    ......
    (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('授权被拒绝', 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);
    });

安装

下载成功后,考虑到用户体验,还得有一个自动安装吧~
当然去打开指定路径的文件也是需要我们自己写原生模块的。

按照上面的流程,我新建了update模块和UpdateModule.java类
这里流程很上面的获取版本号一致,我直接贴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("安装失败");
            }
        }
    }

更新下逻辑代码,下载成功后自动安装

    ......
    ret.promise.then(res => {
        // 下载成功
      NativeModules.Update.installApp(downloadDest, (res) => {
          // 安装成功
      });
    }).catch(err => {
      ToastAndroid.show(err, ToastAndroid.LONG);
    });
    ......

你可能感兴趣的:(react-native App更新方案)