鴻蒙動態(tài)權(quán)限申請完整規(guī)范流程和操作詳解
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
好久沒有寫博客了,正好今天HarmonyOS發(fā)布會,看完激動人心的發(fā)布會之后,還是覺得需要寫些東西。本來準(zhǔn)備分享之前自己做的分布式流轉(zhuǎn)的視頻播放器的,但是分布式流轉(zhuǎn)開發(fā)內(nèi)容已經(jīng)有好多博主發(fā)表過了,于是搜了下社區(qū)內(nèi)容,發(fā)現(xiàn)動態(tài)權(quán)限申請這塊的內(nèi)容沒人發(fā)布,并且發(fā)現(xiàn)有幾篇博客的動態(tài)權(quán)限申請的代碼過于簡單存在漏洞。于是想著把這塊內(nèi)容整理整理發(fā)出來。
一、權(quán)限概述
已在config.json文件中聲明的非敏感權(quán)限,非敏感權(quán)限不涉及用戶的敏感數(shù)據(jù)或危險操作,會在應(yīng)用安裝時自動授予,該類權(quán)限的授權(quán)方式為系統(tǒng)授權(quán)(system_grant)。
敏感權(quán)限需要應(yīng)用動態(tài)申請,通過運(yùn)行時發(fā)送彈窗的方式請求用戶授權(quán),該類權(quán)限的授權(quán)方式為用戶授權(quán)(user_grant)。
非敏感權(quán)限代碼編寫比較簡單,這里就不做講解。本文只講解敏感權(quán)限如何編寫代碼,即動態(tài)權(quán)限申請流程。
二、敏感權(quán)限列表
敏感權(quán)限的申請需要按照動態(tài)申請流程向用戶申請授權(quán)。
三、采用一個簡單的相冊案例演示動態(tài)權(quán)限申請開發(fā)流程
相冊需要讀取本機(jī)存儲的權(quán)限,即ohos.permission.READ_USER_STORAGE,它屬于敏感權(quán)限。
1、項目效果圖以及操作場景展示
(1)首次安裝app,用戶需要讀取相冊數(shù)據(jù)時,會彈出對話框提醒用戶授權(quán)。
點(diǎn)擊"允許"之后才能正常加載數(shù)據(jù)。
(2)如果點(diǎn)擊禁止,并且沒有勾選"禁止授權(quán)并且禁止后續(xù)再彈框提示",那么下次打開app時依然會進(jìn)行彈框提示。
(3)如果點(diǎn)擊禁止,并且勾選了"禁止授權(quán)并且禁止后續(xù)再彈框提示",那么后續(xù)也不會再繼續(xù)彈框進(jìn)行授權(quán)了,也就看不到數(shù)據(jù)。如果需要進(jìn)行授權(quán)的話,需要用戶自行去系統(tǒng)設(shè)置中手動更改權(quán)限。此時我們應(yīng)該在頁面上友好地使用toast提醒用戶去系統(tǒng)設(shè)置中手動更改權(quán)限。
請記住我現(xiàn)在描述的3種操作場景,與后續(xù)編寫代碼緊密相關(guān),有些開發(fā)者編寫代碼一行代碼就搞定了動態(tài)授權(quán)操作,那樣的代碼只能滿足我說的第一種使用場景,后面兩種無法滿足,使用起來非常不友好。
2、代碼編寫步驟
(1)配置config.json
首先在config.json的module中添加如下配置:
- "reqPermissions": [
- {
- "name": "ohos.permission.READ_USER_STORAGE",
- "reason": "$string:permreason_storage",
- "usedScene":
- {
- "ability": ["com.xdw.album.MainAbility"],
- "when": "always"
- }
- }
- ]
權(quán)限申請格式采用數(shù)組格式,可支持同時申請多個權(quán)限,權(quán)限個數(shù)最多不能超過1024個。
reqPermissions權(quán)限申請字段說明如下表
(2)編寫權(quán)限彈框觸發(fā)代碼
此步驟需要結(jié)合自己項目實際業(yè)務(wù)邏輯編寫,本相冊項目是在主頁面打開的時候就觸發(fā)了權(quán)限的申請,因此修改MainAbilitySlice代碼,在onStart的時候就去進(jìn)行校驗,具體代碼如下
- if (verifySelfPermission("ohos.permission.READ_USER_STORAGE") != IBundleManager.PERMISSION_GRANTED) {
- // 應(yīng)用未被授予權(quán)限
- if (canRequestPermission("ohos.permission.READ_USER_STORAGE")) {
- // 是否可以申請彈框授權(quán)(首次申請或者用戶未選擇禁止且不再提示)
- requestPermissionsFromUser(
- new String[] { "ohos.permission.READ_USER_STORAGE" } , MY_PERMISSIONS_REQUEST_READ_USER_STORAGE);
- } else {
- // 顯示應(yīng)用需要權(quán)限的理由,提示用戶進(jìn)入設(shè)置授權(quán)
- new ToastDialog(getContext()).setText("請進(jìn)入系統(tǒng)設(shè)置進(jìn)行授權(quán)").show();
- }
- } else {
- // 權(quán)限已被授予
- //加載顯示系統(tǒng)相冊中的照片
- showPhotos();
- }
這斷代碼還使用到了一個自定義的常量MY_PERMISSIONS_REQUEST_READ_USER_STORAGE,需要提前定義它,代碼如下:
- public static final int MY_PERMISSIONS_REQUEST_READ_USER_STORAGE = 0; //自定義的一個權(quán)限請求識別碼,用于處理權(quán)限回調(diào)
第一行首先調(diào)用系統(tǒng)方法verifySelfPermission校驗權(quán)限是否已被授予,如果未授予則調(diào)用系統(tǒng)方法canRequestPermission查詢該權(quán)限是否可以申請彈框授權(quán),因為如果用戶之前如果勾選了禁止授權(quán)并且禁止后續(xù)再彈框提示,那么就不能再進(jìn)行彈框授權(quán)了,此時需要toast提示引導(dǎo)用戶自行去系統(tǒng)設(shè)置中手動更改權(quán)限。如果可以申請彈框授權(quán),則調(diào)用系統(tǒng)方法requestPermissionsFromUser進(jìn)行彈框授權(quán)(應(yīng)用上的彈框就是來自這個方法)。如果之前應(yīng)用已經(jīng)被授權(quán)過,則直接調(diào)用業(yè)務(wù)處理方法,這里自定義的業(yè)務(wù)處理方法是showPhotos,它的代碼請見后面完整MainAbilitySlice代碼。
此時還缺少一個在授權(quán)彈框上點(diǎn)擊允許授權(quán)按鈕之后的回調(diào)業(yè)務(wù)邏輯處理,該回調(diào)業(yè)務(wù)邏輯需要重寫onRequestPermissionsFromUserResult方法,而該方法是Ability類的方法,而不是AbilitySlice類的方法。因此需要在MainAbility中重寫該方法,然后在該重寫方法中調(diào)用MainAbilitySlice對象中的showPhotos方法,這個就涉及到了MainAbility與MainAbilitySlice的通信。
關(guān)于MainAbility與MainAbilitySlice的通信的具體講解請看我另外一篇博文,這里不在做詳解。
(3)編寫requestPermissionsFromUser的回調(diào)
該回調(diào)只能在Ability種進(jìn)行編寫,因此修改MainAbility的代碼,核心代碼如下:
- @Override
- public void onRequestPermissionsFromUserResult(int requestCode, String[] permissions, int[] grantResults) {
- super.onRequestPermissionsFromUserResult(requestCode, permissions, grantResults);
- switch (requestCode) {
- case MY_PERMISSIONS_REQUEST_READ_USER_STORAGE: {
- // 匹配requestPermissions的requestCode
- if (grantResults.length > 0
- && grantResults[0] == IBundleManager.PERMISSION_GRANTED) {
- // 權(quán)限被授予之后做相應(yīng)業(yè)務(wù)邏輯的處理
- mainAbilitySlice.showPhotos();
- } else {
- // 權(quán)限被拒絕
- new ToastDialog(getContext()).setText("權(quán)限被拒絕").show();
- }
- return;
- }
- }
- }
這里對requestCode進(jìn)行了判斷,它就是我們之前自定義的權(quán)限申請碼,用來區(qū)分我們在多個地方進(jìn)行權(quán)限申請的操作,能區(qū)分每次不同請求的回調(diào)。
四、常見操作誤區(qū)
(1)只用一行簡單代碼進(jìn)行動態(tài)權(quán)限申請,而沒有提前校驗權(quán)限和回調(diào)的過程
- requestPermissionsFromUser(
- new String[] { "ohos.permission.READ_USER_STORAGE" } , MY_PERMISSIONS_REQUEST_READ_USER_STORAGE);
這種情況就會出現(xiàn)萬一有一次禁止了權(quán)限,后面就不會顯示相冊數(shù)據(jù)并且沒人任何提示,影響用戶體驗。
(2)canRequestPermission代碼邏輯沒有編寫
該邏輯代碼不編寫,就會出現(xiàn)用戶點(diǎn)擊了"禁止授權(quán)并且禁止后續(xù)再彈框提示",然后進(jìn)入頁面就不會顯示相冊數(shù)據(jù)并且沒人任何提示,影響用戶體驗。
因此,為了加強(qiáng)用戶體驗,請不要省略上述動態(tài)權(quán)限申請的代碼編寫流程。
五、完整代碼
MainAbilitySlice的代碼如下:
- package com.xdw.album.slice;
- import com.xdw.album.MainAbility;
- import com.xdw.album.ResourceTable;
- import ohos.aafwk.ability.AbilitySlice;
- import ohos.aafwk.ability.DataAbilityHelper;
- import ohos.aafwk.ability.DataAbilityRemoteException;
- import ohos.aafwk.content.Intent;
- import ohos.agp.components.Component;
- import ohos.agp.components.Image;
- import ohos.agp.components.TableLayout;
- import ohos.agp.components.Text;
- import ohos.agp.window.dialog.ToastDialog;
- import ohos.bundle.IBundleManager;
- import ohos.data.resultset.ResultSet;
- import ohos.hiviewdfx.HiLog;
- import ohos.hiviewdfx.HiLogLabel;
- import ohos.media.image.ImageSource;
- import ohos.media.image.PixelMap;
- import ohos.media.image.common.Size;
- import ohos.media.photokit.metadata.AVStorage;
- import ohos.utils.net.Uri;
- import java.io.FileDescriptor;
- import java.io.FileNotFoundException;
- import java.util.ArrayList;
- public class MainAbilitySlice extends AbilitySlice {
- private static final String TAG = "MainAbilitySlice";
- private static final HiLogLabel LABEL = new HiLogLabel(HiLog.DEBUG, 0, "TAG");
- public static final int MY_PERMISSIONS_REQUEST_READ_USER_STORAGE = 0; //自定義的一個權(quán)限請求識別碼,用于處理權(quán)限回調(diào)
- private TableLayout tlAlbum; //定義表格布局,用來加載圖片控件
- private Text textLoading, textNum; //定義正在加載文本,照片數(shù)量顯示文本
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- super.setUIContent(ResourceTable.Layout_ability_main);
- MainAbility mainAbility = (MainAbility) getAbility();
- mainAbility.setMainAbilitySlice(this);
- initView();
- if (verifySelfPermission("ohos.permission.READ_USER_STORAGE") != IBundleManager.PERMISSION_GRANTED) {
- // 應(yīng)用未被授予權(quán)限
- if (canRequestPermission("ohos.permission.READ_USER_STORAGE")) {
- // 是否可以申請彈框授權(quán)(首次申請或者用戶未選擇禁止且不再提示)
- requestPermissionsFromUser(
- new String[] { "ohos.permission.READ_USER_STORAGE" } , MY_PERMISSIONS_REQUEST_READ_USER_STORAGE);
- } else {
- // 顯示應(yīng)用需要權(quán)限的理由,提示用戶進(jìn)入設(shè)置授權(quán)
- new ToastDialog(getContext()).setText("請進(jìn)入系統(tǒng)設(shè)置進(jìn)行授權(quán)").show();
- }
- } else {
- // 權(quán)限已被授予
- //加載顯示系統(tǒng)相冊中的照片
- showPhotos();
- }
- }
- @Override
- public void onActive() {
- super.onActive();
- }
- @Override
- public void onForeground(Intent intent) {
- super.onForeground(intent);
- }
- private void initView() {
- //初始化相關(guān)UI組件
- tlAlbum = (TableLayout) findComponentById(ResourceTable.Id_tl_album);
- tlAlbum.setColumnCount(3); //表格設(shè)置成3列
- textLoading = (Text) findComponentById(ResourceTable.Id_text_loading);
- textNum = (Text) findComponentById(ResourceTable.Id_text_num);
- }
- //定義加載顯示圖片的方法
- public void showPhotos() {
- //先移除之前的表格布局中的所有組件
- tlAlbum.removeAllComponents();
- //定義一個數(shù)組,用來存放圖片的id,它的size就是照片數(shù)量
- ArrayList<Integer> img_ids = new ArrayList<Integer>();
- //初始化DataAbilityHelper,用來獲取系統(tǒng)共享數(shù)據(jù)
- DataAbilityHelper helper = DataAbilityHelper.creator(getContext());
- try {
- //讀取系統(tǒng)相冊的數(shù)據(jù)
- ResultSet result = helper.query(AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI, null, null);
- //根據(jù)獲取的數(shù)據(jù)覺得“正在加載”提示是否顯示
- if (result == null) {
- textLoading.setVisibility(Component.VISIBLE);
- } else {
- textLoading.setVisibility(Component.HIDE);
- }
- //遍歷獲取的數(shù)據(jù),來動態(tài)加載表格布局中的圖片組件
- while (result != null && result.goToNextRow()) {
- //從獲取的數(shù)據(jù)中讀取圖片的id
- int mediaId = result.getInt(result.getColumnIndexForName(AVStorage.Images.Media.ID));
- //生成uri,后面會根據(jù)uri獲取文件
- Uri uri = Uri.appendEncodedPathToUri(AVStorage.Images.Media.EXTERNAL_DATA_ABILITY_URI, "" + mediaId);
- //獲取文件信息
- FileDescriptor filedesc = helper.openFile(uri, "r");
- //定義一個圖片編碼參數(shù)選項用于設(shè)置相關(guān)編碼參數(shù)
- ImageSource.DecodingOptions decodingOpts = new ImageSource.DecodingOptions();
- decodingOpts.desiredSize = new Size(300, 300);
- //根據(jù)文件信息生成pixelMap對象,該對象是設(shè)置Image組件的關(guān)鍵api
- ImageSource imageSource = ImageSource.create(filedesc, null);
- PixelMap pixelMap = imageSource.createThumbnailPixelmap(decodingOpts, true);
- //構(gòu)造一個圖片組件并且設(shè)置相關(guān)屬性
- Image img = new Image(MainAbilitySlice.this);
- img.setId(mediaId);
- img.setHeight(300);
- img.setWidth(300);
- img.setMarginTop(20);
- img.setMarginLeft(20);
- img.setPixelMap(pixelMap);
- img.setScaleMode(Image.ScaleMode.ZOOM_CENTER);
- //在表格布局中加載圖片組件
- tlAlbum.addComponent(img);
- HiLog.info(LABEL, "uri=" + uri);
- img_ids.add(mediaId);
- }
- } catch (DataAbilityRemoteException | FileNotFoundException e) {
- e.printStackTrace();
- }
- //完成照片數(shù)量的刷新,如果沒有照片,則在UI中顯示“沒有照片”的文本
- if (img_ids.size() > 0) {
- textLoading.setVisibility(Component.HIDE);
- textNum.setVisibility(Component.VISIBLE);
- textNum.setText("照片數(shù)量:" + img_ids.size());
- } else {
- textLoading.setVisibility(Component.VISIBLE);
- textLoading.setText("沒有照片");
- textNum.setVisibility(Component.HIDE);
- }
- }
- }
復(fù)制MainAbility的代碼如下:
- package com.xdw.album;
- import com.xdw.album.slice.MainAbilitySlice;
- import ohos.aafwk.ability.Ability;
- import ohos.aafwk.content.Intent;
- import ohos.agp.window.dialog.ToastDialog;
- import ohos.bundle.IBundleManager;
- import static com.xdw.album.slice.MainAbilitySlice.MY_PERMISSIONS_REQUEST_READ_USER_STORAGE;
- public class MainAbility extends Ability {
- private MainAbilitySlice mainAbilitySlice;
- @Override
- public void onStart(Intent intent) {
- super.onStart(intent);
- super.setMainRoute(MainAbilitySlice.class.getName());
- }
- @Override
- public void onRequestPermissionsFromUserResult(int requestCode, String[] permissions, int[] grantResults) {
- super.onRequestPermissionsFromUserResult(requestCode, permissions, grantResults);
- switch (requestCode) {
- case MY_PERMISSIONS_REQUEST_READ_USER_STORAGE: {
- // 匹配requestPermissions的requestCode
- if (grantResults.length > 0
- && grantResults[0] == IBundleManager.PERMISSION_GRANTED) {
- // 權(quán)限被授予之后做相應(yīng)業(yè)務(wù)邏輯的處理
- mainAbilitySlice.showPhotos();
- } else {
- // 權(quán)限被拒絕
- new ToastDialog(getContext()).setText("權(quán)限被拒絕").show();
- }
- return;
- }
- }
- }
- public MainAbilitySlice getMainAbilitySlice() {
- return mainAbilitySlice;
- }
- public void setMainAbilitySlice(MainAbilitySlice mainAbilitySlice) {
- this.mainAbilitySlice = mainAbilitySlice;
- }
- }
復(fù)制布局文件ability_main.xml代碼如下:
- <?xml version="1.0" encoding="utf-8"?>
- <DirectionalLayout
- xmlns:ohos="http://schemas.huawei.com/res/ohos"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:orientation="vertical">
- <Text
- ohos:id="$+id:text_loading"
- ohos:height="match_parent"
- ohos:width="match_parent"
- ohos:text="正在打開..."
- ohos:text_alignment="center"
- ohos:text_size="45fp"></Text>
- <ScrollView
- ohos:height="600vp"
- ohos:width="match_parent"
- ohos:left_padding="25vp"
- >
- <TableLayout
- ohos:id="$+id:tl_album"
- ohos:height="match_content"
- ohos:width="match_parent"
- >
- </TableLayout>
- </ScrollView>
- <Text
- ohos:id="$+id:text_num"
- ohos:height="match_content"
- ohos:width="match_content"
- ohos:text_alignment="center"
- ohos:text_size="20fp"></Text>
- </DirectionalLayout>
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)