升級是應用最基本的功能,由於不多有一個應用發佈後不在進行後期維護!
原生應用的升級比較常見,可是現在混合應用大熱,由於項目,我就基於ionic框架實現了一個簡單的升級,根據服務器端返回來肯定強制仍是非強制更新.
ionic cordova plugin add cordova-plugin-file npm install --save @ionic-native/file
ionic cordova plugin add cordova-plugin-file-transfer npm install --save @ionic-native/file-transfer
ionic cordova plugin add cordova-plugin-app-version npm install --save @ionic-native/app-version
ionic cordova plugin add cordova-plugin-uid npm install --save @ionic-native/uid
ionic cordova plugin add cordova-plugin-file-opener2 npm install --save @ionic-native/file-opener
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; } }
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(); ... }); }
緣由:
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
由於android8的權限問題,apk下載完成後沒法正常自動打開安裝程序,因此必須將平臺targetSdkVersion版本進行修改.服務器
修改latformsandroidappsrcmainAndroidManifest.xml裏面targetSdkVersion的值爲23.(因此得先添加平臺,修改後再編譯)app
方案一: 在/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