升级是应用最基本的功能,因为很少有一个应用发布后不在进行后期维护! 原生应用的升级比较常见,但是如今混合应用大热,因为项目,我就基于ionic框架实现了一个简单的升级,根据服务器端返回来确定强制还是非强制更新.
插件安装 file(访问文件) 1 2 ionic cordova plugin add cordova-plugin-file npm install --save @ionic-native/file
File Transfer(上载和下载文件) 1 2 ionic cordova plugin add cordova-plugin-file-transfer npm install --save @ionic-native/file-transfer
App Version(用来获取版本号) 1 2 ionic cordova plugin add cordova-plugin-app-version npm install --save @ionic-native/app-version
Uid(获取设备标识,主要用于灰度升级,只升级用不到) 1 2 ionic cordova plugin add cordova-plugin-uid npm install --save @ionic-native/uid
File Opener(打开下载完成的apk文件) 1 2 ionic cordova plugin add cordova-plugin-file-opener2 npm install --save @ionic-native/file-opener
Android Permissions(获取运行时权限) 1 2 ionic cordova plugin add cordova-plugin-android-permissions npm install --save @ionic-native/android-permissions
升级服务 在src/app文件夹下创建NativeService.ts升级服务.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 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 ) { } detectionUpgrade ( ) { this .getVersionNumber ().then ((version ) => { this .getImei ().then ((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 ; 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 .storagePermissions ().then (res => { if (res) { this .downloadApp (apkUrl); } }) } }] }).present (); } } }, error => { }) }) }) } 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' ; 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 ) { title && (title.innerHTML = '下载完成,请您完成安装' ); } else { title && (title.innerHTML = '当前已下载:' + num + '%' ); } }); } getVersionNumber (): Promise <string> { return new Promise ((resolve ) => { this .appVersion .getVersionNumber ().then ((value: string ) => { resolve (value); }).catch (err => { console .log ('getVersionNumber:' + err); }); }); } 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 ) { this .platform .exitApp (); } return ; } return this .uid .IMEI } 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 ) { this .platform .exitApp (); } return true ; } return true ; } }
使用 在app.module.ts中注入需要的服务 1 2 3 4 5 6 7 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";
1 2 3 4 5 6 7 8 9 10 11 providers: [ FileTransfer, File, NativeService, AppVersion, Uid, AndroidPermissions, FileOpener, FileTransferObject, ]
使用 在app.component.ts使用
1 2 3 4 5 6 7 constructor(private nativeService: NativeService,...) { platform.ready().then(() => { ... this.nativeService.detectionUpgrade(); ... }); }
注意问题
android升级后通过fileOpener打开apk不出现完成打开按钮
原因:
fileOpener2插件问题
处理方法:
找到platforms下的Android源码,找到fileOpener的Java类,添加如下代码:
一般该类目录为:io.github.pwlin.cordova.plugins.fileopener2;
1 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 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); } }
这样修改如果每次重新生成平台都得改,也可以直接修改插件里面.在安装插件时就已经修改.
android8无法自动打开安装程序(权限拒绝)
因为android8的权限问题,apk下载完成后无法正常自动打开安装程序,所以必须将平台targetSdkVersion版本进行修改.
修改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中添加如下代码.
1 2 3 4 5 configurations.all { resolutionStrategy { force 'com.android.support:support-v4:27.1.0' } }
方案二: 下载(推荐)
安装cordova-android-support-gradle-release插件
1 ionic cordova plugin add cordova-android-support-gradle-release --fetch