沙箱机制
1. 什么是沙箱机制?
1.1. 概念
在操作系统当中,沙箱机制(Sandboxing)是一种安全机制,用于限制程序代码的访问权限,防止恶意软件对系统造成破坏。在沙箱环境中,程序只能访问特定的资源,如文件、注册表、网络等,而不能访问其他系统资源。这样,即使恶意软件试图执行有害操作,也会因为权限限制而无法成功。
1.2. 沙箱机制的主要用途包括:
-
防止恶意软件破坏系统:通过限制程序的访问权限,沙箱机制可以防止恶意软件修改系统文件、注册表等关键资源,保护系统的安全。
-
保护用户隐私:沙箱机制可以限制程序访问用户的个人信息,如文档、图片等,防止恶意软件窃取用户隐私。
-
防止系统崩溃:沙箱机制可以限制程序的资源使用,如内存、CPU等,防止恶意软件占用过多资源导致系统崩溃。
-
提供测试环境:沙箱环境允许开发人员在不影响实际系统的情况下测试程序的功能和性能。
总之,沙箱机制是一种有效的安全机制,可以保护系统资源、用户隐私和系统稳定性,防止恶意软件对系统造成破坏。
2. 鸿蒙沙箱机制
2.1. 原理
鸿蒙操作系统
(OpenHarmony)中的沙箱机制
是一个重要的安全特性,它为每个应用提供了一个独立、安全的运行环境。这种机制通过将应用的文件(包括安装文件、资源文件和缓存文件)隔离在一个独立的沙箱目录中来实现,这样每个应用都能够在自己的沙箱中运行,互不干扰,从而提高了系统的安全性
和稳定性
。
被隔离的沙箱环境如何访问系统资源?
在HarmonyOS
中,每个应用都有其独立的沙箱目录,用于存储应用的文件。沙箱路径是指应用内部的文件路径,而物理路径则是指文件在存储设备上的实际路径。通过映射关系
,沙箱路径
和物理路径
可以相互转换,使得应用可以访问其所需的文件资源。
沙箱有什么具体使用场景?
-
进行文件读写时
HarmonyOS的文件API分为
同步
和异步
两种操作方式。在进行耗时的文件操作时(如文件拷贝),应避免在主进程中进行同步操作,因为这可能会阻塞主进程。相反,应选择使用异步操作或新建一个worker来执行文件操作。例如,可以使用fs.openSync方法来异步打开一个文件,并指定打开模式为读写模式,如果不存在则创建。 -
使用第三方模块时
对于app调用三方模块时出现的问题,如果app源码没有使用绝对路径去访问文件,可以通知三方模块,让其访问文件路径通过context接口进行访问而非使用绝对路径的方式。这样可以确保三方模块也能够遵守沙箱文件的访问规则,从而避免出现路径访问异常。
2.2. 场景演示(系统文件读写)
由于沙箱安全机制的规则,开发者是无法直接访问系统资源的目录的,可以将原来访问/data目录的绝对路径访问方式调整为使用context接口进行访问,也就是访问沙箱目录下的缓存目录(沙箱目录和系统物理路径是可以通过映射关系相互转换的),通过fs.openSync方法来异步打开一个文件,并指定打开模式为读写模式,如果不存在则创建。再使用fs.copyFileSync将文件的内存地址写入沙箱路径的缓存目录中。
//openSync获取系统中该文件对应的内存地址
const file = fs.openSync(photoImagePath, fs.OpenMode.READ_ONLY)
//copyFileSync将内存地址写入沙箱目录中
let Path = getContext().cacheDir + '/' + fileName
fs.copyFileSync(file.fd, Path)
效果:
参考代码:
import { picker } from '@kit.CoreFileKit'
import fs from '@ohos.file.fs';
import emitter from '@ohos.events.emitter';
import { LoadingDialog1 } from '../components/LoadingDialog';
import { ILoginUserModel } from '../../models/AccountModel';
import { Logger } from '../utils/Logger';
import { request } from '@kit.BasicServicesKit';
import { HdHttp } from '../utils/request';
import { promptAction } from '@kit.ArkUI';
@Component
//用于图片上传的组件
export struct PictureUpload {
@Prop
showPicture: string
// 打开上传进度自定义弹窗
dialog: CustomDialogController = new CustomDialogController({
builder: LoadingDialog1({ message: '上传进度:0' }),
customStyle: true,
alignment: DialogAlignment.Center
})
// 用户选择系统中的一个图片返回
async pickerAvatar() {
// 1. 引导用户选择一张系统相册的照片
// 1.1. 限定让用户只能从系统相册中选择1张图片
const options = new picker.PhotoSelectOptions()
options.MIMEType = picker.PhotoViewMIMETypes.IMAGE_TYPE //只能选择图片类型的资源
options.maxSelectNumber = 1 //只能选择一张系统相册中的照片
// 1.2. 利用PhotoViewPicker对象实例中的select自动获取到用户选择的那张图片的地址
const pickerView = new picker.PhotoViewPicker()
let urls = await pickerView.select(options)
if (urls.photoUris.length <= 0) {
return
}
let photoImagePath = urls.photoUris[0]
// 2. 将需要上传的图片写入缓存地址
// 2.1 定义一个不重复的文件名和文件类型
const fileTypeIndex = photoImagePath.lastIndexOf('.')
const fileType = photoImagePath.slice(fileTypeIndex + 1)
const fileName = Date.now() + '.' + fileType
// 2.2. openSync获取系统中该文件对应的内存地址
const file = fs.openSync(photoImagePath, fs.OpenMode.READ_ONLY)
// 2.3. copyFileSync将内存地址写入沙箱目录中
let Path = getContext().cacheDir + '/' + fileName
fs.copyFileSync(file.fd, Path)
// 2.4. 沙箱目录中缓存文件的地址
const cacheFullPath = `internal://cache/${fileName}`
// 3. 上传
this.dialog.open()
let user = AppStorage.get('user') as ILoginUserModel
let token = user?.token
let upload = await request.uploadFile(getContext(), {
method: 'POST',
url: 'https://api-harmony-teach.itheima.net/hm/userInfo/avatar',
header: {
"Content-Type": "multipart/form-data",
"Authorization": `Bearer ${token}`
},
files: [{
filename: fileName,
type: fileType,
name: 'file',
uri: cacheFullPath
}],
data: []
})
// 监听上传的进度
// uploadedSize:当前上传了多少size
// totalSize:表示当前要上传文件的总大小
upload.on('progress', (uploadedSize, totalSize) => {
// 上传百分比计算:
let pnum = (uploadedSize / totalSize * 100).toFixed(0)
Logger.info('上传进度:', pnum)
// 使用emitter来进行发送事件
emitter.emit({ eventId: 0 }, { data: { process: '已上传:' + pnum } })
// Logger.info('上传大小:' + uploadedSize, '总大小:' + totleSize)
if (uploadedSize === totalSize) {
this.dialog.close()
// 重新更新头像数据
this.update()
}
})
}
// 重新获取最新的图片更新到AppStorage中
async update() {
let res = await HdHttp.get<object>('userInfo')
// 由于我只需要服务器返回数据中的avatar字段的值,所以这里直接采取对象中括号取值法获取结果即可
// 好处:省略掉接口的定义
this.showPicture = res.data['avatar']
promptAction.showToast({ message: '头像修改成功' })
}
build() {
Column() {
Image(this.showPicture || $rawfile('avatar.png'))
.width((40))
.width((40))
.borderRadius((40))
.border({ width: 0.5, color: '#e4e4e4' })
.onClick(async () => {
// 选择系统相册图片进行上传
this.pickerAvatar()
})
}
}
}