由于我做的项目不是放在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
# 用于比较和递增版本号
version: ^3.0.2
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">
<application
android:label="cpm"
android:name="${applicationName}"
android:icon="@mipmap/launcher_icon">
<!-- flutter_downloader下载器安卓配置,如果你想其它应该有权读取您的文件 -->
<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>
<!-- 开始FlutterDownloader定制 -->
<!-- 禁用默认初始化器 -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
<!-- 声明自定义初始化器 -->
<provider
android:name="vn.hunghd.flutterdownloader.FlutterDownloaderInitializer"
android:authorities="${applicationId}.flutter-downloader-init"
android:exported="false">
<!-- 更改此数字以配置最大并发任务数为5 -->
<meta-data
android: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下载权限
报错关键词: 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)
);
// 更新App
await updateApp();
runApp(const MyApp());
}
4.更新方法
前置工作
这使用get库的二次封装弹窗,这个弹窗自行实现,不然代码太多了。主要看注释下的代码
import 'package:get/get.dart';
import 'package:flutter/material.dart';
/// 更新App
updateApp() async {
// 是否有最新版本
bool isUpdate = await isNewVersions();
if (!isUpdate) {
debugPrint('暂不用更新');
return;
}
// 等页面加载完后再执行后面的,这个是重点,你刚进App大概是没有加载完页面的
WidgetsBinding.instance.addPostFrameCallback((_) async {
confirmDialog(
title: '更新程序',
textCancel: '稍后',
textConfirm: '现在更新',
isVerticalLayout: false,
onCancel: () => Get.back(),// 关闭弹窗
onConfirm: () async {
// 下载监听
bindBackgroundIsolate();
// 下载
await downloaderApp();
// 这是另一个方法,后面讲
// _networkInstallApk();
},
);
});
}
/// 检查是否有新的版本要更新
isNewVersions() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
Version version = Version.parse(packageInfo.version);
// 请求过来的版本号,自行请求,或者你有别的实现方法
Version request = Version.parse('0.3.0');
debugPrint('APP_版本:$version,请求_版本:$request,是否有最新的版本:${version < request}');
// 6 * 6 = 36
// 当请求的版本大时就有新的,有新的就返回为true
return request > version;
}
方法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();
// 如果权限请求被授予,返回true
if (status.isGranted) {
return true;
}
} else {
// 如果权限已经授予,或者权限请求被拒绝,返回true
return true;
}
// 如果所有条件都不符合,返回false
return 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'] ?? ''}'}");
}