利用flutter_downloader插件在Flutter中实现文件下载

前言

以前有作一个工具集的微信小程序「开挂Lite」,可是因为小程序自身限制,没有办法实现下载文件的功能,只能把下载连接解析出来。并且受限于微信平台,小程序的审核是一件很麻烦的事情,所以有了将其APP化的想法。android

自从去年Flutter横空出世后,我便一直关注它的发展,时隔一年后从新拾起,发现它的生态已经初具规模,因而决定采用Flutter重作一个「开挂Lite」。后期我也会不定时更新一些和Flutter有关的文章,但愿你们能够多多支持。本文记录的即是我利用Flutter实现文件下载功能的过程。sql

完整源码可在公众号:「01二进制」后台回复:「Flutter 文件下载」获取小程序

开始

咱们先看一下实现的效果:微信小程序

iOS安全


Android微信

本demo的实现效果很是简单,就是点击一个按钮,而后下载文件,完成后提示用户是否打开文件。网络

准备工做

在本 demo 中使用的 IDE 为 Android Studio,同时使用到了如下几个库:app

  
    
  
  
   
   
   
   
  1. async

  2. ide

flutter_downloader: ^1.1.7path_provider: 1.1.2permission_handler: ^3.1.0progress_dialog: ^1.1.0+1toast: ^0.1.4

咱们先新建一个空项目,而后将上述依赖添加到项目的 pubspec.yaml文件,添加位置以下:

接下来咱们能够在 Terminal 中输入 flutter packagesget或者点击 IDE 左上角的 Packagesget字样安装依赖。

而后将初始项目中的多余代码删除,并在中间添加一个按钮。

  
    
  
  
   
   
   
   
body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ RaisedButton( child: Text("点我下载文件"), onPressed: () { // 执行下载操做 _doDownloadOperation(); }, ), ], ),),

其中 _doDownloadOperation()即是咱们执行下载操做的方法,至此,前期准备工做结束。

逻辑分析

虽然整个下载演示的过程很是简单,但仍是有必要来分析整个下载的流程,以下图所示:

因此咱们接下来要作的事情即是:

  1. 获取权限:网络权限、存储权限

  2. 获取下载路径

  3. 设置下载回调(用于监听下载过程)

操做

获取权限

这里使用到一个权限获取插件:permission_handler,这个插件提供了跨平台(Android和iOS)的权限检查以及获取API,地址在:https://pub.flutter-io.cn/packages/permission_handler。在获取权限前咱们须要先申明权限(Android)。

打开项目根目录下的 android/app/src/main/AndroidManifest.xml文件,位置以下图所示:

而后添加咱们须要使用的权限的申明,以下图所示:

接下来咱们就能够写代码来获取所需的权限了。建立一个 _checkPermission()函数用于判断权限是否给予。固然因为平台差别,咱们须要判断其为Android平台,申请代码以下:

  
    
  
  
   
   
   
   
// 申请权限Future<bool> _checkPermission() async { // 先对所在平台进行判断 if (Theme.of(context).platform == TargetPlatform.android) { PermissionStatus permission = await PermissionHandler() .checkPermissionStatus(PermissionGroup.storage); if (permission != PermissionStatus.granted) { Map<PermissionGroup, PermissionStatus> permissions = await PermissionHandler() .requestPermissions([PermissionGroup.storage]); if (permissions[PermissionGroup.storage] == PermissionStatus.granted) { return true; } } else { return true; } } else { return true; } return false;}

获取下载路径

这里是使用的插件是 path_provider,它是一个配合Dart的IO库以便在Flutter中实现文件读写的插件,Flutter中文网对该插件有着详细的介绍(https://flutterchina.club/reading-writing-files/),这里咱们须要明白一个问题,就是iOS没有外置存储这一律念,所以须要对平台进行判断,代码以下:

  
    
  
  
   
   
   
   
// 获取存储路径Future<String> _findLocalPath() async { // 由于Apple没有外置存储,因此第一步咱们须要先对所在平台进行判断 // 若是是android,使用getExternalStorageDirectory // 若是是iOS,使用getApplicationSupportDirectory final directory = Theme.of(context).platform == TargetPlatform.android ? await getExternalStorageDirectory() : await getApplicationSupportDirectory(); return directory.path;}

经过上述代码咱们即可以获取存储路径,可是若是咱们不想把文件下载到存储路径呢?好比我就喜欢单独设置一个 /Download路径专门用于保存下载文件,其实也很简单:

  
    
  
  
   
   
   
   

// 获取存储路径var _localPath = (await _findLocalPath()) + '/Download';final savedDir = Directory(_localPath);// 判断下载路径是否存在bool hasExisted = await savedDir.exists();// 不存在就新建路径if (!hasExisted) { savedDir.create();}

下载文件

下载文件这里我找了一些资料,发现貌似只有一个 flutter_downloader插件,也不知道是什么状况。该插件的配置过程也是挺复杂的,好在文档(https://pub.flutter-io.cn/packages/flutter_downloader)写的还算明白。这个插件能够实现后台下载,分别基于 Android 中的 WorkManager 和 iOS 中的 NSURLSessionDownloadTask 实现的。

接下来分别说下在iOS端和Android端的设置。

插件配置

iOS端配置

  • 启用 background mode

想要执行这一步,咱们在Xcode中打开该项目的 iOS module,以下图所示:

而后双击左侧Runner选项,选择 Capabilities 选项,按图中所示启用background mode

  • 添加 sqlite 依赖库

文档中还提供了一些可选配置

  • 设置 HTTP 请求支持

为了安全起见,苹果官方已经默认不让开发者使用不安全的http通讯协议了,而是建议开发者使用安全的https协议。若咱们仍是须要使用 http 协议须要作一些配置,文档中给了两种方式配置,一种是容许单个HTTP请求的域名,另外一种是容许全部HTTP请求的域名,这里出于演示目的,选择第二种。

只须要在 Info.plist文件中添加以下代码便可:

  
    
  
  
   
   
   
   
<key>NSAppTransportSecurity</key><dict> <key>NSAllowsArbitraryLoads</key><true/></dict>
  • 设置最大同时下载数

默认支持同时下载最多3个文件,若是你须要更改一样须要更改 Info.plist

  
    
  
  
   
   
   
   
<key>FDMaximumConcurrentTasks</key><integer>5</integer>
  • 设置下载完成通知

一样的,修改 Info.plist

  
    
  
  
   
   
   
   
<key>FDAllFilesDownloadedMessage</key><string>All files have been downloaded</string>

Android端配置

说完了iOS端的配置,咱们再来讲下Android端的配置。在 AndroidManifest.xml 文件中添加以下代码:

  
    
  
  
   
   
   
   
<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>

位置以下:

还有其余相似于iOS端的可选配置,功能大同小异,这里就不说了,详见官网。

编写下载代码

配置结束后,其实下载的代码很简单:

  
    
  
  
   
   
   
   
// 根据 downloadUrl 和 savePath 下载文件_downloadFile(downloadUrl, savePath) async { await FlutterDownloader.enqueue( url: downloadUrl, savedDir: savePath, showNotification: true, // show download progress in status bar (for Android) openFileFromNotification: true, // click on notification to open downloaded file (for Android) );}

固然咱们须要提早引入 flutter_downloader

  
    
  
  
   
   
   
   
import 'package:flutter_downloader/flutter_downloader.dart';

文档中还提供了其余API,譬如暂停下载、取消下载,这里就再也不阐述了,文档已经写的很清楚了。

到这其实就已经完成了下载的逻辑,然而下载的逻辑是实现了,想要让用户用的明白,咱们还须要加一些提示信息,就像开头demo展现的有下载进度条和下载完成的提示框,接下来咱们就来为下载设置这些提示信息吧。

设置下载提示信息

这里以对话框和进度条的形式展示下载过程,咱们使用到了 progress_dialog这个插件,能够很方便的显示出一个下载对话框,地址是https://pub.flutter-io.cn/packages/progress_dialog。

使用 progress_dialog插件很是简单,首先咱们引入依赖文件:

  
    
  
  
   
   
   
   
import 'package:progress_dialog/progress_dialog.dart';

而后建立一个对话框:

  
    
  
  
   
   
   
   
ProgressDialog pr;

若是想要建立一个下载提示对话框的话咱们只须要在合适的地方初始化这个Dialog:

  
    
  
  
   
   
   
   
pr = new ProgressDialog(context,ProgressDialogType.Download);

而后执行 pr.show();便可显示对话框。取消这个对话框也很是的简单,只需执行 pr.hide();

若是想要更新对话框中的提示信息,好比下载进度,只需执行下述代码:

  
    
  
  
   
   
   
   
pr.update(progress: percentage,message: "Please wait...");

同时咱们还能够经过 isShowing()函数判断对话框是否显示

  
    
  
  
   
   
   
   
bool isProgressDialogShowing = pr.isShowing();

是否是很是方便呢?

有了展现的对话框,下一步天然就是获取下载进度了,好在 flutter_downloader已经给咱们提供了一个下载回调,咱们能够在下面的这个回调函数中更新咱们的UI。

  
    
  
  
   
   
   
   
FlutterDownloader.registerCallback((id, status, progress) { // code to update your UI});

其中id是下载任务的id,status是当前id下载任务的状态,有 undefined,enqueued,running,complete,failed,canceled,paused这几种状态,progress即是当前id下载任务的进度。

这里方便起见我选择在 initState()函数中初始化下载回调函数和对话框:

  
    
  
  
   
   
   
   
@overridevoid initState() { super.initState(); // 初始化进度条 ProgressDialog pr = new ProgressDialog(context, ProgressDialogType.Download); pr.setMessage('下载中…'); // 设置下载回调 FlutterDownloader.registerCallback((id, status, progress) { // 打印输出下载信息 print('Download task ($id) is in status ($status) and process ($progress)'); ...... });

而后咱们须要根据下载的状态分状况讨论

  
    
  
  
   
   
   
   
@overridevoid initState() { super.initState(); ...... // 设置下载回调 FlutterDownloader.registerCallback((id, status, progress) { // 打印输出下载信息 print('Download task ($id) is in status ($status) and process ($progress)'); if (!pr.isShowing()) { pr.show(); } if (status == DownloadTaskStatus.running) { pr.update(progress: progress.toDouble(), message: "下载中,请稍后…"); } if (status == DownloadTaskStatus.failed) { showToast("下载异常,请稍后重试"); if (pr.isShowing()) { pr.hide(); } } if (status == DownloadTaskStatus.complete) { print(pr.isShowing()); if (pr.isShowing()) { pr.hide(); } } });

其实到这里下载文件的操做就算结束了,可是一般在下载完成后APP都会提示你是否要打开,因而在这咱们干脆 就拓展一下,实现打开咱们已经下载好的文件。

打开下载完成的文件

那如何打开已经下载好的文件呢?插件已经提供好了打开下载文件的API,咱们只须要像下面这样使用就能够了。

  
    
  
  
   
   
   
   
// 根据taskId打开下载文件Future<bool> _openDownloadedFile(taskId) { return FlutterDownloader.open(taskId: taskId);}

想要打开已经下载完成的文件,咱们必需要要确保文件已经下载好了。因此咱们须要紧接上面的代码中判断下载完成的函数。这里咱们以弹出对话框的形式询问用户是否打开文件。


代码以下

  
    
  
  
   
   
   
   
@overridevoid initState() { super.initState(); ...... if (status == DownloadTaskStatus.complete) { print(pr.isShowing()); if (pr.isShowing()) { pr.hide(); } // 显示是否打开的对话框 showDialog( // 设置点击 dialog 外部不取消 dialog,默认可以取消 barrierDismissible: false, context: context, builder: (context) => AlertDialog( title: Text('提示'), // 标题文字样式 content: Text('文件下载完成,是否打开?'), // 内容文字样式 backgroundColor: CupertinoColors.white, elevation: 8.0, // 投影的阴影高度 semanticLabel: 'Label', // 这个用于无障碍下弹出 dialog 的提示 shape: Border.all(), // dialog 的操做按钮,actions 的个数尽可能控制不要过多,不然会溢出 `Overflow` actions: <Widget>[ // 点击取消按钮 FlatButton( onPressed: () => Navigator.pop(context), child: Text('取消')), // 点击打开按钮 FlatButton( onPressed: () { Navigator.pop(context); // 打开文件 _openDownloadedFile(id).then((success) { if (!success) { Scaffold.of(context).showSnackBar(SnackBar( content: Text('Cannot open this file'))); } }); }, child: Text('打开')), ], )); } });}

对话框的使用上述代码已经注释的很详细了。

至此,咱们便使用 Flutter 完成了一个完整的下载文件的过程了。

总结

总的来讲,利用Flutter实现文件下载的思路仍是很清楚的,获取权限->获取路径->开始下载->监听下载进程,一鼓作气。同时,借助于 Flutter 社区的快速发展,已经有不少优秀的开发者开发了一些很是好用的插件,凭借着这些插件咱们能够快速实现本身想要的功能。在这个demo中整个界面编写+逻辑实现总共也才 223 行代码,虽然界面有些丑陋,但考虑到Dart语言的迷之缩进这个行数也是很短的了。

最后想要源码能够扫描下面的二维码关注个人公众号「01二进制」,后台回复「Flutter 文件下载」便可,后期我也会不定时更新一些和Flutter有关的文章,但愿你们能够多多支持。



本文分享自微信公众号 - 01二进制(gh_d1999add1857)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。