正文
升級是應(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();
...
});
}
注意問題
- 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)修改.
- android8無法自動打開安裝程序(權(quán)限拒絕)
因?yàn)閍ndroid8的權(quán)限問題,apk下載完成后無法正常自動打開安裝程序,所以必須將平臺targetSdkVersion版本進(jìn)行修改.
修改latforms\android\app\src\main\AndroidManifest.xml里面targetSdkVersion的值為23.(所以得先添加平臺,修改后再編譯)
- 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