ionic應(yīng)用自動升級

正文

升級是應(yīng)用最基本的功能,因?yàn)楹苌儆幸粋€應(yīng)用發(fā)布后不在進(jìn)行后期維護(hù)!
原生應(yīng)用的升級比較常見,但是如今混合應(yīng)用大熱,因?yàn)轫?xiàng)目,我就基于ionic框架實(shí)現(xiàn)了一個簡單的升級,根據(jù)服務(wù)器端返回來確定強(qiáng)制還是非強(qiáng)制更新.

插件安裝

file(訪問文件)

ionic cordova plugin add cordova-plugin-file
npm install --save @ionic-native/file

File Transfer(上載和下載文件)

ionic cordova plugin add cordova-plugin-file-transfer
npm install --save @ionic-native/file-transfer

App Version(用來獲取版本號)

ionic cordova plugin add cordova-plugin-app-version
npm install --save @ionic-native/app-version

Uid(獲取設(shè)備標(biāo)識,主要用于灰度升級,只升級用不到)

ionic cordova plugin add cordova-plugin-uid
npm install --save @ionic-native/uid

File Opener(打開下載完成的apk文件)

ionic cordova plugin add cordova-plugin-file-opener2
npm install --save @ionic-native/file-opener

Android Permissions(獲取運(yùn)行時權(quán)限)

ionic cordova plugin add cordova-plugin-android-permissions
npm install --save @ionic-native/android-permissions

升級服務(wù)

在src/app文件夾下創(chuàng)建NativeService.ts升級服務(wù).

/**
 * Created by llcn on 11-29.
 * 升級模塊
 */
import {Injectable} from '@angular/core';
import {Platform, AlertController} from 'ionic-angular';
import {AppVersion} from '@ionic-native/app-version';
import {File} from '@ionic-native/file';
import {FileTransfer, FileTransferObject} from "@ionic-native/file-transfer";
import {FileOpener} from '@ionic-native/file-opener';
import {Uid} from "@ionic-native/uid";
import {AndroidPermissions} from "@ionic-native/android-permissions";
import {ToastController} from 'ionic-angular';
import {HttpClient} from "@angular/common/http";

@Injectable()
export class NativeService {

  constructor(private http: HttpClient,
              private platform: Platform,
              private alertCtrl: AlertController,
              private transfer: FileTransfer,
              private appVersion: AppVersion,
              private file: File,
              private fileOpener: FileOpener,
              private uid: Uid,
              private toastCtrl: ToastController,
              private androidPermissions: AndroidPermissions) {
  }


  /**
   * 檢查app是否需要升級
   */
  detectionUpgrade() {
    //這里連接后臺獲取app最新版本號,然后與當(dāng)前app版本號(this.getVersionNumber())對比
    //版本號不一樣就需要申請,不需要升級就return

    this.getVersionNumber().then((version) => { // 獲取版本號
      this.getImei().then((imei) => { // 獲取imei,用于灰度升級,有些需求不需要這一步

        let body = {tag: 'update', data: {type: "chcnav", terminal: imei, version: version}} //參數(shù)
        const url = 'xxx.xxx.xxx'; // 接口地址
        
        this.http.get(url).subscribe(res => {
          // 判斷版本號
          if (res && ((res as any).status > 0) && ((res as any).data.version !== version)) { 
            let apkUrl =  (res as any).data.path; // apk下載路徑
            if ((res as any).data.force_update) { //是否強(qiáng)制升級(有些版本更迭是強(qiáng)制的,所以用戶必須安裝)
              this.alertCtrl.create({
                title: '升級提示',
                subTitle: '發(fā)現(xiàn)新版本,是否立即升級?',
                enableBackdropDismiss: false,
                buttons: [{
                  text: '確定',
                  handler: () => {
                    this.storagePermissions().then(res => {
                      if (res) {
                        this.downloadApp(apkUrl);
                      }
                    })
                  }
                }]
              }).present();
            } else {
              this.alertCtrl.create({
                title: '升級提示',
                subTitle: '發(fā)現(xiàn)新版本,是否立即升級?',
                enableBackdropDismiss: false,
                buttons: [{
                  text: '取消'
                }, {
                  text: '確定',
                  handler: () => {
                    // this.downloadApp(apkUrl);
                    this.storagePermissions().then(res => {
                      if (res) {
                        this.downloadApp(apkUrl);
                      }
                    })
                  }
                }]
              }).present();
            }
          }
        }, error => {
        })
      })
    })
  }

  /**
   * 下載安裝app
   */
  downloadApp(url: any) {
    let options;
    options = {
      title: '下載進(jìn)度',
      subTitle: '當(dāng)前已下載: 0%',
      enableBackdropDismiss: false
    }
    let alert = this.alertCtrl.create(options);
    alert.present();

    const fileTransfer: FileTransferObject = this.transfer.create();
    console.log(this.file.externalRootDirectory)
    const apk = this.file.externalRootDirectory + 'android.apk'; //apk保存的目錄

    fileTransfer.download(url, apk).then(() => {
      this.fileOpener.open(apk, 'application/vnd.android.package-archive').then(() => {
      }).catch(e => {
        console.log('Error opening file' + e)
      });
    }).catch(err => {
      // 存儲權(quán)限出問題
      this.toastCtrl.create({
        message: '存儲apk失敗,請檢查您是否關(guān)閉了存儲權(quán)限!',
        duration: 3000,
        position: 'bottom'
      }).present();
    });

    fileTransfer.onProgress((event: ProgressEvent) => {
      let num = Math.floor(event.loaded / event.total * 100);
      let title = document.getElementsByClassName('alert-sub-title')[0];
      if (num === 100) {
        // alert.dismiss();
        title && (title.innerHTML = '下載完成,請您完成安裝');
      } else {
        title && (title.innerHTML = '當(dāng)前已下載:' + num + '%');
      }
    });

  }


  /**
   * 獲得app版本號,如0.01
   * @description  對應(yīng)/config.xml中version的值
   * @returns {Promise<string>}
   */
  getVersionNumber(): Promise<string> {
    return new Promise((resolve) => {
      this.appVersion.getVersionNumber().then((value: string) => {
        resolve(value);
      }).catch(err => {
        console.log('getVersionNumber:' + err);
      });
    });
  }


  /**
   * 獲取imei號
   */
  async getImei() {
    const {hasPermission} = await this.androidPermissions.checkPermission(
      this.androidPermissions.PERMISSION.READ_PHONE_STATE
    );

    if (!hasPermission) {
      const result = await this.androidPermissions.requestPermission(
        this.androidPermissions.PERMISSION.READ_PHONE_STATE
      );

      if (!result.hasPermission) {
        // throw new Error('Permissions required');
        this.platform.exitApp(); // 因?yàn)楸仨?所以被拒絕就退出app
      }

      return;
    }

    return this.uid.IMEI
  }


  /**
   * 存儲運(yùn)行時權(quán)限
   * apk下載時請求存儲權(quán)限
   *
   */
  async storagePermissions() {
    const {hasPermission} = await this.androidPermissions.checkPermission(
      this.androidPermissions.PERMISSION.READ_EXTERNAL_STORAGE
    );

    if (!hasPermission) {
      const result = await this.androidPermissions.requestPermission(
        this.androidPermissions.PERMISSION.READ_EXTERNAL_STORAGE
      );

      if (!result.hasPermission) {
        // throw new Error('存儲權(quán)限被拒絕');
        this.platform.exitApp(); // 因?yàn)楸仨?所以被拒絕就退出app
      }
      return true;
    }

    return true;
  }

}


使用

在app.module.ts中注入需要的服務(wù)

import {File} from "@ionic-native/file";
import {FileTransfer, FileTransferObject} from '@ionic-native/file-transfer';
import {AppVersion} from '@ionic-native/app-version';
import {AndroidPermissions} from '@ionic-native/android-permissions/';
import {Uid} from '@ionic-native/uid';
import {NativeService} from './NativeService'
import {FileOpener} from "@ionic-native/file-opener";
  providers: [
    FileTransfer,
    File,
    NativeService,
    AppVersion,
    Uid,
    AndroidPermissions,
    FileOpener,
    FileTransferObject,
  ]

使用

在app.component.ts使用

 constructor(private nativeService: NativeService,...) {
    platform.ready().then(() => {
     ...
      this.nativeService.detectionUpgrade();
     ...
    });
  }

注意問題

  1. android升級后通過fileOpener打開apk不出現(xiàn)完成打開按鈕

原因: fileOpener2插件問題

處理方法:

找到platforms下的Android源碼,找到fileOpener的Java類,添加如下代碼:

一般該類目錄為:io.github.pwlin.cordova.plugins.fileopener2;

intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

private void _open(String fileArg, String contentType, Boolean openWithDefault, CallbackContext callbackContext) throws JSONException {
    String fileName = "";
    try {
      CordovaResourceApi resourceApi = webView.getResourceApi();
      Uri fileUri = resourceApi.remapUri(Uri.parse(fileArg));
      fileName = this.stripFileProtocol(fileUri.toString());
    } catch (Exception e) {
      fileName = fileArg;
    }
    File file = new File(fileName);
    if (file.exists()) {
      try {
        Uri path = Uri.fromFile(file);
        Intent intent = new Intent(Intent.ACTION_VIEW);
        if ((Build.VERSION.SDK_INT >= 23 && !contentType.equals("application/vnd.android.package-archive")) || ((Build.VERSION.SDK_INT == 24 || Build.VERSION.SDK_INT == 25) && contentType.equals("application/vnd.android.package-archive"))) {
 
          Context context = cordova.getActivity().getApplicationContext();
          path = FileProvider.getUriForFile(context, cordova.getActivity().getPackageName() + ".opener.provider", file);
          intent.setDataAndType(path, contentType);
          intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
          intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
          intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//這里
          //intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
 
          List<ResolveInfo> infoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
          for (ResolveInfo resolveInfo : infoList) {
            String packageName = resolveInfo.activityInfo.packageName;
            context.grantUriPermission(packageName, path, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
          }
        } else {
          intent.setDataAndType(path, contentType);
          intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
          intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);//這里
        }
        /*
         * @see
         * http://stackoverflow.com/questions/14321376/open-an-activity-from-a-cordovaplugin
         */
        if (openWithDefault) {
          cordova.getActivity().startActivity(intent);
        } else {
          cordova.getActivity().startActivity(Intent.createChooser(intent, "Open File in..."));
        }
 
        callbackContext.success();
      } catch (android.content.ActivityNotFoundException e) {
        JSONObject errorObj = new JSONObject();
        errorObj.put("status", PluginResult.Status.ERROR.ordinal());
        errorObj.put("message", "Activity not found: " + e.getMessage());
        callbackContext.error(errorObj);
      }
    } else {
      JSONObject errorObj = new JSONObject();
      errorObj.put("status", PluginResult.Status.ERROR.ordinal());
      errorObj.put("message", "File not found");
      callbackContext.error(errorObj);
    }
  }

這樣修改如果每次重新生成平臺都得改,也可以直接修改插件里面.在安裝插件時就已經(jīng)修改.

  1. android8無法自動打開安裝程序(權(quán)限拒絕)

因?yàn)閍ndroid8的權(quán)限問題,apk下載完成后無法正常自動打開安裝程序,所以必須將平臺targetSdkVersion版本進(jìn)行修改.

修改latforms\android\app\src\main\AndroidManifest.xml里面targetSdkVersion的值為23.(所以得先添加平臺,修改后再編譯)

  1. error: resource android:attr/fontVariationSettings resource android:attr/ttcIndex not found.

方案一: 在/platforms/android/build.gradle和/platforms/android/app/build.gradle中添加如下代碼.

configurations.all {
    resolutionStrategy {
        force 'com.android.support:support-v4:27.1.0'
    }
}

方案二: 下載(推薦)

安裝cordova-android-support-gradle-release插件

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

相關(guān)閱讀更多精彩內(nèi)容

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