OpenHarmony 文件監(jiān)聽開發(fā)樣例介紹
OpenHarmony是由開放原子開源基金會(OpenAtom Foundation)孵化及運營的開源項目,目標是面向全場景、全連接、全智能時代,基于開源的方式,搭建一個智能終端設備操作系統(tǒng)的框架和平臺,促進萬物互聯(lián)產(chǎn)業(yè)的繁榮發(fā)展。
監(jiān)聽機制是一種常見的計算機安全技術,它可以監(jiān)控計算機系統(tǒng)中的各種活動,以便及時發(fā)現(xiàn)和處理安全問題。文件監(jiān)聽具有重要的現(xiàn)實意義,對文件進行監(jiān)聽,企業(yè)可以及時發(fā)現(xiàn)員工不當處理敏感信息和意外泄漏信息的情況,采取行動避免數(shù)據(jù)泄露。
本文以文件管理FileManager為例,介紹文件監(jiān)聽的應用實現(xiàn)以此了解@ohos.file.fs模塊中涉及的接口的使用方法。
一、開發(fā)環(huán)境準備
下載并安裝DevEco Studio,建議使用DevEco Studio 3.1 Release (Build Version: 3.1.0.500, built on April 28, 2023)及以上版本:
- DevEco Studio下載鏈接
- 下載sample倉庫源碼:
- sample倉源碼路徑
下載源碼之后將FileManager工程導入DevEco Studio,首先對工程進行自動化簽名,然后進行編譯構(gòu)建生成HAP應用安裝包,安裝應用示例之前,請先查看"README_zh.md"文件來確認應用示例是否為stage模型,若為Stage模型需要查看entry/src/main路徑下的module.json5文件中的"deviceType"字段來確認該應用支持的設備類型;否則為FA模型,查看entry/src/main路徑下的config.json文件中的"deviceType"字段來確認該應用示例支持的設備類型,兩種模型都可嘗試通過修改該字段使其可以在相應類型的設備上運行。文件管理FileManager為stage模型,支持設備為rk3568。安裝運行之后,即可在設備上查看應用示例運行效果,以及進行相關調(diào)試。
二、實現(xiàn)
文件管理新增功能為文件監(jiān)聽,使用@ohos.file.fs模塊watcher接口對文件、文件夾進行監(jiān)控,添加相應監(jiān)聽后對文件進行增加、刪除、修改操作后日志信息顯示出相應監(jiān)聽日志,停止相應監(jiān)聽后對文件進行增加、刪除、修改操作后日志信息無變化。
在使用接口時需要查看并配置接口對應的權限,監(jiān)聽文件時使用了系統(tǒng)接口,需要配置系統(tǒng)應用簽名,可以參考特殊權限配置方法 ,把配置文件中的“app-feature”字段信息改為“hos_system_app”,再將“apl”字段信息改為“system_core”。另外,監(jiān)聽文件使用了API10接口,因此本工程僅支持API10版本的SDK。
1、流程圖
OpenHarmony 文件監(jiān)聽開發(fā)樣例介紹-開源基礎軟件社區(qū)
2、實現(xiàn)過程
(1)增加監(jiān)聽
使用數(shù)組watcherEvent保存監(jiān)聽變動的事件集,使用組件TextPickerDialog選擇監(jiān)聽變動事件集,當用戶選擇一個監(jiān)聽事件添加監(jiān)聽時,調(diào)用startWatcher()方法進行監(jiān)聽,并將其保存到已添加監(jiān)聽事件WatcherList數(shù)組中。
src/main/ets/pages/WatcherFile.ets:
TextPickerDialog.show({
range: this.watcherEvent,
selected: this.select,
onAccept: (value: TextPickerResult) => {
this.select = Number(value.index);
this.eachEvent = this.watcherEvent[this.select];
this.myWatcher.startWatcher(this.eachEvent);
this.eventArray.push(this.eachEvent); // 新增列表項數(shù)據(jù)
}
})
在startWatcher()方法中,根據(jù)需要添加的監(jiān)聽事件使用fs.createWatcher()創(chuàng)建Watcher對象,用來監(jiān)聽文件或目錄變動,調(diào)用watcher.start()開啟監(jiān)聽,當監(jiān)聽事件發(fā)生一次,回調(diào)將會獲取監(jiān)聽事件相關日志。
src/main/ets/filemanager/fileFs/MyWatcher.ts:
startWatcher(watcherName: string): void {
let watcherDir: string = this.baseDir + '/watcherDir';
Logger.info(TAG, `${watcherName}-startWatcher start path = ${watcherDir}`);
if (this.watcherCodeMap.has(watcherName)) { // 是否存在key 'watchName': true
try {
Logger.info(TAG, `${watcherName}-startWatcher has watchName`);
let watcher: fs.Watcher = fs.createWatcher(watcherDir, this.watcherCodeMap.get(watcherName), (data) => { //獲取key對應的value
AppStorage.SetOrCreate('eventLog', JSON.stringify(data.event));
AppStorage.SetOrCreate('fileNameLog', data.fileName);
AppStorage.SetOrCreate('cookieLog', JSON.stringify(data.cookie));
Logger.info(TAG, `${watcherName}-startWatcher :{event: ${data.event}, fileName: ${data.fileName}, cookie: ${data.cookie}}`);
});
watcher.start();
setTimeout(async () => {
this.watcherList.push(watcher);
}, this.timeOut);
Logger.info('watcherList is ' + JSON.stringify(this.watcherList));
} catch (e) {
Logger.error(TAG, `${watcherName}-startWatcher has failed for: {message: ${e.message}, code: ${e.code}}`);
}
} else {
Logger.info(TAG, `${watcherName}-startWatcher does not in watcherCodeMap`);
}
}
(2)停止監(jiān)聽
當用戶選擇一個監(jiān)聽停止監(jiān)聽時,調(diào)用stopWatcher()方法停止監(jiān)聽,并將已添加監(jiān)聽事件WatcherList數(shù)組中該監(jiān)聽事件移除。
src/main/ets/pages/WatcherFile.ets:
TextPickerDialog.show({
range: showEventArray,
selected: this.selectDel,
onAccept: (value: TextPickerResult) => {
this.selectDel = Number(value.index);
this.myWatcher.stopWatcher(this.myWatcher.watcherList[this.selectDel], this.selectDel);
this.eventArray.splice(this.selectDel, 1); // 刪除列表項數(shù)據(jù)
showEventArray.splice(this.selectDel, 1);
}
})
在stopWatcher()方法中,調(diào)用watcher.stop()停止監(jiān)聽,當停止監(jiān)聽的事件發(fā)生時,不再產(chǎn)生監(jiān)聽日志。
src/main/ets/filemanager/fileFs/MyWatcher.ts:
stopWatcher(watcher: fs.Watcher, index: number): void {
if (watcher !== null) {
watcher.stop();
setTimeout(async () => {
this.watcherList.splice(index, 1);
}, this.timeOut);
Logger.info(TAG, 'stopWatcher successful');
} else {
Logger.info(TAG, 'stopWatcher null');
}
}
(3)增加文件
當對應用沙箱文件或文件夾添加IN_CREATE監(jiān)聽后,在沙箱文件路徑下新增一個文件,應用內(nèi)則會打印出相應監(jiān)聽日志,使用fs.openSync打開文件,fs.writeSync方法對文件進行寫入內(nèi)容,fs.closeSync關閉文件。在沙箱路徑下增加一個文件的具體實現(xiàn)方法如下所示:
src/main/ets/filemanager/fileFs/MyWatcher.ts:
addFileToWatcher(path: string): void {
let content = CONTENT + '\n';
try {
let addFile = this.baseDir + '/watcherDir/' + path;
Logger.info('addFileToWatcher addFile = ' + addFile);
let file = fs.openSync(addFile, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE | fs.OpenMode.TRUNC);
fs.writeSync(file.fd, content);
fs.closeSync(file);
} catch (e) {
Logger.error(TAG, `addFileToWatcher has failed for: {message: ${e.message}, code: ${e.code}}`);
}
}
(4)刪除文件
當對應用沙箱文件或文件夾添加IN_DELETE監(jiān)聽后,在沙箱文件路徑下刪除一個文件,應用內(nèi)則會打印出相應監(jiān)聽日志,使用fs.unlink刪除文件。在沙箱路徑下刪除一個文件的具體實現(xiàn)方法如下所示:
src/main/ets/filemanager/fileFs/MyWatcher.ts:
deleteFileToWatcher(path: string): void {
try {
let deleteFile = this.baseDir + '/watcherDir/' + path;
Logger.info(TAG, 'deleteFileToWatcher deleteFile = ' + deleteFile);
fs.unlink(deleteFile).then(() => {
Logger.info(TAG, 'deleteFileToWatcher file succeed');
}).catch((err) => {
Logger.info(TAG, 'deleteFileToWatcher file failed with error message: ' + err.message + ', error code: ' + err.codeor);
});
} catch (e) {
Logger.error(TAG, `readyFileToWatcher has failed for: {message: ${e.message}, code: ${e.code}}`);
}
}
(5)修改文件
當對應用沙箱文件或文件夾添加IN_MODIFY監(jiān)聽后,在沙箱文件路徑下對一個文件內(nèi)容進行修改,應用內(nèi)則會打印出相應監(jiān)聽日志,(注意:若只修改文件名,應用內(nèi)不會打印監(jiān)聽日志)。使用fs.renameSync對文件名進行修改,使用fs.openSync打開文件,fs.writeSync方法對文件進行寫入內(nèi)容,fs.closeSync關閉文件。在沙箱路徑下修改一個文件的具體實現(xiàn)方法如下所示:
src/main/ets/filemanager/fileFs/MyWatcher.ts:
modifyFileToWatcher(oldName: string, newName: string, content: string): number {
// 重命名文件
let filePath = null;
if (newName.trim() === oldName.trim()) {
filePath = this.baseDir + '/watcherDir/' + oldName;
Logger.info('modifyFileToWatcher The new file name is the same as the old file name');
} else {
try {
let srcFile = this.baseDir + '/watcherDir/' + oldName;
Logger.info('modifyFileToWatcher srcFile = ' + srcFile);
let dstFile = this.baseDir + '/watcherDir/' + newName;
filePath = dstFile;
Logger.info('modifyFileToWatcher dstFile = ' + dstFile);
fs.renameSync(srcFile, dstFile);
} catch (e) {
Logger.info(`modifyFileToWatcher -readyFileRW-creat has failed for: {message: ${e.message}, code: ${e.code}}`);
}
}
// 修改文件內(nèi)容
Logger.info('modifyFileContentToWatcher filePath = ' + filePath);
try {
/* 修改文件內(nèi)容時必須以fs.OpenMode.TRUNC打開文件,如果文件存在且以只寫或讀寫的方式打開文件,則將其長度裁剪為零。這樣修改文件內(nèi)容時,若修改后的文件 * 比修改前小,也會正確修改,否則修改后文件的文件比未修改前小時,則修改錯誤。
*/
let file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE | fs.OpenMode.TRUNC);
let writeLen = fs.writeSync(file.fd, content);
Logger.info('modifyFileContentToWatcher write data to file succeed and size is:' + writeLen);
fs.closeSync(file);
let fileRead = fs.openSync(filePath, fs.OpenMode.READ_WRITE);
let buf = new ArrayBuffer(BUFFER);
this.fileSize = fs.readSync(fileRead.fd, buf);
let resultPut = bufferToString(buf);
Logger.info('modifyFileContentToWatcher Read num = ' + this.fileSize);
Logger.info('modifyFileContentToWatcher Read resultPut = ' + resultPut);
fs.closeSync(fileRead);
Logger.info('modifyFileContentToWatcher write data to file succeed and fileSize is:' + this.fileSize);
} catch (e) {
Logger.info(`-readyFileRW-creat has failed for: {message: ${e.message}, code: ${e.code}}`);
}
return this.fileSize;
}
}
測試
sample功能完成之后,需要對開發(fā)完成的功能進行測試,此時就需要增加自動化測試用例實現(xiàn)對功能的基本測試。UiTest可以通過簡潔易用的API提供查找和操作界面控件能力,支持用戶開發(fā)基于界面操作的自動化測試腳本。如何編寫UI測試用例參考編寫UI測試腳本,測試執(zhí)行完畢后可直接在DevEco Studio中查看測試結(jié)果,如下所示:
總結(jié)
通過使用這個示例,開發(fā)者可以快速掌握文件監(jiān)聽的實現(xiàn)方法,并將其運用到自己的應用中,實現(xiàn)更強大和豐富的功能。同時,這個示例也提供了一個學習和交流的平臺,開發(fā)者可以與其他開發(fā)者分享經(jīng)驗和技巧,共同推動OpenHarmony生態(tài)的發(fā)展。