Flutter后台下载管理插件background_downloader_sql的使用
Flutter后台下载管理插件 background_downloader_sql
的使用
1. 插件简介
background_downloader_sql
是 background_downloader
的扩展包,它为下载器添加了基于 SQLite 的持久化存储功能。默认情况下,background_downloader
使用的是基于文件系统的 LocalStorePersistentStorage
,而 background_downloader_sql
则使用 SQLite 数据库来存储下载任务的状态和其他相关信息。
2. 使用 SqlitePersistentStorage
要使用 SqlitePersistentStorage
,你需要在第一次调用 FileDownloader
时传递一个 SqlitePersistentStorage
实例。这可以通过以下代码实现:
final sqlStorage = SqlitePersistentStorage(migrationOptions: ['local_store', 'flutter_downloader']);
FileDownloader(persistentStorage: sqlStorage);
这段代码会初始化 FileDownloader
,并使用 SQLite 数据库作为持久化存储。你还可以通过 migrationOptions
参数指定从其他存储方式(如 LocalStore
或 Flutter Downloader
)进行数据迁移。
3. 数据库结构
SqlitePersistentStorage
使用简单的表结构来存储不同类型的数据,每个表都有一个 taskId
列和一个 objectJsonMap
列,后者用于存储对象的 JSON 字符串表示。你可以通过 retrieveTaskRecords
方法查询数据库中的任务记录。
4. 迁移选项
background_downloader_sql
支持从 LocalStore
和 Flutter Downloader
的 SQLite 数据库进行数据迁移。你可以通过在 SqlitePersistentStorage
构造函数中传递 migrationOptions
来启用这些迁移选项。
final sqlStorage = SqlitePersistentStorage(migrationOptions: ['local_store', 'flutter_downloader']);
5. 完整示例 Demo
下面是一个完整的示例应用,展示了如何使用 background_downloader_sql
进行后台下载管理。这个示例包括下载、暂停、恢复、取消等功能,并且展示了如何处理通知和权限请求。
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:background_downloader/background_downloader.dart';
import 'package:background_downloader_sql/background_downloader_sql.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
void main() {
Logger.root.onRecord.listen((LogRecord rec) {
debugPrint(
'${rec.loggerName}>${rec.level.name}: ${rec.time}: ${rec.message}');
});
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
[@override](/user/override)
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final log = Logger('ExampleApp');
final buttonTexts = ['Download', 'Cancel', 'Pause', 'Resume', 'Reset'];
ButtonState buttonState = ButtonState.download;
bool downloadWithError = false;
TaskStatus? downloadTaskStatus;
DownloadTask? backgroundDownloadTask;
StreamController<TaskProgressUpdate> progressUpdateStream = StreamController();
bool loadAndOpenInProgress = false;
bool loadABunchInProgress = false;
[@override](/user/override)
void initState() {
super.initState();
// 初始化 FileDownloader 并使用 SqlitePersistentStorage
FileDownloader(persistentStorage: SqlitePersistentStorage());
// 配置下载器
FileDownloader().configure(globalConfig: [
(Config.requestTimeout, const Duration(seconds: 100)),
], androidConfig: [
(Config.useCacheDir, Config.whenAble),
], iOSConfig: [
(Config.localize, {'Cancel': 'StopIt'}),
]).then((result) => debugPrint('Configuration result = $result'));
// 注册回调并配置通知
FileDownloader()
.registerCallbacks(taskNotificationTapCallback: myNotificationTapCallback)
.configureNotificationForGroup(FileDownloader.defaultGroup,
running: const TaskNotification('Download {filename}', 'File: {filename} - {progress} - speed {networkSpeed} and {timeRemaining} remaining'),
complete: const TaskNotification('{displayName} download {filename}', 'Download complete'),
error: const TaskNotification('Download {filename}', 'Download failed'),
paused: const TaskNotification('Download {filename}', 'Paused with metadata {metadata}'),
progressBar: true)
.configureNotificationForGroup('bunch',
running: const TaskNotification('{numFinished} out of {numTotal}', 'Progress = {progress}'),
complete: const TaskNotification("Done!", "Loaded {numTotal} files"),
error: const TaskNotification('Error', '{numFailed}/{numTotal} failed'),
progressBar: false,
groupNotificationId: 'notGroup')
.configureNotification(
complete: const TaskNotification('Download {filename}', 'Download complete'),
tapOpensFile: true); // dog can also open directly from tap
// 监听更新
FileDownloader().updates.listen((update) {
switch (update) {
case TaskStatusUpdate _:
if (update.task == backgroundDownloadTask) {
buttonState = switch (update.status) {
TaskStatus.running || TaskStatus.enqueued => ButtonState.pause,
TaskStatus.paused => ButtonState.resume,
_ => ButtonState.reset
};
setState(() {
downloadTaskStatus = update.status;
});
}
case TaskProgressUpdate _:
progressUpdateStream.add(update); // 传递给 widget 以显示进度
}
});
}
/// 处理用户点击通知
void myNotificationTapCallback(Task task, NotificationType notificationType) {
debugPrint(
'Tapped notification $notificationType for taskId ${task.taskId}');
}
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: Colors.purple,
brightness: Brightness.light,
),
),
home: Scaffold(
appBar: AppBar(
title: const Text('background_downloader example app'),
),
body: Center(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(16),
child: Row(
children: [
Expanded(
child: Text('Force error',
style: Theme.of(context).textTheme.titleLarge)),
Switch(
value: downloadWithError,
onChanged: (value) {
setState(() {
downloadWithError = value;
});
})
],
),
),
Center(
child: ElevatedButton(
onPressed: processButtonPress,
child: Text(
buttonTexts[buttonState.index],
),
)),
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
const Expanded(child: Text('File download status:')),
Text('${downloadTaskStatus ?? "undefined"}')
],
),
),
const Divider(
height: 30,
thickness: 5,
color: Colors.blueGrey,
),
Center(
child: ElevatedButton(
onPressed:
loadAndOpenInProgress ? null : processLoadAndOpen,
child: Text(
Platform.isIOS ? 'Load, open and add' : 'Load & Open',
))),
Center(
child: Text(
loadAndOpenInProgress ? 'Busy' : '',
)),
const Divider(
height: 30,
thickness: 5,
color: Colors.blueGrey,
),
Center(
child: ElevatedButton(
onPressed:
loadABunchInProgress ? null : processLoadABunch,
child: const Text('Load a bunch'))),
Center(child: Text(loadABunchInProgress ? 'Enqueueing' : '')),
],
),
)),
bottomSheet: DownloadProgressIndicator(progressUpdateStream.stream,
showPauseButton: true,
showCancelButton: true,
backgroundColor: Colors.grey,
maxExpandable: 3)),
);
}
/// 处理中心按钮点击(初始为 'Download',但根据状态变化)
Future<void> processButtonPress() async {
switch (buttonState) {
case ButtonState.download:
// 开始下载
await getPermission(PermissionType.notifications);
backgroundDownloadTask = DownloadTask(
url: downloadWithError
? 'https://avmaps-dot-bbflightserver-hrd.appspot.com/public/get_current_app_data' // 返回 403 状态码
: 'https://storage.googleapis.com/approachcharts/test/5MB-test.ZIP',
filename: 'zipfile.zip',
directory: 'my/directory',
baseDirectory: BaseDirectory.applicationDocuments,
updates: Updates.statusAndProgress,
retries: 3,
allowPause: true,
metaData: '<example metaData>',
displayName: 'My display name');
await FileDownloader().enqueue(backgroundDownloadTask!);
break;
case ButtonState.cancel:
// 取消下载
if (backgroundDownloadTask != null) {
await FileDownloader()
.cancelTasksWithIds([backgroundDownloadTask!.taskId]);
}
break;
case ButtonState.reset:
downloadTaskStatus = null;
buttonState = ButtonState.download;
break;
case ButtonState.pause:
if (backgroundDownloadTask != null) {
await FileDownloader().pause(backgroundDownloadTask!);
}
break;
case ButtonState.resume:
if (backgroundDownloadTask != null) {
await FileDownloader().resume(backgroundDownloadTask!);
}
break;
}
if (mounted) {
setState(() {});
}
}
/// 处理 'Load & Open' 按钮
///
/// 下载一张狗的图片并打开
Future<void> processLoadAndOpen() async {
if (!loadAndOpenInProgress) {
await getPermission(PermissionType.notifications);
var task = DownloadTask(
url:
'https://i2.wp.com/www.skiptomylou.org/wp-content/uploads/2019/06/dog-drawing.jpg',
baseDirectory: BaseDirectory.applicationSupport,
filename: 'dog.jpg');
setState(() {
loadAndOpenInProgress = true;
});
await FileDownloader().download(task);
await FileDownloader().openFile(task: task);
if (Platform.isIOS) {
// 将图片添加到照片库并打印路径
var auth = await FileDownloader()
.permissions
.status(PermissionType.iosChangePhotoLibrary);
if (auth != PermissionStatus.granted) {
auth = await FileDownloader()
.permissions
.request(PermissionType.iosChangePhotoLibrary);
}
if (auth == PermissionStatus.granted) {
final identifier = await FileDownloader()
.moveToSharedStorage(task, SharedStorage.images);
if (identifier != null) {
final path = await FileDownloader()
.pathInSharedStorage(identifier, SharedStorage.images);
debugPrint(
'iOS path to dog picture in Photos Library = ${path ?? "permission denied"}');
} else {
debugPrint(
'Could not add file to Photos Library, likely because permission denied');
}
} else {
debugPrint('iOS Photo Library permission not granted');
}
}
setState(() {
loadAndOpenInProgress = false;
});
}
}
Future<void> processLoadABunch() async {
if (!loadABunchInProgress) {
setState(() {
loadABunchInProgress = true;
});
await getPermission(PermissionType.notifications);
for (var i = 0; i < 5; i++) {
await FileDownloader().enqueue(DownloadTask(
url:
'https://storage.googleapis.com/approachcharts/test/5MB-test.ZIP',
filename: 'File_${Random().nextInt(1000)}',
group: 'bunch',
updates: Updates.progress)); // 必须提供进度更新!
await Future.delayed(const Duration(milliseconds: 500));
}
setState(() {
loadABunchInProgress = false;
});
}
}
/// 尝试获取权限(如果尚未授予)
Future<void> getPermission(PermissionType permissionType) async {
var status = await FileDownloader().permissions.status(permissionType);
if (status != PermissionStatus.granted) {
if (await FileDownloader()
.permissions
.shouldShowRationale(permissionType)) {
debugPrint('Showing some rationale');
}
status = await FileDownloader().permissions.request(permissionType);
debugPrint('Permission for $permissionType was $status');
}
}
}
enum ButtonState { download, cancel, pause, resume, reset }
更多关于Flutter后台下载管理插件background_downloader_sql的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter后台下载管理插件background_downloader_sql的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是如何在Flutter项目中使用background_downloader_sql
插件进行后台下载管理的示例代码。这个插件允许你在后台下载文件,并且使用SQLite数据库来管理下载任务。
1. 添加依赖
首先,你需要在pubspec.yaml
文件中添加background_downloader_sql
的依赖:
dependencies:
flutter:
sdk: flutter
background_downloader_sql: ^latest_version # 替换为实际最新版本号
2. 导入插件
在你的Dart文件中导入插件:
import 'package:background_downloader_sql/background_downloader_sql.dart';
3. 初始化插件
在你的应用启动时(例如在MainActivity.kt
或AppDelegate.swift
中,以及Dart代码的main.dart
中),初始化插件:
void main() {
WidgetsFlutterBinding.ensureInitialized();
// 初始化BackgroundDownloaderSQL
BackgroundDownloaderSQL.initialize().then((_) {
runApp(MyApp());
});
}
4. 创建下载任务
创建一个下载任务并启动它:
import 'package:flutter/material.dart';
import 'package:background_downloader_sql/background_downloader_sql.dart';
void startDownload() async {
String url = "https://example.com/yourfile.zip";
String saveDir = (await getApplicationDocumentsDirectory()).path;
String filePath = "$saveDir/yourfile.zip";
// 创建下载任务
var task = await BackgroundDownloaderSQL.createDownloadTask(
taskId: "task_1",
url: url,
savePath: filePath,
headers: null, // 你可以添加HTTP headers
showNotification: true,
openFileFromNotification: true,
);
// 启动下载任务
await BackgroundDownloaderSQL.start(taskId: "task_1");
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Background Downloader SQL Example'),
),
body: Center(
child: ElevatedButton(
onPressed: startDownload,
child: Text('Start Download'),
),
),
),
);
}
}
5. 监听下载状态
你可以通过监听下载状态来更新UI或执行其他操作:
void listenToDownloadStatus() {
BackgroundDownloaderSQL.downloadStatusStream.listen((status) {
if (status.taskId == "task_1") {
print("Download Status: ${status.status}");
print("Progress: ${status.progress}%");
// 你可以在这里更新UI
}
});
}
确保在合适的时机调用listenToDownloadStatus
,例如在MyApp
的initState
中:
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
listenToDownloadStatus();
}
@override
Widget build(BuildContext context) {
// ...(与之前相同)
}
}
6. 处理下载完成和错误
你可以处理下载完成和错误事件:
void handleDownloadEvents() {
BackgroundDownloaderSQL.downloadCompleteStream.listen((taskId) {
if (taskId == "task_1") {
print("Download completed for task: $taskId");
// 执行下载完成后的操作
}
});
BackgroundDownloaderSQL.downloadErrorStream.listen((error) {
if (error.taskId == "task_1") {
print("Download error for task: ${error.taskId}, Error: ${error.errorMessage}");
// 执行下载错误后的操作
}
});
}
同样,在MyApp
的initState
中调用handleDownloadEvents
:
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
listenToDownloadStatus();
handleDownloadEvents();
}
@override
Widget build(BuildContext context) {
// ...(与之前相同)
}
}
这样,你就完成了在Flutter中使用background_downloader_sql
插件进行后台下载管理的基本设置。这个示例展示了如何创建下载任务、启动任务、监听下载状态、处理下载完成和错误事件。