赞
踩
HarmonyOS Next 系列之省市区弹窗选择器实现(一)
HarmonyOS Next 系列之验证码输入组件实现(二)
HarmonyOS Next 系列之底部标签栏TabBar实现(三)
HarmonyOS Next 系列之HTTP请求封装和Token持久化存储(四)
HarmonyOS Next 系列之从手机选择图片或拍照上传功能实现(五)
HarmonyOS Next(基于API11)实现从手机选择图片或拍照上传功能,常用于头像上传等操作
1、媒体读写权限检查和申请
2、从手机存储选择图片或拍照
3、把图片复制到缓存目录
4、接口请求上传图片
分析说明:
图片上传使用API request.uploadFile 而该api上传文件的本地路径只支持internal协议
所以选择完图片/或拍照后需要把图片从内部存储复制到cache目录下,该操作需要外部存储设备媒体读写权限,且是用户级别的权限,因此每次复制图片前需要检查权限如果没权限需弹窗口让用户授权,最后在通过该api实现上传
(1)检查权限
工具类文件:
import bundleManager from '@ohos.bundle.bundleManager';
import abilityAccessCtrl, { Context, Permissions } from '@ohos.abilityAccessCtrl';
//校验应用是否授予权限
//@params permissions:权限名称数组
//@return permissionabilityAccessCtrl:权限名称
async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
let atManager = abilityAccessCtrl.createAtManager();
let grantStatus: abilityAccessCtrl.GrantStatus = 0;
// 获取应用程序的accessTokenID
let tokenId: number = 0;
try {
let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
tokenId = appInfo.accessTokenId;
} catch (err) {
console.error(`getBundleInfoForSelf failed, code is ${err.code}, message is ${err.message}`);
}
// 校验应用是否被授予权限
try {
grantStatus = await atManager.checkAccessToken(tokenId, permission);
} catch (err) {
console.error(`checkAccessToken failed, code is ${err.code}, message is ${err.message}`);
}
return grantStatus;
}
//检查用户权限
//@params permissions:权限名称数组
export async function checkPermissions(permissions: Permissions): Promise<boolean> {
try {
let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permissions);
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
}
catch (e) {
return Promise.reject(e)
}
}
调用:
const READ_MEDIA_PERMISSION: Permissions = 'ohos.permission.READ_MEDIA' //媒体读取权限
const WRITE_MEDIA_PERMISSION: Permissions = 'ohos.permission.WRITE_MEDIA' //媒体写入权限
let permissionList: Permissions[] = []; //需要申请选项列表
let readPermission = await checkPermissions(READ_MEDIA_PERMISSION)//检查是否有媒体读取权限
!readPermission && permissionList.push(READ_MEDIA_PERMISSION)
let writePermission = await checkPermissions(WRITE_MEDIA_PERMISSION)//检查是否有媒体写入权限
!writePermission && permissionList.push(READ_MEDIA_PERMISSION)
(2)申请权限
工具类文件:
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import common from '@ohos.app.ability.common'
interface rejectObj {
code: number
message: string
}
/**
* 申请权限
* @params context:AblitiyContext
* @params permissions:权限名称数组
* @returns Promise<boolean>:是否授权成功
*/
export async function applyPermission(context: common.UIAbilityContext, permissions: Array<Permissions>): Promise<boolean> {
let atManager = abilityAccessCtrl.createAtManager();
return new Promise((resolve: (res: boolean) => void, reject: (e: rejectObj) => void) => {
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
let grantStatus: Array<number> = data.authResults;
resolve(grantStatus.every(item => item === 0))
}).catch((err: rejectObj) => {
reject(err)
})
})
}
调用:
private context = getContext(this) as common.UIAbilityContext; //UIAbilityContext
.....
.....
.....
//申请权限
let res: boolean = await applyPermission(this.context, permissionList)
if (!res) {//用户未同意授权
AlertDialog.show({
title: "提示",
message: "无权限读写用户外部存储中的媒体文件信息,请前往系统设置开启",
alignment: DialogAlignment.Center,
secondaryButton: {
value: '关闭',
action: () => {
}
}
})
}
(1)从手机存储选择图片
import picker from '@ohos.file.picker';
....
....
//从相册选择
let PhotoSelectOptions = new picker.PhotoSelectOptions();
PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
PhotoSelectOptions.maxSelectNumber = 1;
let photoPicker = new picker.PhotoViewPicker();
photoPicker.select(PhotoSelectOptions).then(async (PhotoSelectResult) => {
if (PhotoSelectResult.photoUris.length) {
console.log(`图片本地路径:${PhotoSelectResult.photoUris[0]}`)
}
})
(2)拍照
import camera from '@ohos.multimedia.camera';
import camerapicker from '@ohos.multimedia.cameraPicker';
import { BusinessError } from '@ohos.base';
....
....
try{
let pickerProfile: camerapicker.PickerProfile = {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
};
let pickerResult: camerapicker.PickerResult = await camerapicker.pick(this.context,
[camerapicker.PickerMediaType.PHOTO, camerapicker.PickerMediaType.PHOTO], pickerProfile);
} catch (error) {
let err = error as BusinessError;
console.error(`the pick call failed. error code: ${err.code}`);
}
默认复制图片到缓存目录cache根路径下,移动后文件名前面加上当前时间戳区分:timestamep+原name.格式
import fs from '@ohos.file.fs';
/**
* 复制文件到缓存目录下
* @param path :文件路径
* @param context :Context
* @returns Promise<string> 移动后文件路径
*/
export async function copyFileToCache(path: string,context:Context): Promise<string> {
try {
let file = fs.openSync(path, fs.OpenMode.READ_WRITE)
if (file) {
let fileDir: string = `${context.cacheDir}` //临时文件目录
//时间戳生成随机文件名
let newPath: string = `${new Date().getTime()}_${path.split("/")[path.split("/").length-1]}`
let targetPath: string = `${fileDir}/${newPath}`
fs.copyFileSync(file.fd, targetPath)
return newPath
}
else {
return ''
}
} catch (e) {
return Promise.resolve('')
}
}
//开始上传图片 path:图片路径后缀(图片名称)
async uploadImage(path: string) {
let uri=`internal://cache/${path}` //上传图片全路径
let uploadConfig: request.UploadConfig = {
url:"http://xxxxxxx",
header:{},
method: "POST",
files: [{ filename: path, name: "file", uri, type: path.split('.')[path.split('.').length-1] }],
data: [],
};
try {
let uploadTask:request.UploadTask=await request.uploadFile(this.context, uploadConfig)
//上传中回调
uploadTask.on('progress', (size,total) => {
console.log(size.toString(),total.toString(),'上传进度')
})
//每上传一张图片成功回调
uploadTask.on('headerReceive', (data: object) => {
console.info("upOnComplete success data" + JSON.stringify(data));
})
//所有图片上传完成回调
uploadTask.on('complete', (taskStates: request.TaskState[]) => {
console.info("upOnComplete complete taskState:" + JSON.stringify(taskStates));
})
//上传失败回调
uploadTask.on('fail', (taskStates: request.TaskState[]) => {
console.info("upOnComplete fail taskState:" + JSON.stringify(taskStates));
})
}catch (e){
console.log( JSON.stringify(e),'e')
}
}
需要注意的是我们在复制图片步骤中通过context.cacheDir获取到的缓存目录路径如下所示:
"path":"/data/storage/el2/base/haps/entry/cache/1717854801890_IMG_20240603_170235.jpg"
需转换成internal协议路径,
前面 “/data/storage/el2/base/haps/entry/cache"实际等价于"internal://cache”
所以在上传接口拼接uri参数时候只需要知道图片名称+格式即可,最终上传参数拼接后路径为internal://cache/1717854801890_IMG_20240603_170235.jpg
完整代码将封装一个完整的组件,自定义底部弹窗菜单选择拍照或从手机相册选择,选完自动上传。
代码目录结构
utils/index.ets(工具类):
import fs from '@ohos.file.fs';
import bundleManager from '@ohos.bundle.bundleManager';
import abilityAccessCtrl, { Context, Permissions } from '@ohos.abilityAccessCtrl';
import common from '@ohos.app.ability.common'
/**
* 复制文件到缓存目录下
* @param path :文件路径
* @param context :Context
* @returns Promise<string> 移动后文件路径
*/
export async function copyFileToCache(path: string,context:Context): Promise<string> {
try {
let file = fs.openSync(path, fs.OpenMode.READ_WRITE)
if (file) {
let fileDir: string = `${context.cacheDir}` //临时文件目录
//时间戳生成随机文件名
let newPath: string = `${new Date().getTime()}_${path.split("/")[path.split("/").length-1]}`
let targetPath: string = `${fileDir}/${newPath}`
fs.copyFileSync(file.fd, targetPath)
return newPath
}
else {
return ''
}
} catch (e) {
return Promise.resolve('')
}
}
//校验应用是否授予权限
//@params permissions:权限名称数组
//@return permissionabilityAccessCtrl:权限名称
async function checkAccessToken(permission: Permissions): Promise<abilityAccessCtrl.GrantStatus> {
let atManager = abilityAccessCtrl.createAtManager();
let grantStatus: abilityAccessCtrl.GrantStatus = 0;
// 获取应用程序的accessTokenID
let tokenId: number = 0;
try {
let bundleInfo: bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
tokenId = appInfo.accessTokenId;
} catch (err) {
console.error(`getBundleInfoForSelf failed, code is ${err.code}, message is ${err.message}`);
}
// 校验应用是否被授予权限
try {
grantStatus = await atManager.checkAccessToken(tokenId, permission);
} catch (err) {
console.error(`checkAccessToken failed, code is ${err.code}, message is ${err.message}`);
}
return grantStatus;
}
//检查用户权限
//@params permissions:权限名称数组
export async function checkPermissions(permissions: Permissions): Promise<boolean> {
try {
let grantStatus: abilityAccessCtrl.GrantStatus = await checkAccessToken(permissions);
return grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED
}
catch (e) {
return Promise.reject(e)
}
}
interface rejectObj {
code: number
message: string
}
/**
* 申请权限
* @params context:AblitiyContext
* @params permissions:权限名称数组
* @returns Promise<boolean>:是否授权成功
*/
export async function applyPermission(context: common.UIAbilityContext, permissions: Array<Permissions>): Promise<boolean> {
let atManager = abilityAccessCtrl.createAtManager();
return new Promise((resolve: (res: boolean) => void, reject: (e: rejectObj) => void) => {
atManager.requestPermissionsFromUser(context, permissions).then((data) => {
let grantStatus: Array<number> = data.authResults;
resolve(grantStatus.every(item => item === 0))
}).catch((err: rejectObj) => {
reject(err)
})
})
}
ImageUploadDialog.ets(图片上传弹窗菜单选择组件):
import picker from '@ohos.file.picker';
import { checkPermissions, applyPermission, copyFileToCache } from '../../utils/index'
import { request } from '@kit.BasicServicesKit';
import { Permissions } from '@ohos.abilityAccessCtrl';
import camera from '@ohos.multimedia.camera';
import camerapicker from '@ohos.multimedia.cameraPicker';
import { BusinessError } from '@ohos.base';
import { common } from '@kit.AbilityKit';
//上传回调数据类型
interface ReceiveRes {
body: string
headers: object
}
@Extend(Text)
function custText() {
.width('100%')
.height('48')
.fontColor('#39364D')
.textAlign(TextAlign.Center)
}
@CustomDialog
export default struct ImageUploadDialog {
dialogController: CustomDialogController
@Prop uploadURL:string='';//上传接口地址
private context = getContext(this) as common.UIAbilityContext; //UIAbilityContext
private success: (res: ReceiveRes) => void = () => {} //上传成功回调
private fail: (res: request.TaskState[]) => void = () => {} //上传失败回调
private complete: (res: request.TaskState[]) => void = () => {} //上传完成回调
//检查权限
async checkAppPermission(): Promise<boolean> {
try {
const READ_MEDIA_PERMISSION: Permissions = 'ohos.permission.READ_MEDIA' //媒体读取权限
const WRITE_MEDIA_PERMISSION: Permissions = 'ohos.permission.WRITE_MEDIA' //媒体写入权限
let permissionList: Permissions[] = []; //需要申请选项列表
let readPermission = await checkPermissions(READ_MEDIA_PERMISSION)//检查是否有媒体读取权限
!readPermission && permissionList.push(READ_MEDIA_PERMISSION)
let writePermission = await checkPermissions(WRITE_MEDIA_PERMISSION)//检查是否有媒体写入权限
!writePermission && permissionList.push(READ_MEDIA_PERMISSION)
if (permissionList.length) {
//申请权限
let res: boolean = await applyPermission(this.context, permissionList)
if (!res) {//用户未同意授权
AlertDialog.show({
title: "提示",
message: "无权限读写用户外部存储中的媒体文件信息,请前往系统设置开启",
alignment: DialogAlignment.Center,
secondaryButton: {
value: '关闭',
action: () => {
}
}
})
}
return res
}
return true
}
catch (e) {
return Promise.reject(e)
}
}
//开始上传图片 path:图片路径后缀(图片名称)
async uploadImage(path: string) {
console.log(path, 'path')
let uri=`internal://cache/${path}` //上传图片全路径
let uploadConfig: request.UploadConfig = {
url:this.uploadURL,
header:{},
method: "POST",
files: [{ filename: path, name: "file", uri, type: path.split('.')[path.split('.').length-1] }],
data: [],
};
try {
let uploadTask:request.UploadTask=await request.uploadFile(this.context, uploadConfig)
//上传中回调
uploadTask.on('progress', (size,total) => {
console.log(size.toString(),total.toString(),'上传进度')
})
//每上传一张图片成功回调
uploadTask.on('headerReceive', (data: object) => {
let res = data as ReceiveRes
this.success && this.success(res)
})
//所有上传完成回调
uploadTask.on('complete', (taskStates: request.TaskState[]) => {
console.info("upOnComplete complete taskState:" + JSON.stringify(taskStates));
this.complete && this.complete(taskStates)
})
//上传失败回调
uploadTask.on('fail', (taskStates: request.TaskState[]) => {
console.info("upOnComplete fail taskState:" + JSON.stringify(taskStates));
this.fail&&this.fail(taskStates)
})
}catch (e){
console.log( JSON.stringify(e),'e')
}
}
build() {
Column() {
//拍照
Text('拍照').custText().onClick(async()=>{
//检查是否有读写外部媒体权限
let res: boolean = await this.checkAppPermission()
//无权限返回
if (!res) return
try {
let pickerProfile: camerapicker.PickerProfile = {
cameraPosition: camera.CameraPosition.CAMERA_POSITION_BACK
};
let pickerResult: camerapicker.PickerResult = await camerapicker.pick(this.context,
[camerapicker.PickerMediaType.PHOTO, camerapicker.PickerMediaType.PHOTO], pickerProfile);
if(pickerResult?.resultUri){
//关闭弹窗
this.dialogController.close()
//复制图片到缓存目录(缓存目录才有读写权限)
let filePath = await copyFileToCache(pickerResult.resultUri, this.context)
if (filePath) {
//上传头像并设置
this.uploadImage(filePath)
}
}
} catch (error) {
let err = error as BusinessError;
console.error(`the pick call failed. error code: ${err.code}`);
}
})
Divider().color('#F7F9FA').width('100%').strokeWidth(1)
//从手机相册选择
Text('从手机相册选择').custText().onClick(async () => {
//检查是否有读写外部媒体权限
let res: boolean = await this.checkAppPermission()
//无权限返回
if (!res) return
//关闭弹窗
this.dialogController.close()
//从相册选择
let PhotoSelectOptions = new picker.PhotoSelectOptions();
PhotoSelectOptions.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE;
PhotoSelectOptions.maxSelectNumber = 1;
let photoPicker = new picker.PhotoViewPicker();
photoPicker.select(PhotoSelectOptions).then(async (PhotoSelectResult) => {
if (PhotoSelectResult.photoUris.length) {
//复制图片到缓存目录(缓存目录才有读写权限)
let filePath = await copyFileToCache(PhotoSelectResult.photoUris[0],this.context)
if (filePath) {
this.uploadImage(filePath)
}
}
})
})
Button('取消', { type: ButtonType.Capsule })
.backgroundColor('#F7F7F7')
.fontSize('16fp')
.fontColor('#333333')
.width('100%')
.margin({ top: '30' })
.onClick(() => {
this.dialogController.close()
})
}.width('100%').padding({ left: '16', top: '11', right: '16', bottom: '16' })
.backgroundColor(Color.White)
.borderRadius({
topLeft: '24',
topRight: '24'
})
}
}
组件入参 说明:
uploadURL:上传接口url
success:(res: ReceiveRes)=>void 上传成功回调函数
fail:(res: request.TaskState[])=>void=()=>{} //上传失败回调
complete: (res: request.TaskState[]) => void = () => {} //上传完成回调
完成或失败回调参数说明
request.TaskState[]: {
path:string //图片在本地路径
message:string //上传结果信息
responseCode //上传结果状态码 0:成功,其他值失败
}[]
成功回调参数说明
interface ReceiveRes {
body: string //接口返回数据,字符串类型需要通过JSON.Parse转对象来取值
headers: object //请求头数据,对象类型
}
获取上传成功接口返回图片url可通过uploadTask.on(‘headerReceive’,callback)获取
pages/Index
import ImageUploadDialog from '../components/ImageUploadDialog/ImageUploadDialog'
import { promptAction } from '@kit.ArkUI'
@Entry
@Component
struct Index {
@State dialogController: CustomDialogController | null = null //选择上传类型弹窗控制器
aboutToAppear(): void {
this.dialogController= new CustomDialogController({
builder: ImageUploadDialog({
uploadURL: 'http://xxxxxxxxx',//上传地址
success:e=>{//上传成功回调,e上传成功接口返回数据
let res= JSON.parse(e.body) as object //接口上传成功返回数据
console.log(JSON.stringify(res),'上传成功')
//根据实际接口返回字段获取图片url
//url=res['data']
},
fail:e=>{//上传失败回调
console.log(JSON.stringify(e))
promptAction.showToast({message:'上传失败'})
},
complete:e=>{//上传完成回调
console.log(JSON.stringify(e),'complete')
}
}),
alignment: DialogAlignment.Bottom,//弹窗居于底部
customStyle: true,//自定义样式
})
}
build() {
Column(){
Button('上传').onClick(()=>{
this.dialogController?.open()
})
}.width('100%')
}
}
最后不要忘记添加权限
三个:
"ohos.permission.INTERNET":网访问权限
"ohos.permission.READ_MEDIA":外部存储设备媒体读取权限
"ohos.permission.WRITE_MEDIA":外部存储设备媒体写入权限
module.json5:
//权限
requestPermissions: [{
"name": "ohos.permission.INTERNET",
},{
"name": "ohos.permission.READ_MEDIA",
"reason": "$string:reasonReadWriteMedia",//使用权限原因
"usedScene": {
"abilities": [//使用的该权限的EntryAbility名称数组
"EntryAbility"
],
"when": "inuse"
}
},{
"name": "ohos.permission.WRITE_MEDIA",
"reason": "$string:reasonReadWriteMedia",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
}]
entry\src\main\resources\base\element\string.json
{
"string": [
,{
"name":"reasonReadWriteMedia",
"value": "上传头像"
}
]
}
效果:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。