React-Native开发鸿蒙NEXT-权限处理

338 阅读6分钟

React-Native开发鸿蒙NEXT-权限处理

原创 悬空八只脚 悬空八只脚 2024年11月01日 20:34 江苏

开发app首先绕不开的就是权限隐私处理。

RN中对app权限的获取判断涉及到原生与js的交互,比起原生app的直接请求需要考虑交互过程中的异常处理。利用RN开发HarmonyOS Next应用同样,下面结合项目看看如何在原生(ArkTS)与RN(js)间进行权限请求交互。

尽管现在越来越强调仅在需要时进行动态请求,一般还是会把权限做成应用启动批量请求默认权限和具体使用时动态申请。为此,在RN代码中,构建了SpecSystemTurboModule类,分别提供初始默认批量请求和动态请求方法。

SpecSystemTurboModule.tsx


import {TurboModule, TurboModuleRegistry} from 'react-native';

// 使用自定义的TurboModules
export interface SpecSystemTurboModule extends TurboModule {
  ......
  // 校验初始化权限
  checkedPermission(): void;
  // 查询指定权限
  queryPermission(permissionEnum: string): void;
  ......

}

export default TurboModuleRegistry.getEnforcing<SpecSystemTurboModule>(
  'SystemTurboModule',
);

鉴于Android端对权限请求的要求,将启动时默认的批量请求触发放置在用户同意隐私协议之后。由于目前尚未将新工程适配andorid与ios,暂时判断下操作系统类型只针对鸿蒙系统。

xx页面.tsx

  /**
   * 刷新隐私协议同意状态
   */
  const doRefreshPrivacyAgreeState = async () => {
    // 判断用户是否同意过隐私协议
    let _privacyAgreeState = await getData('privacyAgreeState');
    if (_privacyAgreeState == null || _privacyAgreeState == 'no') {
      setShowPrivacyPolicy(true);
    } else {
      ......
      if (Platform.OS === 'harmony') {
        // TODO:权限校验
        SystemTurboModule.checkedPermission();
      }
      ......
    }
  };

初始权限请求由于仅会在应用启动时请求一次,对于权限请求的接收,在根页面(app.tsx)中定义消息监听。

App.tsx

function App(): JSX.Element {
    ......
    // 权限校验/申请结果的监听
    const permissionResultEventEmitterRef = useRef(null);

    useEffect(() => {
        ......
        permissionResultEventEmitterRef.current = DeviceEventEmitter.addListener(
          '......name111', 
          (data: string) => {
            console.log(App.TAG + ' permissionResult = ' + data);
            try {
              // todo:这是针对鸿蒙权限的处理,不同的系统申请的权限名称数量不一样,需要做针对性处理
              // todo:现在实现了RN测拿到原生对权限结果的返回,可以在RN里针对原生的权限情况做针对处理了
              let result = JSON.parse(data);
              let permissions = JSON.parse(result.data.value);
              console.log(typeof permissions);
              ......对数据的业务处理
            } catch (e) {
              console.log(TAG + 'permissionResult ERROR = ' + e);
            }
          },
        );
        return () => {
          ......
          permissionResultEventEmitterRef.current &&
            permissionResultEventEmitterRef.current.remove();
          ......
        };
    }, []);
    ......
}

对于动态请求部分,则在需要的时候先请求权限,根据返回结果判断是否执行和请求相关的动作,这里以项目中利用react-native-image-picker更行用户个人头像为例。

在点击用户头像时,先请求相机权限

xxx页面.tsx

const check = async select => {
    ......
    if (select == 0) {
      //更新头像
      checkPermission();
    }
    ......
}

const checkPermission = () => {
    if (Platform.OS === 'harmony') {
      // TODO:先权限校验,根据结果打开相机
      SystemTurboModule.queryPermission('CAMERA');
    }
}

同样,这页面中定义用于接收动态权限请求的消息监听,根据返回结果判断是否成功获取了权限,进而决定是提示用户没有权限还是去打开相机。注意这里定义的消息名称和初始化批量获取是两个分开的。动态请求是更加普遍的方式,初始批量请求存在审核被拒风险,尽可能按需请求权限。

const queryPermissionResultEventEmitterRef = useRef(null);
......
queryPermissionResultEventEmitterRef.current =
DeviceEventEmitter.addListener(
  '......name222', 
  (data: string) => {
    console.log(TAG + ' queryPermissionResultListener = ' + data);
    if (global.TAG === TAG) {
      try {
        // todo:这是针对鸿蒙权限的处理,不同的系统申请的权限名称数量不一样,需要做针对性处理
        let result = JSON.parse(data);
        let permissions = JSON.parse(result.data.value);
        permissions.map(item => {
          if (
            item.permission &&
            item.permission === 'CAMERA' &&
            item.result == 0
          ) {
            // 打开相机
            selectPic();
          } else {
            xnToast('请打开相机权限!');
          }
        });
      } catch (e) {
        console.log(TAG + 'permissionResult ERROR = ' + e);
      }
    }
  },
);
......

接下来看下原生部分。用于交互的SystemTurboModule中,通过emitter抛出原生消息

SystemTurboModule.ets

import emitter from '@ohos.events.emitter';
import ConstUtil from '../utils/ConstUtil';
......
export interface TurboModuleEventData {
param: string
}

export class SystemTurboModule extends TurboModule {
......
checkedPermission() {
  console.log("checkedPermission.log from SystemTurboModule: ")
  emitter.emit(ConstUtil.event_id_check_permissions,{});
}

queryPermission(permissionEnum: string) {
  console.log("queryPermission.log from SystemTurboModule: ")
  let data: TurboModuleEventData = { param: permissionEnum }
  emitter.emit(ConstUtil.event_id_query_permission,{data});
}
......
}

原生部分对权限的处理集中在EntryAbility.ets。

  1. onWindowStageCreate中定义消息接收。

  2. 通过@ohos.abilityAccessCtrl类完成权限的请求。默认权限其实是在ArkTS中直接定义的,动态申请时,由于ios/android/harmony对于权限名称定义的不同,同样需要根据参数做一对一的匹配(是否存在类似ClassFromName的方式?)。

3.获取的结果同样通过emitter抛给index.ets,之所以这样是因为在index中有构造RN容器的创建方法,可以获取到当前bundle的RN对象实例。


import { RNAbility, RNOHCoreContext } from '@rnoh/react-native-openharmony';
import window from '@ohos.window';
import { emitter } from '@kit.BasicServicesKit';
import abilityAccessCtrl, { Context, PermissionRequestResult, Permissions } from '@ohos.abilityAccessCtrl';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { JSON } from '@kit.ArkTS';
import common from '@ohos.app.ability.common';
import { PermissionResult } from '../model/DataModel'
import ConstUtil from '../utils/ConstUtil'
import { App } from '@kit.ArkUI';

export let preferences: dataPreferences.Preferences | null = null
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
let appAccessTokenId:number = -1;
export default class EntryAbility extends RNAbility {

  onWindowStageCreate(windowStage: window.WindowStage): void {
    ......
    // 校验初始化权限
    emitter.on(ConstUtil.event_id_check_permissions, () => {
      console.info('event_id_check_permissions');
      this.checkPermissions();
    });
    // 使用前申请权限权限
    emitter.on(ConstUtil.event_id_query_permission, (data) => {
      try{
        console.info('event_id_check_permission and data = ' + JSON.stringify(data));
        if(data.data){
          this.queryPermission(data.data.param);
        }
      } catch (e) {
      }
    });
    ......
  }

  public checkPermissions(){
    if (appAccessTokenId != -1) {
      atManager.requestPermissionsFromUser(getContext(this),
        ['ohos.permission.ACCESS_BLUETOOTH',
          'ohos.permission.APPROXIMATELY_LOCATION',
          'ohos.permission.LOCATION',
        ]
      ).then((data: PermissionRequestResult) => {
        let authResults:Array<PermissionResult> = new Array;
        authResults.push(new PermissionResult('ACCESS_BLUETOOTH',data.authResults[0]))
        // authResults.push(new PermissionResult('CAMERA',data.authResults[1]));// 相机权限在使用之前单独申请(鸿蒙要求)
        authResults.push(new PermissionResult('APPROXIMATELY_LOCATION',data.authResults[2]));
        authResults.push(new PermissionResult('LOCATION',data.authResults[3]));
        let eventData: emitter.EventData = {
          data: {
            "key": "permissionResult",
            "value":JSON.stringify(authResults),
          }
        };
        // 发送给index.ets,在index里通过rnInstance发送给js
        emitter.emit(ConstUtil.event_id_check_permission_result,eventData);
      }).catch((err: BusinessError) => {
        console.error('data:' + JSON.stringify(err));
      });
    }else{
      hilog.error(0x0000, 'testTag', 'checkPermissions failed: %{public}s', "xxxxxx");
    }
  }

  /**
   * 临时申请权限
   */
  public queryPermission(permissionString:string){
    if (appAccessTokenId != -1) {
      let permissions:Array<Permissions> = new Array;
      // 转换
      if(permissionString === 'CAMERA'){
        permissions.push('ohos.permission.CAMERA');
      }
      if(permissions.length > 0){
        atManager.requestPermissionsFromUser(getContext(this),
          permissions
        ).then((data: PermissionRequestResult) => {
          let authResults:Array<PermissionResult> = new Array;
          authResults.push(new PermissionResult(permissionString,data.authResults[0]));
          let eventData: emitter.EventData = {
            data: {
              "key": "queryPermissionResult",
              "value":JSON.stringify(authResults),
            }
          };
          // 发送给index.ets,在index里通过rnInstance发送给js
          emitter.emit(ConstUtil.event_id_query_permission_result,eventData);
        }).catch((err: BusinessError) => {
          console.error('data:' + JSON.stringify(err));
        });
      }
    }else{
        hilog.error(0x0000, 'testTag', 'checkPermissions failed: %{public}s', "xxxxxx");
    }
  }
  ......
}

在index.ets中,主要实现向RN发送权限获取结果。

  1. 在aboutToAppear生命周期中注册消息接收
  2. 利用当前bundle对应的RN实例向RN发送消息
import {
    AnyJSBundleProvider,
    ComponentBuilderContext,
    FileJSBundleProvider,
    MetroJSBundleProvider,
    ResourceJSBundleProvider,
    RNApp,
    RNSurface,
    RNOHErrorDialog,
    LogBoxDialog,
    LogBoxTurboModule,
    RNOHLogger,
    TraceJSBundleProviderDecorator,
    RNOHCoreContext,
    RNInstance,
    RNComponentContext,
    RNOHContext,
    buildRNComponentForTag,
  } from '@rnoh/react-native-openharmony'
  import { SampleView, PropsDisplayer, GeneratedSampleView } from "@rnoh/sample-package"
  import {
    RNLinearGradient,
    LINEAR_GRADIENT_TYPE,
    LinearGradientDescriptor
  } from "@react-native-oh-tpl/react-native-linear-gradient"
  import ConstUtil from '../utils/ConstUtil'
  import { emitter } from '@kit.BasicServicesKit';
  import window from '@ohos.window';
  import PrecreateRN from './PrecreateRN';
  import { arkTsComponentNames } from "../rn/LoadBundle"
  import { createRNPackages, LoadManager, buildCustomComponent, ENABLE_CAPI_ARCHITECTURE } from '../rn';
  import { rnPackageEventData, TurboModuleEventData } from '../TurboModule/SystemTurboModule';
  import json from '@ohos.util.json';
@Entry
@Component
struct Index {
private _rnInstance?: RNInstance | null;
aboutToAppear() {
    ......
    // 注册原生发送给rn的emitter
    this.registerNativeToJsEmitter();
    ......
  }

  registerNativeToJsEmitter() {
    ......
    // 权限校验结果
    emitter.on(ConstUtil.event_id_check_permission_result, (data) => {
      console.info('event_id_check_permissions and data = ' + JSON.stringify(data));
      this.sendDeviceEvent(ConstUtil.event_native_to_js_permission_result, JSON.stringify(data));
    });
    this._rnInstance = LoadManager.metroInstance;
    ......
  }

  sendDeviceEvent(eventName: string, payload: string) {
    if (this._rnInstance != null) {
      console.error('向js发送消息,  eventName = ' + eventName || '' + " and payload = " + payload || '');
      this._rnInstance.emitDeviceEvent(eventName, payload);
    } else {
      console.error('_rnInstance 为 null!!!无法向js发送消息');
    }
  }
  ......
}

以上便是RN开发HarmonyOS NEXT应用中权限处理的一种处理方式,同时也是RN与原生交互的通用方法,整体基于RN的TurboModules。对于ios与android,只需针对各自系统开发原生代码处理权限,在RN端需要针对不同操作系统返回的权限处理结果做分类处理,当然也可以在原生端做下统一的封装。


微信扫一扫
关注该公众号悬空八只脚