ionic應用自動升級

正文

升級是應用最基本的功能,由於不多有一個應用發佈後不在進行後期維護!
原生應用的升級比較常見,可是現在混合應用大熱,由於項目,我就基於ionic框架實現了一個簡單的升級,根據服務器端返回來肯定強制仍是非強制更新.

插件安裝

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(獲取設備標識,主要用於灰度升級,只升級用不到)

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(獲取運行時權限)

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

升級服務

在src/app文件夾下建立NativeService.ts升級服務.javascript

/**
 * 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最新版本號,而後與當前app版本號(this.getVersionNumber())對比
    //版本號不同就須要申請,不須要升級就return

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

        let body = {tag: 'update', data: {type: "chcnav", terminal: imei, version: version}} //參數
        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) { //是否強制升級(有些版本更迭是強制的,因此用戶必須安裝)
              this.alertCtrl.create({
                title: '升級提示',
                subTitle: '發現新版本,是否當即升級?',
                enableBackdropDismiss: false,
                buttons: [{
                  text: '肯定',
                  handler: () => {
                    this.storagePermissions().then(res => {
                      if (res) {
                        this.downloadApp(apkUrl);
                      }
                    })
                  }
                }]
              }).present();
            } else {
              this.alertCtrl.create({
                title: '升級提示',
                subTitle: '發現新版本,是否當即升級?',
                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: '下載進度',
      subTitle: '當前已下載: 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 => {
      // 存儲權限出問題
      this.toastCtrl.create({
        message: '存儲apk失敗,請檢查您是否關閉了存儲權限!',
        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 = '當前已下載:' + num + '%');
      }
    });

  }


  /**
   * 得到app版本號,如0.01
   * @description  對應/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(); // 由於必須,因此被拒絕就退出app
      }

      return;
    }

    return this.uid.IMEI
  }


  /**
   * 存儲運行時權限
   * apk下載時請求存儲權限
   *
   */
  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('存儲權限被拒絕');
        this.platform.exitApp(); // 由於必須,因此被拒絕就退出app
      }
      return true;
    }

    return true;
  }

}

使用

在app.module.ts中注入須要的服務

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使用java

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

注意問題

  1. android升級後經過fileOpener打開apk不出現完成打開按鈕

緣由: fileOpener2插件問題android

處理方法:git

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

通常該類目錄爲:io.github.pwlin.cordova.plugins.fileopener2;web

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

這樣修改若是每次從新生成平臺都得改,也能夠直接修改插件裏面.在安裝插件時就已經修改.npm

  1. android8沒法自動打開安裝程序(權限拒絕)

由於android8的權限問題,apk下載完成後沒法正常自動打開安裝程序,因此必須將平臺targetSdkVersion版本進行修改.服務器

修改latformsandroidappsrcmainAndroidManifest.xml裏面targetSdkVersion的值爲23.(因此得先添加平臺,修改後再編譯)app

  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
相關文章
相關標籤/搜索