React-Native 原生 APP 更新

當一個 APP在運行的時候, 開發者想要將本身的代碼更新到用戶的手機上時, 通常都有兩種方案, 一是熱更新, 二就是 APP 更新.
熱更新暫且不說,這篇文章就講講 APP 如何更新

App更新流程

  1. 在 App 打開時請求接口或文件, 獲取遠程版本/版本更新說明/地址等等重用信息
  2. 經過庫或者原生方案, 獲取 App 的當前版本
  3. 比較遠程版本和當前版本的區別(可使用庫,也能夠本身寫一個比較方案)
  4. 經過獲取到的連接進行操做(能夠跳轉到對應網站下載,相似蒲公英這種,能夠是 apk 連接, 經過安卓原生方法下載, 也能夠是 App Store 連接)

大體的流程圖

image

詳細說明:

  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 比較方案, 較爲簡陋:react

    /**
     * 比較兩版本號 
     * @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 方法來直接跳轉
    固然安卓是能夠直接經過本身提供的地址下載的, 這裏提供一個方法(此方法來源於網絡):android

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

    若是是咱們本身提供下載服務,須要注意的是帶寬, 若是網速過慢則用戶體驗過差, 還有就會帶來更多的流量消耗, 其中的取捨,須要開發者決定ios

更新APP信息

在打包時, 經過腳本更新接口或者文件信息, 固然這個得看具體的打包方案
好比我如今的方案是使用 Jenkins 打包, 在打包時使用 shell 腳本更新對應信息(有須要也可使用其餘語言腳本):shell

  1. 首先定義須要獲取的文件地址npm

    androidVersionFilePath="$WORKSPACE/android/app/build.gradle"  // 經過此文件獲取安卓的版本信息
    iosVersionFilePath="$WORKSPACE/ios/veronica/Info.plist" // 經過此文件獲取iOS的版本信息
    changeLogPath="$WORKSPACE/change.log" // 將版本更新信息存儲在此文件中
  2. 經過文件地址, 獲取打完包後的版本信息json

    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. 替換現有文件中的信息:react-native

    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 做爲格式的,說明文字是能夠任意填寫的,會觸發一些解析問題:promise

    • 不容許出現會形成 JSON.parse 解析失敗的符號, 如 \ , ``, \n ,\r, \" 等等
    • 由於說明文字的換行我是經過 # 切割的, 因此也不容許出現這個符號

大體流程圖

image

總結

關於 APP 原生版本的更新流程基本就是這樣,固然這個流程不光適用 APP, 也能夠用於 PC 軟件的更新
除了原生版本的更新,還有熱更新, 也是很是重要的, 我將會在後面的博客中解析他網絡

app

相關文章
相關標籤/搜索