由于我做的项目不是放在APP商店(公司内部用)的,一些flutter的第三方库不合适我,我需要用的是从网上下载再安装(从服务下),网上也找了花了我好几天时间。不全又乱,这我自己做一下备份
现在只使用安卓下载,ios没有做(后期可能更新)
app更新要求
1.进入app就查看app是否要更新(更新对比自己写)
2.下载完成可以自动弹窗安装界面
正式开始
1.使用第三方库
dependencies:# 查询应用程序包信息package_info_plus: ^5.0.1# 创建和管理下载任务的插件flutter_downloader: ^1.11.6# 安装插件,打开安装界面install_plugin: ^2.1.0# 权限处理程序permission_handler: ^11.3.0
package_info_plus插件,获取版本的。我这就不实现了,这个用起来没有难度的,主要是看你怎么封装对比版本,我这下面用的是网上的app连接就不用对比版本了,直接下载。
2.权限
添加权限
android\app\src\main\AndroidManifest.xml
manifest需要加上xmlns:tools="http://schemas.android.com/tools",
不然可能报错
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"><applicationandroid:label="cpm"android:name="${applicationName}"android:icon="@mipmap/launcher_icon"><!-- flutter_downloader下载器安卓配置,如果你想其它应该有权读取您的文件 --><providerandroid:name="vn.hunghd.flutterdownloader.DownloadedFileProvider"android:authorities="${applicationId}.flutter_downloader.provider"android:exported="false"android:grantUriPermissions="true"><meta-dataandroid:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/provider_paths"/></provider><!-- 开始FlutterDownloader定制 --><!-- 禁用默认初始化器 --><providerandroid:name="androidx.startup.InitializationProvider"android:authorities="${applicationId}.androidx-startup"android:exported="false"tools:node="merge"><meta-dataandroid:name="androidx.work.WorkManagerInitializer"android:value="androidx.startup"tools:node="remove" /></provider><!-- 声明自定义初始化器 --><providerandroid:name="vn.hunghd.flutterdownloader.FlutterDownloaderInitializer"android:authorities="${applicationId}.flutter-downloader-init"android:exported="false"><!-- 更改此数字以配置最大并发任务数为5 --><meta-dataandroid:name="vn.hunghd.flutterdownloader.MAX_CONCURRENT_TASKS"android:value="5" /></provider><!-- 结束FlutterDownloader定制 --></application><!-- 允许网络连接 --><uses-permission android:name="android.permission.INTERNET" /><!-- 接入wifi状态 --><uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/><!-- 允许程序获取网络信息状态 --><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /><!-- 写外部存储权限 --><uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/><!-- 读取外部存储的权限 --><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /><!-- 安装 .apk 文件(请求安装包)权限 --><uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
</manifest>
如果你是HTTP下载的
允许访问明文HTTP流量
你可能要通过这个去添加http下载权限
报错关键词: Cleartext HTTP traffic to xxx not permitted
3.初始化
import 'package:flutter_downloader/flutter_downloader.dart';
import './utils/update_app.dart';// 等下要做的更新方法void main() async {// 下载器 插件在使用前必须初始化await FlutterDownloader.initialize(debug: true, // 可选:设置为false以禁用将日志打印到控制台(默认:true)ignoreSsl: true, // 选项:设置为false以禁用HTTP链接(默认值:false));// 更新Appawait updateApp();runApp(const MyApp());
}
4.更新方法
前置工作
这使用get库的二次封装弹窗,这个弹窗自行实现,不然代码太多了。主要看注释下的代码
import 'package:get/get.dart';
import 'package:flutter/material.dart';
/// 更新App
updateApp() async {// 等页面加载完后再执行后面的,这个是重点,你刚进App大概是没有加载完页面的WidgetsBinding.instance.addPostFrameCallback((_) async {confirmDialog(title: '更新程序',textCancel: '稍后',textConfirm: '现在更新',isVerticalLayout: false,onCancel: () => Get.back(),// 关闭弹窗onConfirm: () async {// 下载监听bindBackgroundIsolate();// 下载await downloaderApp();// 这是另一个方法,后面讲// _networkInstallApk();},);});
}
方法1,使用flutter_downloader下载
其实我这下面与上面的代码放一起的,比较方便,当然主要还是看个人怎么做
import 'dart:isolate';
import 'dart:ui';
import 'package:cpm/utils/gadget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:install_plugin/install_plugin.dart';final ReceivePort _port = ReceivePort(); // 声明接收端口
// 下载文件地址,这个是install_plugin库提供的apk文件地址,可以测试使用
String url = 'https://s3.cn-north-1.amazonaws.com.cn/mtab.kezaihui.com/apk/takeaway_phone_release_1.apk';
String fileName = 'downloader_send_port'; //文件名
final isDown = false.obs; // 下载状态
dynamic taskId = 0; // 文件下载ID
String savedDir = ''; // 本地文件夹路径
RxString percent = '0'.obs; // 下载进度,这我是使用Get库的状态管理准备显示在页面的// 下载文档
Future<void> downloaderApp() async {debugPrint('准备下载。检查有没有存储权限');bool isStorage = await checkPermissionStorage();if (!isStorage) {debugPrint('没有存储权限');return;}savedDir = await findLocalPath();debugPrint('下载中...');isDown.value = true;taskId = await FlutterDownloader.enqueue(url: url, // 链接文件下载savedDir: savedDir, // 本地文件夹路径fileName: fileName, //文件名showNotification: true, // 在状态栏显示下载进度(适用于Android)openFileFromNotification: true, // 点击通知打开下载的文件(适用于Android));// 更新下载进度await FlutterDownloader.registerCallback(Download.downloadCallback);
}/// 下载监听
void bindBackgroundIsolate() {final isSuccess = IsolateNameServer.registerPortWithName(_port.sendPort, fileName);if (!isSuccess) {_unbindBackgroundIsolate();bindBackgroundIsolate();return;}_port.listen((dynamic data) async {///重新下载状态isDown.value = false;dynamic status = data[1];// 在这赋值进度变量percent.value = (data[2] as int).toString();print('data:$data');if (status == 3) {//程序休眠1s,保证下载事项处理完成await Future.delayed(const Duration(seconds: 1));print('下载成功,正在打开');await localInstallApk('$savedDir/$fileName');//_unbindBackgroundIsolate();} else if (status == 4) {print('下载失败');_unbindBackgroundIsolate();}});// 默认进度为10间隔修改一次,可以在这加一个step: 1参数,可以隔1就回调一次FlutterDownloader.registerCallback(Download.downloadCallback);
}// 打开安装界面
localInstallApk(String path) async {final res = await InstallPlugin.install(path);debugPrint("应用安装器 ${res['isSuccess'] == true ? 'success' : 'fail:${res['errorMessage'] ?? ''}'}");
}/// 释放监听
void _unbindBackgroundIsolate() => IsolateNameServer.removePortNameMapping(fileName);/// 注册监听事件,因为要静态方法,所以做一个类才行
class Download {@pragma('vm:entry-point')static void downloadCallback(String id,int status,int progress,) {IsolateNameServer.lookupPortByName(fileName)?.send([id, status, progress]);// print('下载任务:$id,处于状态:$status,进度为: $progress');}
}
还有两个方法,由于可能其它地方也会用到,我就做成通用方法,
你需要把这两个方法引入,或者你自己放到同一个文件
import 'dart:io';
import 'package:path_provider/path_provider.dart' as path_provider;
import 'package:permission_handler/permission_handler.dart';/// 查找本地文件路径并返回路径字符串
/// - [path] 缓冲文件的文件名,默认就是`Download`
/// - 描述:设备上没有备份的临时目录的路径,适合存放下载文件的缓存。
/// - 注意:`path`参数第一位不能是`/`
Future<String> findLocalPath({String path = 'Download'}) async {final directory = Platform.isAndroid? await path_provider.getExternalStorageDirectory(): await path_provider.getApplicationSupportDirectory();String localPath = '${directory?.path}/$path';final savedDir = Directory(localPath);bool hasExisted = await savedDir.exists();if (!hasExisted) {savedDir.create();}return localPath;
}/// 检查设备存储权限并请求权限(如果未授予)
Future<bool> checkPermissionStorage() async {// 获取存储权限的当前状态var status = await Permission.storage.status;// 如果存储权限未被授予,则请求权限if (!status.isGranted) {status = await Permission.storage.request();// 如果权限请求被授予,返回trueif (status.isGranted) {return true;}} else {// 如果权限已经授予,或者权限请求被拒绝,返回truereturn true;}// 如果所有条件都不符合,返回falsereturn false;
}
方法2,使用dio下载
这个我没有在上面的第三方库写dio进去,因为我觉得你会有一个http请求库的。
这个是直接下载的没有暂停的什么功能,好处就是很直接的下载
// 网络上下载apk
_networkInstallApk() async {var progressValue = 0.0;var savePath = await getTemporaryDirectory('takeaway_phone_release_1.apk');// url 就是上面的那一个await Dio().download(url, savePath, onReceiveProgress: (count, total) {final value = count / total;//if (progressValue != value) {if (progressValue < 1.0) {progressValue = count / total;} else {progressValue = 0.0;}debugPrint("${(progressValue * 100).toStringAsFixed(2)}%");}});final res = await InstallPlugin.install(savePath);debugPrint("install apk ${res['isSuccess'] == true ? 'success' : 'fail:${res['errorMessage'] ?? ''}'}");
}