鸿蒙权限申请、网络请求

856 阅读1分钟

鸿蒙权限申请、网络请求

前言

前两篇鸿蒙文章说实话还是挺水的,没什么干货,主要我也是边学边做,后面会慢慢提高的,代码也会慢慢优化。这篇文章就来讲讲鸿蒙的网络请求和动态权限申请。

权限申请

和安卓一样,鸿蒙的敏感权限也要动态申请,具体的看文档吧,这里我这就以文件权限来写个例子。

声明权限

和安卓在AndroidManifest.xml里面声明权限类似,鸿蒙需要在module.json5文件中声明权限:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET"
      },
      {
        "name": "ohos.permission.READ_MEDIA",
        "reason": "$string:app_name",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "always"
        }
      }
    ]
  }
}

这里声明了网络权限和读取文件权限,对安卓开发来说应该很熟悉了,就不多说了。需要注意的是,和安卓中申请权限自己做权限说明不一样,鸿蒙这里要在喷子的地方写清申请理由,还要限定权限使用范围,这点确实比安卓好。

动态申请

声明好权限了,就可以在用到的地方动态申请了,先检查权限授予情况:

  /**
   * 校验当前权限是否已经授权
   *
   * @param permission
   * @returns
   */
  async checkAccessToken(permission: Permissions) {
    let atManager = abilityAccessCtrl.createAtManager();
    let grantStatus: abilityAccessCtrl.GrantStatus = abilityAccessCtrl.GrantStatus.PERMISSION_DENIED;

    // 获取应用程序的accessTokenID
    let tokenID: number = 0;
    try {
      let bundleInfo: bundleManager.BundleInfo =
        await bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
      let appInfo: bundleManager.ApplicationInfo = bundleInfo.appInfo;
      tokenID = appInfo.accessTokenId;
    } catch (err) {
      LogUtil.e(`getBundleInfoForSelf failed, error: ${err}`);
    }

    // 校验应用是否被授予权限
    try {
      grantStatus = await atManager.checkAccessToken(tokenID, permission);
    } catch (err) {
      LogUtil.e(`checkAccessToken failed, error: ${err}`);
    }
    return grantStatus;
  }

基本就是官方demo了,就是挺迷,检查个权限不能封装下吗?直接context检查不好吗?

下面看下申请的代码:

  /**
   * 请求单个权限
   *
   * @param permission
   * @param context
   */
  requestPermission(
    permission: Permissions,
    context: Context,
    onSuccess: (message: string)=> void,
    onFailed: (error: string) => void
  ): void {
    this.requestPermissions([ permission ], context, onSuccess, onFailed);
  }
  
  /**
   * 请求多个权限
   *
   * @param permissions
   * @param context
   */
  requestPermissions(
    permissions: Array<Permissions>,
    context: Context,
    onSuccess: (message: string)=> void,
    onFailed: (error: string) => void
  ): void {
    let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
    // requestPermissionsFromUser会判断权限的授权状态来决定是否唤起弹窗
    atManager.requestPermissionsFromUser(context, permissions).then((data) => {
      let grantStatus: Array<number> = data.authResults;
      let length: number = grantStatus.length;
      for (let i = 0; i < length; i++) {
        if (grantStatus[i] === 0) {
          // 用户授权,可以继续访问目标操作
          onSuccess("success: " + permissions[i]);
        } else {
          // 用户拒绝授权,提示用户必须授权才能访问当前页面的功能,并引导用户到系统设置中打开相应的权限
          onFailed("denied: " + permissions[i]);
        }
      }
    }).catch((err: BusinessError) => {
      onFailed("error: " + err.message)
      LogUtil.d(`Failed to request permissions from user. Code is ${err.code}, message is ${err.message}`);
    })
  }

这里对官方demo改动了些,理解还是挺好理解的,下面再看下整合在一起使用:

  /**
   * 传入权限数组Array<Permissions>
   *
   * @param permission
   * @returns
   */
  async checkAndRequestPermissions(
    permissions: Array<Permissions>,
    context: Context,
    onSuccess: (message: string)=> void,
    onFailed: (error: string) => void
  ): Promise<string> {
    return new Promise(async (_resolve, _reject) => {
      for (let i = 0; i < permissions.length; i++) {
        let grantStatus: abilityAccessCtrl.GrantStatus = await this.checkAccessToken(permissions[i]);
        if (grantStatus === abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
          // 已经授权,可以继续访问目标操作
          onSuccess("success: " + permissions[i]);
        } else {
          // 申请权限
          this.requestPermissions([ permissions[i] ], context, onSuccess, onFailed);
        }
      }
    });
  }

  /**
   * 传入权限数组Array<Permissions>
   *
   * @param permission
   * @returns
   */
  async checkAndRequestPermissionsForAll(
    permissions: Array<Permissions>,
    context: Context
  ): Promise<Boolean> {
    return new Promise(async (resolve, reject) => {
      try {
        let isAllGranted = true;
        for (let i = 0; i < permissions.length; i++) {
          let grantStatus: abilityAccessCtrl.GrantStatus = await this.checkAccessToken(permissions[i]);
          if (grantStatus !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) {
            isAllGranted = false;
            break;
          }
        }

        if (isAllGranted) {
          resolve(true);
        }else {
          resolve(false);
          // 申请权限
          this.requestPermissions( permissions , context, ()=>{}, ()=>{});
        }
      } catch (e) {
        LogUtil.e(e);
        reject(e);
      }
    });
  }

这里写了两种使用方式,一种是一个一个接受申请结果,一个是得到同意全部的回调,我也不知道这样写好不好,感觉就是有点怪怪的,凑合用吧。

网络请求

首先说明啊,这里我没用鸿蒙三方库的axios,有兴趣的朋友可以选择使用它,我是用的鸿蒙API做的,学习下,主要鸿蒙化的APP功能还不多,后面再看了。

网络请求里面用的比较多的就get和post请求,这里简单封装了下,先来看get请求:

  /**
   * 发送网络请求
   *
   * @param url 请求链接
   * @param header 自定义请求头
   * @param onSuccess 成功回调
   * @param onFailed 失败回调
   */
  request(
    url: string,
    header: Record<string,string | number>,
    onSuccess: (message: string)=> void,
    onFailed: (error: string) => void
  ) {
    // http请求对象
    let httpRequest = http.createHttp();
    let opt: http.HttpRequestOptions = {
      method: http.RequestMethod.GET,
      header: header
    }

    // 发送上传请求
    httpRequest.request(url, opt)
      .then((resp) => {
        if (resp.responseCode == 200) {
          onSuccess(JSON.stringify(resp.result))
        }else {
          onFailed("responseCode:" + resp.responseCode)
        }
      })
      .catch((e: BusinessError) => {
        onFailed("error:" + (e as BusinessError).message)
      })
  }

下面看下用法:

let url = hostUrl + “xxx”;
let header: Record<string, string | number> = {
  'Authorization': this.token,
}
httpUtil.request(url, header, (res) => {
  // 拿到body数据
}, (err) => {
  // 处理失败
});

这里的header是一个Record类型,真的卡了我好久不知道怎么通过方法传递过来,而且Record使用set好像不生效,需要用方括号来设置值。

下面再看下post请求,比get复杂了一丢丢:

  /**
   * 发送form请求
   *
   * @param url 请求链接
   * @param header 自定义请求头
   * @param param form数据
   * @param onSuccess 成功回调
   * @param onFailed 失败回调
   */
  postForm(
    url: string,
    header: Record<string,string | number>,
    param: Map<string, string>,
    onSuccess: (message: string)=> void,
    onFailed: (error: string) => void
  ) {
    // 增加form的header
    header['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';

    // http请求对象
    let httpRequest = http.createHttp();
    let opt: http.HttpRequestOptions = {
      method: http.RequestMethod.POST,
      header: header,
      extraData: this.buildQueryString(param)
    }

    // 发送上传请求
    httpRequest.request(url, opt)
      .then((resp) => {
        onSuccess(JSON.stringify(resp.result))
      })
      .catch((e: BusinessError) => {
        onFailed("uploadFile error:" + (e as BusinessError).message)
      })
  }

  private buildQueryString(params: Map<string, string>) {
    let result = ""
    params.forEach((value, key)=> {
      result += `${key}=${value}&`
    })
    result = result.slice(0, -1)
    return result
  }

post请求要发送form数据,需要设置content-type,然后在body里面把form数据通过"&"符号拼接起来,用法如下:

let url = hostUrl + “xxx”;
let header: Record<string, string | number> = {
  'Authorization': this.token,
}

// 参数
let param: Map<string, string> = new Map()
param.set('xxx', xxx)

// 异步请求
httpUtil.postForm(url, header, param, (result) => {
  resolve(result);
}, (error) => {
  reject(error);
})

其实嘛,HTTP协议就是这样,用什么框架都万变不离其中,下面是一个form请求的例子:

 POST / HTTP/1.1
 Host: www.example.com
 User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.6)
 Gecko/20050225 Firefox/1.0.1
 Content-Type: application/x-www-form-urlencoded
 Content-Length: 40
 Connection: Keep-Alive

 sex=man&name=Professional 

第一行说明了请求方法、路径、协议版本,第二行开始是header,第三个部分是用空行隔开的body,更多的应用我们上传文件再说,这里就算抛砖引玉了。

小结

这篇文章简单介绍了下鸿蒙权限申请和网络请求两个部分的内容,还是以官方API为主,稍微修改和封装了下,希望对读者有所帮助。