React-Native 原生 APP 更新

当一个 APP在运行的时候, 开发者想要将本身的代码更新到用户的手机上时, 通常都有两种方案, 一是热更新, 二就是 APP 更新.
热更新暂且不说,这篇文章就讲讲 APP 如何更新react

App更新流程

  1. 在 App 打开时请求接口或文件, 获取远程版本/版本更新说明/地址等等重用信息
  2. 经过库或者原生方案, 获取 App 的当前版本
  3. 比较远程版本和当前版本的区别(可使用库,也能够本身写一个比较方案)
  4. 经过获取到的连接进行操做(能够跳转到对应网站下载,相似蒲公英这种,能够是 apk 连接, 经过安卓原生方法下载, 也能够是 App Store 连接)

大体的流程图

详细说明:

  1. 这些远程信息,能够是接口, 这样能够有一个中台来控制, 固然也能够是一个文件, 让运维来控制
    关于信息,不止于远程版本, 在项目里还能够添加其余属性,如: versionCode, versionCodeSwitch , notUpdate , deleteApp
    1.1 versionCode 经过 code 来升级版本,通常是一个数字(在 ios 里提交 App Store 的时候有须要用到的地方), 这样 versionName 并不会增长, 可是若是添加了 versionCode, 若是要升级 versionName, versionCode 也须要增长
    1.2 versionCodeSwitch 用来控制是否要根据versionCode来更新, 通常我都是在测试和其余环境开启,生产环境关闭的
    1.3 notUpdate 是否要根据远程信息来更新, 通常都是开启状态
    1.3 deleteApp 安卓 app 须要卸载从新安装, 由于直接安装可能存在某些问题, 将会使用此信息,先删除 APP, 再从新下载
  2. 获取当前手机的信息,方案较多, 我使用的是 react-native-device-info 这个库, 这个库里面提供的信息较全, 固然也可使用原生方法, 来获取APP的信息
  3. 关于本地版本号和原生版本号之间的对比也是可使用库,也能够本身写, 这边推荐两个库,下载量都是百万以上的: semver-comparecompare-versions, 这边附上个人 versionName 比较方案, 较为简陋:
    /**
     * 比较两版本号 
     * @param currentVersion 
     * @return boolean 
     * true=须要更新 false=不须要 
     */
    compareVersion = (currentVersion: string): boolean => {
        const {versionName: remoteVersion} = this.remoteInfo || {}
        if (!remoteVersion) {
            return false
        }
        if (currentVersion === remoteVersion) {
            return false
        }
        const currentVersionArr = currentVersion.split('.')
        const remoteVersionArr = remoteVersion.split('.')
        for (let i = 0; i < 3; i++) {
            if (Number(currentVersionArr[i]) < Number(remoteVersionArr[i])) {
                return true
            }
        } 
        return false
    }
  4. 关于下载 app 有不少方案, 最简单的就是跳转连接到第三方平台, 像蒲公英这样的, 使用 RN 提供的 Linking 方法来直接跳转
    固然安卓是能够直接经过本身提供的地址下载的, 这里提供一个方法(此方法来源于网络):
    @ReactMethod
    public void installApk(String filePath, String fileProviderAuthority) {
        File file = new File(filePath);
        if (!file.exists()) {
            Log.e("RNUpdater", "installApk: file doe snot exist '" + filePath + "'");
            // FIXME this should take a promise and fail it
     return;
        }
        if (Build.VERSION.SDK_INT >= 24) {
            // API24 and up has a package installer that can handle FileProvider content:// URIs
     Uri contentUri;
            try {
                contentUri = FileProvider.getUriForFile(getReactApplicationContext(), fileProviderAuthority, file);
            } catch (Exception e) {
                // FIXME should be a Promise.reject really
     Log.e("RNUpdater", "installApk exception with authority name '" + fileProviderAuthority + "'", e);
                throw e;
            }
            Intent installApp = new Intent(Intent.ACTION_INSTALL_PACKAGE);
            installApp.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            installApp.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            installApp.setData(contentUri);
            installApp.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, reactContext.getApplicationInfo().packageName);
            reactContext.startActivity(installApp);
        } else {
            // Old APIs do not handle content:// URIs, so use an old file:// style
     String cmd = "chmod 777 " + file;
            try {
                Runtime.getRuntime().exec(cmd);
            } catch (Exception e) {
                e.printStackTrace();
            }
            Intent intent = new Intent(Intent.ACTION_VIEW);
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            intent.setDataAndType(Uri.parse("file://" + file), "application/vnd.android.package-archive");
            reactContext.startActivity(intent);
        }
    }
    若是是咱们本身提供下载服务,须要注意的是带宽, 若是网速过慢则用户体验过差, 还有就会带来更多的流量消耗, 其中的取舍,须要开发者决定

更新APP信息

在打包时, 经过脚本更新接口或者文件信息, 固然这个得看具体的打包方案
好比我如今的方案是使用 Jenkins 打包, 在打包时使用 shell 脚本更新对应信息(有须要也可使用其余语言脚本):android

  1. 首先定义须要获取的文件地址
    androidVersionFilePath="$WORKSPACE/android/app/build.gradle"  // 经过此文件获取安卓的版本信息
    iosVersionFilePath="$WORKSPACE/ios/veronica/Info.plist" // 经过此文件获取iOS的版本信息
    changeLogPath="$WORKSPACE/change.log" // 将版本更新信息存储在此文件中
  2. 经过文件地址, 获取打完包后的版本信息
    getAndroidVersion(){
      androidVersion=$(cat $androidVersionFilePath  | grep "versionName" | awk '{print $2}' | sed 's/\"//g')
      androidCode=$(cat $androidVersionFilePath  | grep "versionCode " | awk '{print $2}' | sed 's/\"//g')
      androidDelete=$(cat $androidVersionFilePath  | grep "deleteApp" | awk '{print $4}' | sed 's/\"//g')
      return 0
    }
    
    getIOSVersion(){
      rows=$(awk '/CFBundleShortVersionString/ {getline; print}' $iosVersionFilePath)
      iosVersion=$(echo "$rows" | sed -ne 's/<string>\(.*\)<\/string>/\1/p')
      iosVersion=$(echo "$iosVersion" | sed 's/^[[:space:]]*//')
    
      rows2=$(awk '/VersionCode/ {getline; print}' $iosVersionFilePath)
      iosCode=$(echo "$rows2" | sed -ne 's/<string>\(.*\)<\/string>/\1/p')
      iosCode=$(echo "$iosCode" | sed 's/^[[:space:]]*//')
      return 0
    }
    
    desc=$(cat $changeLogPath | tr "\n" "#")
  3. 替换现有文件中的信息:
    sed -i '' "s/\"releaseInfo\":.*$/\"releaseInfo\": \"$desc\"/"  $JsonPath/$fileName
    sed -i '' "s/\"versionName\":.*$/\"versionName\": \"$versionName\",/"  $JsonPath/$fileName
    sed -i '' "s/\"versionCode\":.*$/\"versionCode\": \"$versionCode\",/"  $JsonPath/$fileName
    sed -i '' "s/\"deleteApp\":.*$/\"deleteApp\": \"$deleteApp\",/"  $JsonPath/$fileName
    个人文件是以 json 做为格式的,说明文字是能够任意填写的,会触发一些解析问题:
    • 不容许出现会形成 JSON.parse 解析失败的符号, 如 \ , ````, \n ,\r, \" 等等
    • 由于说明文字的换行我是经过 # 切割的, 因此也不容许出现这个符号

大体流程图

总结

关于 APP 原生版本的更新流程基本就是这样,固然这个流程不光适用 APP, 也能够用于 PC 软件的更新
除了原生版本的更新,还有热更新, 也是很是重要的, 我将会在后面的博客中解析他ios

shell

相关文章
相关标签/搜索