在微信小程序开发中,将图片或者视频保存到用户手机是常见的业务需求之一。通过调用小程序的下载文件以及保存文件到相册的API才能完成,后者会向用户申请权限,一旦用户不小心拒绝,那么下次保存文件的时候将不再弹出权限申请窗口,显然这对用户会造成困扰。
提示:如果用户在首次权限申请中拒绝,则必须在小程序的设置模块打开该权限,不再弹窗操作。
因此,作为开发者如何通过代码去引导用户正确操作呢?
首先我们可以通过API(wx.getSetting)获取当前小程序的设置信息,查看当前的权限状态。
wx.getSetting({ success: res => { console.log(res.authSetting) } })
提示:authSetting 是一个对象,保存当权用户的所有权限设置,对象的 Key 为具体权限别名,Value 为授权状态,即 true 已经授权;false 为拒绝该权限,如果没有申请过授权,则不保存在该对象中。
权限别名请参考:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/authorize.html
如果没有授权,可以调用授权API(wx.authorize)向用户发起授权,同样只有第一次授权限才会弹窗,如果被拒绝过,返回失败。
wx.getSetting({ success: res => { // 保存到相册的授权别名 res.scope = 'scope.writePhotosAlbum' if (!res.authSetting[res.scope]) { // 申请授权 wx.authorize({ scope: res.scope, success: res => { // 同意或已经授权 }, fail() { // 用户拒绝授权 } }) } } })
因此,当用户拒绝过授权后,只能引导用户打开设置界面,开启相关权限。打开设置的API(wx.openSetting)返回当前新的设置信息。
需要注意的是,wx.openSetting 无法直接调用,但可以在弹窗 wx.showModal 回调中打开。
wx.showModal({ content: '请允许小程序使用相册权限', success: () => { wx.openSetting({ success: result => { // 如果用户还是不肯授权,提示没有权限 if (!result.authSetting[res.scope]) { wx.showToast({ title: '未允许该权限' }) } }, }) } })梳理一下,先获取设置信息,判断是否授权,如果没有或者拒绝过,然后引导用户打开设置界面开启相关权限。当然,即使如此,用户可能仍然选择不开启权限,那只能提示用户没用权限,无法保存图片或视频,否则我们进行下一步操作。
整个过程完全是一个异步的操作,所以在封装的代码的时候,可以用 Promise 去完成。
分解一下操作,先下载视频,然后判断是否有权限保存图片视频文件(包括引导用户开启相关权限),最后才是去保存,这一步难点在获取异步的操作,你得先知道用户最后有没有打开权限。
重点来了,业务代码封装。
下载图片视频,调用下载文件的API(wx.downloadFile)
function downloadFile(url, listener) { listener = listener || {} return new Promise((resolve, reject) => { listener.onStart && listener.onStart() const downloadTask = wx.downloadFile({ url, success: res => { // 下载状态正常 if (res.statusCode == 200) { resolve(res) } else { reject(res) } } }) if (listener.onProgress) { downloadTask.onProgressUpdate(listener.onProgress) } }) }
上面封装的 downloadFile 函数接受两个参数,url 下载地址,listener 下载监听回调,对象类型,listener.onStart 开始下载回调,listener.onProgress 下载进度回调
保存到相册,继续封装,API(wx.saveVideoToPhotosAlbum)
function saveMediaToPhotosAlbum(url, listener) { downloadFile(url, listener).then(res => { wx.saveVideoToPhotosAlbum({ filePath: res.tempFilePath, success: listener.onComplete, fail: listener.onComplete }) }) }
调用很简单,如下
const url = 'http://example.com/xxx.mp4' saveMediaToPhotosAlbum(url, { onStart() { wx.showToast({ title: '开始下载', }) }, onComplete(res) { wx.showToast({ title: '下载完成', }) }, onProgress(res) { // 下载进度回调 wx.showLoading({ title: res.progress + "%", }) } })
测试一下,你会发现,开始下载=》下载中=》进度在走,下载完成会弹出保存到相册权限弹窗,当你点击允许后提示已经保存到xxx(安卓)。好像挺完美,按照预想的过程走完,但别忘了,上面提到的授权问题,一旦拒绝,再调用就无法成功保存。
所以,在下载完成后还要进行授权操作,如果授权成功再保存到相册,否则提示没有权限无法保存。
同样用 Promise 封装
function authorize(scope) { scope = 'scope.writePhotosAlbum' return new Promise((resolve, reject) => { wx.getSetting({ success: res => { if (!res.authSetting[scope]) { wx.authorize({ scope, success: resolve, fail: () => wx.showModal({ content: R.string.prompt_authorize_photos, // 请允许小程序使用相册权限 success: () => wx.openSetting({ success: res => { if (res.authSetting[scope]) { resolve(res.authSetting) } else { wx.showModal({ content: R.string.prompt_authorize_cancel }) // 未允许使用该权限 reject(res.authSetting) } } }) }) }) } else { resolve(res.authSetting) } } }) }) }因为是要在文件下载完成后先判断授权状态,所以调用授权的代码应该放在 downloadFile 函数中,继续改造
function downloadFile(url, listener) { listener = listener || {} return new Promise((resolve, reject) => { listener.onStart && listener.onStart() const downloadTask = wx.downloadFile({ url, success: res => { // 下载状态正常 if (res.statusCode == 200) { resolve(res) } else { reject(res) } } }) if (listener.onProgress) { downloadTask.onProgressUpdate(listener.onProgress) } }).then(res => { return authorize().then(() => res).catch(() => { listener.onComplete() return Promise.reject(null) }) }) }
到处就大功告成,赶紧去试试。