升级是应用最基本的功能,由于不多有一个应用发布后不在进行后期维护!
原生应用的升级比较常见,可是现在混合应用大热,由于项目,我就基于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