flutter app内更新升级 爱被打了一巴掌 2023-06-29 05:53 1阅读 0赞 用flutter开发了一个简单的跨平台app,在网上找了很多 app内升级的博客,大部分都是复制的,讲的不全,不过有一篇好文章推荐给大家,重点是引用插件的版本尽量和博客中的一致,否则编译时各种报错,[Flutter 项目 app迭代更新][Flutter _ app],我开始引用各个插件最新版本,结果一直编译失败 下面将分详细说说,为那些初学者提供思路,并把[demo分享出来][demo] ###### 1.有两种方法实现app升级 ###### * 1.直接使用url\_launcher插件跳转本地浏览器下载,版本号没有要求 * 2.使用多个flutter插件,一起达到下载升级效果 ###### 2.大概思路 ###### * 1.进入页面,请求服务端接口,对比服务端的版本与当前手机版本是否一致 * 2.不一致则提示用户可以升级 * 3.如果是android:则下载apk文件,存入手机某个位置,完成后打开该apk进行安装 * 4.如果是ios:则通过url\_launcher插件,打开 app\_store中你们应用的链接地址,用户自己选择是否升级 下面看看手机上的效果 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0_size_16_color_FFFFFF_t_70] ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0_size_16_color_FFFFFF_t_70 1] * 使用 url\_launcher插件方式,会提示使用本地浏览器下载 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0_size_16_color_FFFFFF_t_70 2] ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0_size_16_color_FFFFFF_t_70 3] 最后都能进入安装界面 ![在这里插入图片描述][watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0_size_16_color_FFFFFF_t_70 4] ###### 3.代码实现 ###### * 1.引用插件,千万要注意版本,我最开始使用的最新版,结果一直编译失败 dependencies: flutter: sdk: flutter dio: ^3.0.8 package_info: ^0.4.0+13 permission_handler: 3.2.0 flutter_downloader: 1.1.7 path_provider: 1.5.1 open_file: ^1.3.0 url_launcher: 5.1.2 progress_dialog: 1.2.0 * 2.在android/app/src/main/AndroidManifest.xml中添加配置 <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <application .....> .....其他已有内容省略了 <provider android:name="vn.hunghd.flutterdownloader.DownloadedFileProvider" android:authorities="${applicationId}.flutter_downloader.provider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths"/> </provider> <provider android:name="androidx.work.impl.WorkManagerInitializer" android:authorities="${applicationId}.workmanager-init" android:enabled="false" android:exported="false" /> <provider android:name="vn.hunghd.flutterdownloader.FlutterDownloaderInitializer" android:authorities="${applicationId}.flutter-downloader-init" android:exported="false"> <meta-data android:name="vn.hunghd.flutterdownloader.MAX_CONCURRENT_TASKS" android:value="5" /> </provider> </application> * 3.服务端返回的数据集如下: { "android_version" : "1.0.2", "android_msg" : "对系统进行了优化,修复了多个bug", "android_url" : "http://www.xxx.com/flutterApp.apk" } * 4.代码实现,代码比较长,都很简单且有注释,请细心阅读一下 import 'dart:convert'; import 'dart:io'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_downloader/flutter_downloader.dart'; import 'package:open_file/open_file.dart'; import 'package:package_info/package_info.dart'; import 'package:path_provider/path_provider.dart'; import 'package:url_launcher/url_launcher.dart'; import 'package:permission_handler/permission_handler.dart'; import 'Http.dart'; import 'package:progress_dialog/progress_dialog.dart'; //定义apk的名称,与下载进度dialog String apkName ='flutterApp.apk'; ProgressDialog pr; class Upgrade extends StatefulWidget { Upgrade({ Key key}) : super(key: key); @override _UpgradeState createState() => _UpgradeState(); } class _UpgradeState extends State<Upgrade> { @override void initState() { super.initState(); //检查是否有更新 checkUpdate(context); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text('app upgrade'), ), body: Container( alignment: Alignment.center, child: Text('Checking...' , style: TextStyle( fontSize: 16, ),), ), ); } ///检查是否有更新 Future<void> checkUpdate(BuildContext context) async{ //Android , 需要下载apk包 if(Platform.isAndroid){ print('is android'); PackageInfo packageInfo = await PackageInfo.fromPlatform(); String localVersion = packageInfo.version; //此处使用了dio,封装了httpGet方法,获取服务端中最新的app版本信息 String versionInfo = await httpGet('/appversion.json'); if(versionInfo != ""){ Map<String, dynamic> map = json.decode(versionInfo); String serverAndroidVersion = map['android_version'].toString(); String serverMsg = map['android_msg'].toString(); String url = map['android_url'].toString(); print(url); print('本地版本: ' + localVersion + ',最新版本: ' + serverAndroidVersion ); int c = serverAndroidVersion.compareTo(localVersion); //如果服务端版本大于本地版本则提示更新 if(c == 1){ showUpdate(context, serverAndroidVersion, serverMsg, url); } } } //Ios , 只能跳转到 AppStore , 直接采用url_launcher就可以了 //android也可以采用此方法,会跳转到手机浏览器中下载 if(Platform.isIOS){ print('is ios'); final url = "https://itunes.apple.com/cn/app/id1380512641"; // id 后面的数字换成自己的应用 id 就行了 if (await canLaunch(url)) { await launch(url, forceSafariVC: false); } else { throw 'Could not launch $url'; } } } ///2.显示更新内容 Future<void> showUpdate(BuildContext context , String version, String data, String url) async { return showDialog<void>( context: context, barrierDismissible: true, builder: (BuildContext context) { return CupertinoAlertDialog( title: Text('检测到新版本 v$version'), content : Text('是否要更新到最新版本?') , actions: <Widget>[ FlatButton( child: Text('下次在说'), onPressed: () { Navigator.of(context).pop(); }, ), FlatButton( child: Text('立即更新'), onPressed: ()=>doUpdate(context,version,url) , ), ], ); }, ); } ///3.执行更新操作 doUpdate(BuildContext context , String version,String url) async { //关闭更新内容提示框 Navigator.pop(context); //获取权限 var per = await checkPermission(); if(per != null && !per){ return null; } //开始下载apk executeDownload(context , url); } ///4.检查是否有权限 Future<bool> checkPermission() async { //检查是否已有读写内存权限 PermissionStatus status = await PermissionHandler().checkPermissionStatus(PermissionGroup.storage); print(status); //判断如果还没拥有读写权限就申请获取权限 if(status != PermissionStatus.granted){ var map = await PermissionHandler().requestPermissions([PermissionGroup.storage]); if(map[PermissionGroup.storage] != PermissionStatus.granted){ return false; } } return true; } ///5.下载apk Future<void> executeDownload(BuildContext context ,String url) async { //下载时显示下载进度dialog pr = new ProgressDialog(context,type: ProgressDialogType.Download, isDismissible: true, showLogs: true); if (!pr.isShowing()) { pr.show(); } //apk存放路径 final path = await _apkLocalPath; File file = File(path + '/' + apkName); if (await file.exists()) await file.delete(); //下载 final taskId = await FlutterDownloader.enqueue( url: url,//下载最新apk的网络地址 savedDir: path, fileName: apkName, showNotification: true, openFileFromNotification: true); FlutterDownloader.registerCallback((id, status, progress) { if (status == DownloadTaskStatus.running) { pr.update(progress: progress.toDouble(), message: "下载中,请稍后…"); } if (status == DownloadTaskStatus.failed) { if (pr.isShowing()) { pr.hide(); } } if (taskId == id && status == DownloadTaskStatus.complete) { if (pr.isShowing()) { pr.hide(); } _installApk(); } }); } //6.安装app Future<Null> _installApk() async { String path = await _apkLocalPath; await OpenFile.open(path + '/' + apkName); } // 获取apk存放地址(外部路径) Future<String> get _apkLocalPath async { final directory = await getExternalStorageDirectory(); return directory.path; } } 谢谢 [Flutter _ app]: https://blog.csdn.net/DeckeDeng/article/details/90067482 [demo]: https://download.csdn.net/download/zhuyu19911016520/12095883 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0_size_16_color_FFFFFF_t_70]: https://img-blog.csdnimg.cn/2020011021303174.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0_size_16_color_FFFFFF_t_70 1]: https://img-blog.csdnimg.cn/20200110213042266.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0_size_16_color_FFFFFF_t_70 2]: https://img-blog.csdnimg.cn/20200110213207482.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0_size_16_color_FFFFFF_t_70 3]: https://img-blog.csdnimg.cn/20200110213216422.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70 [watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0_size_16_color_FFFFFF_t_70 4]: https://img-blog.csdnimg.cn/20200110213236567.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly96aHV5dS5ibG9nLmNzZG4ubmV0,size_16,color_FFFFFF,t_70
还没有评论,来说两句吧...