uniapp开发的APP下载图片到手机相册功能的实现
下载图片地址
不要下载php动态脚本合成的图片
这里要特别注意一个问题,比如我使用php动态生成的图片,下载后,显示的后缀变成了.php!所以尽量不要下载脚本动态合成的图片!
还有以下几点需要注意:
- 对传入的参数需要encode!encodeURIComponent(title)等
- 不要对链接所有的都编码,比如http://、/等就不需要编码,仅仅对中文、空格、内容里面的符号进行编码即可。
- 比如这个链接:https://libs.qdxin.cn/appc/haibao/app.php?title=%E5%BC%80%E5%B1%95%E5%8D%81%E5%A4%A7%E4%B8%93%E9%A1%B9%E4%BF%83%E5%B0%B1%E4%B8%9A%E8%A1%8C%E5%8A%A8type=image.png,如果必须使用php脚本合成的图片的时候,下载无效可以尝试在链接末尾加上.png或者.jpg试试。
下载普通图片
我们先定义接收的数据
data() {
return {
// 传递进来的base64格式
fileBase64: '',
// 传递进来的图片地址 不支持php动态生成的,会下载成php文件
fileimgUrl: ''
}
},
要使用手机相册,必须要先获得授权,所以要先检测用户给没给你相册的授权。这里我推荐使用uni封装好的插件。
App权限判断和提示 https://ext.dcloud.net.cn/plugin?id=594 我们只需要复制permission.js到我们的工程目录下即可,然后在文件中引用。
import permision from '@/common/js/permission.js';
由于安卓权限判断比较复杂,所以相册的权限封装成一个函数
async requestAndroidPermission(permisionID) {
var result = await permision.requestAndroidPermission(permisionID)
let status = false;
if (result == 1) {
// 已获得授权
status = true;
} else if (result == 0) {
// 未获得授权
status = false;
} else {
// 被永久拒绝权限
status = false;
}
return status;
},
实现APP图片的下载
/**
* 判断app是否有添加图片到手机相册的权限
*/
appSaveImage(imgurl) {
const msg = '该功能需要相册权限,请前往设置页面进行授权';
this.fileimgUrl = imgurl;
let that = this;
switch (uni.getSystemInfoSync().platform) {
case 'android':
const status = this.requestAndroidPermission('android.permission.WRITE_EXTERNAL_STORAGE');
if(status) {
that.downLoadImg();
} else {
// 没有授权
uni.showModal({
content: msg,
showCancel: true,
success() {
permision.gotoAppPermissionSetting();
}
});
}
break;
case 'ios':
if(permision.judgeIosPermission("photoLibrary")) {
that.downLoadImg();
} else {
// 没有授权
uni.showModal({
content: msg,
showCancel: true,
success() {
permision.gotoAppPermissionSetting();
}
});
}
break;
}
},
/**
* 下载图片资源,保存图片到系统相册
*/
downLoadImg() {
let that = this;
uni.showLoading({
title: '保存中,请稍后'
});
uni.downloadFile({
url: that.fileimgUrl,
success: (res) => {
uni.hideLoading();
console.log(res)
if (res.statusCode === 200) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: function() {
uni.showToast({
title: "保存成功",
icon: "none"
});
},
fail: function() {
uni.showToast({
title: "保存失败,请稍后重试",
icon: "none"
});
}
});
} else {
// 当状态码为400时候
uni.showToast({
title: "保存失败,请稍后重试",
icon: "none"
});
}
},
fail: (err) => {
uni.showToast({
title: "失败啦",
icon: "none"
});
}
})
},
注意,根据APP权限插件的介绍,this.requestAndroidPermission传入的是一个字符串!不是一个常量!
android.permission.READ_EXTERNAL_STORAGE | 外部存储(含相册)读取权限 |
res.statusCode如果失败了,是有可能得到400这个数值的。如果同样的文件 一个能下载成功 一个下载会在complete回调里提示状态码400,考虑传入url是否被转码了 尝试利用 解码之后 再传入decodeURL(url),参考
将base64转存为图片
主要就是用这个api:https://www.html5plus.org/doc/zh_cn/nativeobj.html#plus.nativeObj.Bitmap.save
appSaveBase64(base64) {
const msg = '该功能需要相册权限,请前往设置页面进行授权';
this.fileBase64 = base64;
console.log(this.fileBase64)
let that = this;
switch (uni.getSystemInfoSync().platform) {
case 'android':
const status = this.requestAndroidPermission('android.permission.WRITE_EXTERNAL_STORAGE');
if(status) {
that.downLoadBase64();
} else {
// 没有授权
uni.showModal({
content: msg,
showCancel: true,
success() {
permision.gotoAppPermissionSetting();
}
});
}
break;
case 'ios':
if(permision.judgeIosPermission("photoLibrary")) {
that.downLoadBase64();
} else {
// 没有授权
uni.showModal({
content: msg,
showCancel: true,
success() {
permision.gotoAppPermissionSetting();
}
});
}
break;
}
},
downLoadBase64() {
const base64 = this.fileBase64;
const bitmap = new plus.nativeObj.Bitmap("test");
uni.showLoading({
title: '保存中,请稍后'
});
bitmap.loadBase64Data(
base64,
// 加载Base64图片数据成功
function() {
const url = "_doc/" + new Date().getTime() + ".png"; // url为时间戳命名方式
// console.log('saveHeadImgFile', url)
bitmap.save(
url,
{
overwrite: true, // 是否覆盖
quality: 'quality' // 图片清晰度
},
// successcallback 保存图片操作成功回调
(i) => {
uni.hideLoading();
uni.saveImageToPhotosAlbum({
filePath: url,
success: function() {
uni.showToast({
title: '海报保存成功',
icon: 'none'
})
bitmap.clear();
}
});
},
// errorcallback 保存图片操作失败回调
(e) => {
uni.showToast({
title: '海报保存失败,请稍候重试',
icon: 'none'
});
bitmap.clear();
}
);
},
// 加载Base64图片数据失败
(e) => {
uni.showToast({
title: '海报加载失败',
icon: 'none'
})
bitmap.clear()
}
);
},
主要逻辑思路跟下载图片链接的一样,先判断有没有权限,没有权限就跳转手机设置开启权限,当有权限的时候,就执行下载。
uni-app将图片存入系统的官方api是uni.saveImageToPhotosAlbum(OBJECT),需要给定一个文件路径filePath,但是这个路径我们是没办法拿到的。
解决思路:手机转存base64需要用到这个方法:newplus.nativeObj.Bitmap,可以参考这个base64例子,把base64转成bitmap文件对象,保存到系统相册(但是此时某些手机上无法显示出来,其实是保存成功的),然后使用uni.saveImageToPhotoAlbum方法将图片成功保存并显示。
1、Bitmap是原生图片对象,其有个方法是loadBase64Data;于是我们首先创建一个bitmap对象:
2、然后使用loadBase64Data将base64字符串转换为bitmap文件对象:
3、调用bimap.save方法,将图片文件存入手机存储(记得bitmap.clear(),比较占内存)
4、uni.saveImageToPhotoAlbum方法将图片成功保存并显示
代码封装与使用
为方便日后调用,我将代码全部封装成一个mixin,在vue中引用调用方法即可。
(已经根据四中IOS的权限机制问题,更新了整体代码)
注意,其中的saveImage、isAuth只适用于小程序,不支持app。
/**
*
判断用户是否已授权,已授权返回成功,执行保存图片到相册;
如果用户拒绝授权,再次点击保存图片时,引导用户开启权限;
*/
import permision from '@/common/js/permission.js';
const downloadImg = {
data() {
return {
// 传递进来的base64格式
fileBase64: '',
// 传递进来的图片地址 不支持php动态生成的,会下载成php文件
fileimgUrl: ''
}
},
methods: {
async requestAndroidPermission(permisionID) {
var result = await permision.requestAndroidPermission(permisionID)
let status = false;
if (result == 1) {
// 已获得授权
status = true;
} else if (result == 0) {
// 未获得授权
status = false;
} else {
// 被永久拒绝权限
status = false;
}
return status;
},
appSaveBase64(base64) {
const msg = '该功能需要相册权限,请前往手机设置进行授权';
this.fileBase64 = base64;
console.log(this.fileBase64)
let that = this;
switch (uni.getSystemInfoSync().platform) {
case 'android':
const status = this.requestAndroidPermission('android.permission.WRITE_EXTERNAL_STORAGE');
if(status) {
that.downLoadBase64();
} else {
// 没有授权
uni.showModal({
content: msg,
showCancel: true,
success() {
permision.gotoAppPermissionSetting();
}
});
}
break;
case 'ios':
/**
* 由于ios的机制问题,你只有用过该功能它的权限列表才会出现该权限的设置,而安卓的权限直接就在列表里
*/
if(permision.judgeIosPermission("photoLibrary")) {
that.downLoadBase64();
} else {
// 没有授权
// 获取是否是第一次启动相册 false的话就是第一次使用
const iosFirstPhoto = uni.getStorageSync('iosFirstPhoto');
if(!iosFirstPhoto) {
that.downLoadBase64();
//设为true后就代表不是第一次开启相机
uni.setStorageSync('iosFirstPhoto', 'true');
} else {
uni.showModal({
content: msg,
showCancel: true,
success(res) {
// 点击确定才执行
if(res.confirm) {
permision.gotoAppPermissionSetting();
}
}
});
}
}
break;
}
},
downLoadBase64() {
const base64 = this.fileBase64;
const bitmap = new plus.nativeObj.Bitmap("test");
uni.showLoading({
title: '保存中,请稍后'
});
bitmap.loadBase64Data(
base64,
// 加载Base64图片数据成功
function() {
const url = "_doc/" + new Date().getTime() + ".png"; // url为时间戳命名方式
// console.log('saveHeadImgFile', url)
bitmap.save(
url,
{
overwrite: true, // 是否覆盖
quality: 'quality' // 图片清晰度
},
// successcallback 保存图片操作成功回调
(i) => {
uni.hideLoading();
uni.saveImageToPhotosAlbum({
filePath: url,
success: function() {
uni.showToast({
title: '海报保存成功',
icon: 'none'
})
bitmap.clear();
}
});
},
// errorcallback 保存图片操作失败回调
(e) => {
uni.showToast({
title: '海报保存失败,请稍候重试',
icon: 'none'
});
bitmap.clear();
}
);
},
// 加载Base64图片数据失败
(e) => {
uni.showToast({
title: '海报加载失败',
icon: 'none'
})
bitmap.clear()
}
);
},
/**
* 判断app是否有添加图片到手机相册的权限
*/
appSaveImage(imgurl) {
const msg = '该功能需要相册权限,请前往设置页面进行授权';
this.fileimgUrl = imgurl;
let that = this;
switch (uni.getSystemInfoSync().platform) {
case 'android':
const status = this.requestAndroidPermission('android.permission.WRITE_EXTERNAL_STORAGE');
if(status) {
that.downLoadImg();
} else {
// 没有授权
uni.showModal({
content: msg,
showCancel: true,
success() {
permision.gotoAppPermissionSetting();
}
});
}
break;
case 'ios':
if(permision.judgeIosPermission("photoLibrary")) {
that.downLoadImg();
} else {
// 没有授权
uni.showModal({
content: msg,
showCancel: true,
success() {
permision.gotoAppPermissionSetting();
}
});
}
break;
}
},
/**
* 下载图片资源,保存图片到系统相册
*/
downLoadImg() {
let that = this;
uni.showLoading({
title: '保存中,请稍后'
});
uni.downloadFile({
url: that.fileimgUrl,
success: (res) => {
uni.hideLoading();
console.log(res)
if (res.statusCode === 200) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success: function() {
uni.showToast({
title: "保存成功",
icon: "none"
});
},
fail: function() {
uni.showToast({
title: "保存失败,请稍后重试",
icon: "none"
});
}
});
} else {
// 当状态码为400时候
uni.showToast({
title: "保存失败,请稍后重试",
icon: "none"
});
}
},
fail: (err) => {
uni.showToast({
title: "失败啦",
icon: "none"
});
}
})
},
/*
* 引导用户开启权限
*/
isAuth() {
uni.showModal({
content: '由于您还没有允许保存图片到您相册里,无法进行保存,请点击确定允许授权',
success: (res) => {
if (res.confirm) {
uni.openSetting({
success: (result) => {
console.log(result.authSetting);
}
});
}
}
});
},
/**
* 保存图片 只适用于小程序
*/
saveImage(imgUrl) {
let that = this;
// 向用户发起授权请求
uni.authorize({
scope: 'scope.writePhotosAlbum',
success: () => {
// 已授权
that.downLoadImg();
},
fail: () => {
// 拒绝授权,获取当前设置
uni.getSetting({
success: (result) => {
if (!result.authSetting['scope.writePhotosAlbum']) {
that.isAuth()
}
}
});
}
})
}
}
}
export default downloadImg;
使用
import downloadImg from '@/common/js/mixin_downloadImage.js';
mixins: [ downloadImg ],
// #ifdef APP-PLUS
this.appSaveBase64(Base64);
// #endif
IOS机制原因,设置中无相册
由于ios的机制问题,你只有用过该功能它的权限列表才会出现该权限的设置,而安卓的权限直接就在列表里,比如下图
首先必须在manifest.json中添加模块,在uni里,照片和相册都归属于camera模块,但是他是HX3.6.11+新增,我的电脑是3.6.4所以,我们选择手动添加。
功能模块 https://uniapp.dcloud.net.cn/tutorial/app-modules.html
打包后缺少某些模块提示的解决方法:https://ask.dcloud.net.cn/article/283
模块名称 | 模块标识 | 功能 | 说明 | 支持平台 |
---|---|---|---|---|
Barcode(扫码) | Barcode | 调用相机扫码功能 | HBuilderX3.6.11+新增 | Android、iOS |
Bluetooth(低功耗蓝牙) | Bluetooth | 使用设备蓝牙功能 | Android、iOS | |
Camera&Gallery(相机和相册) | Camera | 调用相机拍照,访问或修改相册 | HBuilderX3.6.11+新增 | Android、iOS |
/* 5+App特有相关 */
"app-plus" : {
"compatible" : {...},
....
/* 模块配置 */
"modules" : {
"Webview-x5" : {},
"OAuth" : {},
"Share" : {},
"Camera" : {}
},
由于IOS的机制,必须要求先调用,否则设置中不能有相册选项。因为当先判断有无相册权限的时候,会因为没有权限而直接进入提示,从而打开app的设置,但是此时设置中由于没有调用相册的功能,不会有开启相册这个选项。所以,我考虑使用缓存区缓存一个值,当第一次开启保存到相册的操作的时候,直接去调用保存,这样无论用户同意不同意,我们都会在设置中添加一个“相册”的选项!(上图右1),之后,再次触发时候走正常的判断流程即可。
case 'ios':
/**
* 由于ios的机制问题,你只有用过该功能它的权限列表才会出现该权限的设置,而安卓的权限直接就在列表里
*/
if(permision.judgeIosPermission("photoLibrary")) {
that.downLoadBase64();
} else {
// 没有授权
// 获取是否是第一次启动相册 false的话就是第一次使用
const iosFirstPhoto = uni.getStorageSync('iosFirstPhoto');
if(!iosFirstPhoto) {
that.downLoadBase64();
//设为true后就代表不是第一次开启相机
uni.setStorageSync('iosFirstPhoto', 'true');
} else {
uni.showModal({
content: msg,
showCancel: true,
success(res) {
// 点击确定才执行
if(res.confirm) {
permision.gotoAppPermissionSetting();
}
}
});
}
}
break;
}
详细的整体代码见《三》