HarmonyOS實現(xiàn)動態(tài)申請授權(quán)
應(yīng)用向用戶動態(tài)申請授權(quán),是指在用戶使用應(yīng)用的過程中,應(yīng)用方會根據(jù)應(yīng)用場景和業(yè)務(wù)向用戶動態(tài)地請求相應(yīng)的權(quán)限。例如,當(dāng)應(yīng)用需要訪問用戶的相機或麥克風(fēng)時,會向用戶彈出一個授權(quán)請求框,詢問用戶是否允許應(yīng)用訪問這些設(shè)備,而用戶可以選擇允許或拒絕此次授權(quán)請求。這種方式可以提高應(yīng)用的安全性,在一定程度上保護用戶的隱私信息安全。
Nothing theoretical can be clearer than the code. 本期筆者將以一個Demo為例,與讀者們共同探討在ArkUI的框架中如何實現(xiàn)動態(tài)申請授權(quán)的功能。
通常情況下,一些提供基礎(chǔ)功能的權(quán)限可以通過靜態(tài)的方式獲?。粗苯訉㈤_發(fā)者需要的權(quán)限在模塊級別的module.json5文件中聲明),如聯(lián)網(wǎng)權(quán)限。而對于一些能夠為應(yīng)用提供用戶的隱私數(shù)據(jù)的敏感權(quán)限,則需要以動態(tài)的方式可視化地向用戶申請。本期的Demo以申請獲取大致位置權(quán)限(即"ohos.permission.APPROXIMATELY_LOCATION")為例,實現(xiàn)動態(tài)申請權(quán)限的功能。
新建工程
打開DevEco Studio(開發(fā)工具的版本必須支持API9),創(chuàng)建一個新的project,相關(guān)勾選如下:
在module.json5中添加相應(yīng)的權(quán)限
成功創(chuàng)建工程后,在工程文件目錄中打開目錄:entry/src/main/module.json5, 添加兩個權(quán)限——定位權(quán)限( "ohos.permission.LOCATION")和獲取大致位置的權(quán)限("ohos.permission.APPROXIMATELY_LOCATION" )。
{
"module": {
"name": "entry",
"type": "entry",
"description": "$string:module_desc",
"mainElement": "EntryAbility",
"deviceTypes": [
"phone"
],
"deliveryWithInstall": true,
"installationFree": false,
"pages": "$profile:main_pages",
//添加模塊所需的相關(guān)權(quán)限
"requestPermissions": [
{
"name": "ohos.permission.APPROXIMATELY_LOCATION",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
},
{
"name": "ohos.permission.LOCATION",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
},
],
......
}
}
事實上,當(dāng)應(yīng)用同時獲取以上兩種權(quán)限后,應(yīng)用可以獲取設(shè)備的精準(zhǔn)位置,精準(zhǔn)度在米級別。
集成功能模塊
要實現(xiàn)向用戶動態(tài)申請授權(quán)的功能,我們需要制作兩個功能模塊,分別是檢查是否已獲得所需權(quán)限的功能和向用戶發(fā)起權(quán)限申請的功能。為了達到公共調(diào)用和功能模塊化,我們需要將這兩個功能模塊集成到兩個不同的TypeScript文件中,并將可調(diào)用接口導(dǎo)出。
在ets文件夾下新建目錄,并將其命名為Service。
在Service目錄下新建兩個TypeScript文件(右鍵Service目錄,選擇新建,再選擇TypeScript),分別命名為Detector與Applicant。
在編輯器中打開Detector.ts,加入以下代碼以集成檢查應(yīng)用是否已獲得所需權(quán)限的功能,各代碼塊的具體功能已寫注解。
//導(dǎo)入程序訪問控制管理模塊
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
//導(dǎo)入包管理模塊
import bundleManager from '@ohos.bundle.bundleManager';
///定義待檢測的權(quán)限列表
const permissionsList: Array<Permissions> = ['ohos.permission.APPROXIMATELY_LOCATION'] //權(quán)限數(shù)據(jù)的列表
//模塊的日志標(biāo)簽
const TAG = '------[Detector] '
const APPROVAL:number = 0
//默認(rèn)導(dǎo)出的模塊接口
export default async function Check_Access(){
//創(chuàng)建AtManager實例
let atManager = abilityAccessCtrl.createAtManager()
//定義局部變量grantStatus
let grantStatus:abilityAccessCtrl.GrantStatus
//定義局部變量tokenId
let tokenId:number
try{
//等待包管理模塊獲取本模塊所在的包的BundleInfo
let bundleInfo:bundleManager.BundleInfo = await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)
//獲取上述BundleInfo中攜帶的ApplicationInfo
let appInfo:bundleManager.ApplicationInfo = bundleInfo.appInfo
//獲上述ApplicationInfo攜帶的accessTokenId
tokenId = appInfo.accessTokenId
}catch (err){
console.error(TAG+`getBundleInfoForSelf failed, code is ${err.code}, message is ${err.message}`)
}
try{
//利用AtManager實例檢查是否已獲得所需權(quán)限
grantStatus = await atManager.checkAccessToken(tokenId,permissionsList[0])
}catch (err){
console.error(TAG+`checkAccessToken failed, code is ${err.code}, message is ${err.message}`)
}
//根據(jù)不同的檢查結(jié)果做不同的輸出
if(grantStatus == APPROVAL){
console.info(TAG+'Accessible')
return true
}else {
console.error(TAG+'Inaccessible')
return false
}
}
因為筆者已給代碼添加了注釋,所以筆者就不對這個模塊做太多啰嗦的分析了。這個模塊的大致執(zhí)行邏輯是,通過調(diào)用系統(tǒng)能力@ohos.bundle.bundleManager獲取本包的accessTokenId,再調(diào)用系統(tǒng)能力@ohos.abilityAccessCtrl,在傳入accessTokenId和權(quán)限列表的條件下檢查module是否已獲取權(quán)限列表中的每個權(quán)限。當(dāng)atManager的異步方法checkAccessToken返回結(jié)果為0時,表示所需權(quán)限均已被提供。
在編輯器中打開Applicant.ts,加入以下代碼以集成向用戶發(fā)起權(quán)限申請的功能,各代碼塊的具體功能已寫注解。
//導(dǎo)入程序訪問控制管理模塊
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl'
//導(dǎo)入common
import common from '@ohos.app.ability.common';
//定義待動態(tài)申請的權(quán)限列表
const permissionsList: Array<Permissions> = ['ohos.permission.APPROXIMATELY_LOCATION']
const APPROVAL:number = 0
//模塊的日志標(biāo)簽
const TAG = '------[Applicant] '
//默認(rèn)導(dǎo)出的模塊接口
export default async function Request_Permission_From_Users(context:common.UIAbilityContext){
//預(yù)定義函數(shù)執(zhí)行結(jié)果的狀態(tài)
let isFinished:boolean = false
//創(chuàng)建AtManager實例
let atManager = abilityAccessCtrl.createAtManager()
//等待程序訪問控制模塊完成權(quán)限請求的異步操作,完成后根據(jù)返回結(jié)果執(zhí)行then()或catch()
await atManager.requestPermissionsFromUser(context, permissionsList).then((result)=>{
//將API返回的數(shù)據(jù)存儲到變量grantStatus中
let grantStatus: Array<number> = result.authResults
//判斷用戶是否提供所有相關(guān)權(quán)限
for(let i = 0 ; i < grantStatus.length ; i++){
if(grantStatus[i] === APPROVAL){ //用戶提供所有權(quán)限, ===指的是全等
console.info(TAG+'Succeed! Obtain all the permissions')
isFinished = true //將函數(shù)執(zhí)行結(jié)果的狀態(tài)設(shè)置為true
}else{ //用戶未提供所有權(quán)限
console.error(TAG+'User denies providing the permissions')
}
}
}).catch((err)=>{
console.error(TAG+`Request permission failed, code is ${err.code}, message is ${err.message}`)
})
return isFinished
}
這個模塊的大致執(zhí)行邏輯是,在atManager的異步方法requestPermissionsFromUser。
中傳入所需的上下文對象和待申請權(quán)限列表,并等待其異步過程的結(jié)束。在requestPermissionsFromUser的異步過程中,系統(tǒng)會彈出選擇框,詢問用戶是否提供權(quán)限。當(dāng)用戶點擊選擇框中的允許或禁止時,權(quán)限會被授予或否,異步過程隨即結(jié)束(此處指的是申請單個權(quán)限的場景),requestPermissionsFromUser將申請結(jié)果以number型數(shù)組的形式輸出。當(dāng)此數(shù)組中的每個元素的值都為0時,表示所有權(quán)限都成功獲得,否則,用戶拒絕授權(quán)了至少一個權(quán)限。
Detector.ts和Applicant.ts均默認(rèn)導(dǎo)出了接口,想要在其他地方調(diào)用它們的功能,只需導(dǎo)入接口即可。
編輯頁面UI
添加圖片資源
在工程文件目錄中打開目錄:src/main/resources/rawfile, 添加兩張任意的圖片(可以在IDE中將待添加的圖片資源直接粘貼至rawfile目錄下,也可以在文件資源管理器中通過文件路徑打開rawfile目錄并添加圖片資源),分別命名為image1和image2。當(dāng)然,圖片的格式?jīng)]有要求,只要在之后的步驟中能被正確引用即可。
設(shè)計頁面UI
打開Index.ets,刪除原有的Text組件,新增兩個Button組件和一個Image組件(相關(guān)屬性設(shè)置如下),并聲明一個用@state修飾的布爾變量ifAccessible。
@Entry
@Component
struct Index {
//將應(yīng)用是否獲取權(quán)限這條信息用布爾型變量ifAccessible儲存
@State ifAccessible:boolean = false
build() {
Row() {
Column() {
//添加兩個Button組件和一個Image組件
//條件渲染Image組件
if(this.ifAccessible){
Image($rawfile('image2.png'))
.height(200)
.width(200)
}else{
Image($rawfile('image1.png'))
.height(200)
.width(200)
}
//第一個Button組件
Button('檢查應(yīng)用是否獲得權(quán)限')
.fontSize(20)
.width('70%')
.margin({
top:40
})
.backgroundColor(Color.Pink)
//第二個Button組件
Button('向用戶動態(tài)申請權(quán)限')
.fontSize(20)
.width('70%')
.margin({
top:20
})
.backgroundColor(Color.Pink)
}
.width('100%')
}
.height('100%')
}
}
預(yù)覽器效果如下:
添加自定義彈窗
首先,在組件Index之外用struct聲明一個新的自定義組件dialog,并用裝飾器@CustomDialog對其進行修飾,使dialog擁有成為自定義彈窗的能力。接著,我們對彈窗顯示的內(nèi)容進行自定義設(shè)置,在build函數(shù)中加入自定義UI聲明。其中,CustomDialogController類型的成員變量controller和字符串類型成員變量message皆必不可少,后者可用于對dialog的外部傳參。
之后,我們在組件Index中new兩個彈窗控制器( CustomDialogController類的實例),并分別將它們賦予兩個私有成員變量(dialogController_Accessible和dialogController_Inaccessible)以供調(diào)用。當(dāng)然,我們是用兩個message不同的dialog組件來構(gòu)造這兩個彈窗控制器的,所以彈窗控制器dialogController_Accessible和彈窗控制器dialogController_Inaccessible可用于打開和關(guān)閉它們所對應(yīng)的dialog。
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
......
//new兩個彈窗控制器
private dialogController_Accessible : CustomDialogController = new CustomDialogController({
builder:dialog({
message:'已獲取權(quán)限'
})
})
private dialogController_Inaccessible : CustomDialogController = new CustomDialogController({
builder:dialog({
message:'暫未獲取權(quán)限'
})
})
build() {
......
}
}
//自定義彈窗
@CustomDialog
struct dialog{
controller:CustomDialogController
@State message:string = ''
build(){
Column() {
Text(this.message)
.fontSize(20)
.height(40)
.fontColor(Color.White)
}
.width('100%')
.backgroundColor(Color.Gray)
}
}
編寫回調(diào)方法
首先,我們從先前步驟中已集成的功能模塊Detector導(dǎo)入異步函數(shù)Check_Access,并自定義異步方法detect。在detect的方法體中,我們通過關(guān)鍵字await等待異步過程,這意味者原本的異步任務(wù)變成了一個等價的延時同步任務(wù),保證了detect中操作語句的順序執(zhí)行。由于關(guān)鍵字await只能在異步方法或異步函數(shù)中出現(xiàn),所以detect必須是異步方法。根據(jù)Check_Access的不同返回結(jié)果(成功或失?。到y(tǒng)會生成不同內(nèi)容的彈窗與用戶交互。detect()編寫完后,我們在用于檢查應(yīng)用是否獲得權(quán)限的Button組件的onclick事件中加入此異步方法。
import Check_Access from 'ets/Service/Detector' //從模塊Detector中導(dǎo)入異步函數(shù)Check_Access
@Entry
@Component
struct Index {
.....
//編寫異步方法detect,調(diào)用之前已寫好的模塊文件Detector
async detect(){
let res = await Check_Access()
this.ifAccessible = res
if(res){
this.dialogController_Accessible.open()
}else{
this.dialogController_Inaccessible.open()
}
}
......
build() {
Row() {
Column() {
......
Button('檢查應(yīng)用是否獲得權(quán)限')
.fontSize(20)
.width('70%')
.margin({
top:40
})
.backgroundColor(Color.Pink)
//設(shè)置onclick回調(diào),并調(diào)用異步函數(shù)detect()
.onClick(()=>{
this.detect()
})
......
}
.width('100%')
}
.height('100%')
}
}
......
接著,我們從先前步驟中已集成的功能模塊Applicant中導(dǎo)入異步函數(shù)Request_Permission_From_Users,并導(dǎo)入common。隨后,我們通過getContext方法獲取上下文對象,并將其轉(zhuǎn)化為UIAbilityContext類型,存入私有成員變量context中。之后,自定義異步方法apply,在方法體中將成員變量context傳入異步函數(shù)Request_Permission_From_Users中。這樣以后,我們便可在用于向用戶動態(tài)申請權(quán)限的Button組件的onclick事件中加入自定義方法apply了。
......
import Request_Permission_From_Users from 'ets/Service/Applicant'
import common from '@ohos.app.ability.common'
@Entry
@Component
struct Index {
......
//獲取上下文對象, 儲存在成員變量context中
private context = getContext(this) as common.UIAbilityContext
//編寫異步方法,調(diào)用之前已寫好的模塊文件Applicant
async apply(){
let res = await Request_Permission_From_Users(this.context)
this.ifAccessible = res
if(res){
this.dialogController_Accessible.open()
}else{
this.dialogController_Inaccessible.open()
}
}
......
build() {
Row() {
Column() {
......
Button('向用戶動態(tài)申請權(quán)限')
.fontSize(20)
.width('70%')
.margin({
top:20
})
.backgroundColor(Color.Pink)
//設(shè)置onclick回調(diào),并調(diào)用異步函數(shù)apply()
.onClick(()=>{
this.apply()
})
}
.width('100%')
}
.height('100%')
}
}
......
至此,本期的Demo完成了。
真機&模擬機調(diào)試
Demo完成之后,我們需要用模擬器或真機來運行以查看效果。
事實上,變量ifAccessible的值應(yīng)該保存在本地的數(shù)據(jù)庫里,這樣每次重新創(chuàng)建此Demo的頁面實例時,才能正確地顯示對應(yīng)圖像,讀者們可以自行修復(fù)這個bug。
當(dāng)然,我們也可以打開日志欄,通過日志信息觀察功能模塊的運行。打開編輯器下方的Hilog,勾選'show only js log',并在標(biāo)簽篩選欄中輸入0FEFE,過濾后的日志信息如下。