Android之编写申请权限库PermissionX

news2024/12/26 10:37:52

比如要实现拨打电话的功能,一般我们要编写如下Android运行时权限API

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        if(ContextCompat.checkSelfPermission(this,Manifest.permission.CALL_PHONE)!=
                PackageManager.PERMISSION_GRANTED){
            ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE),1)
        }else{
            call()
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        when(requestCode){
            //如果requestCode是1
            1->{
                if(grantResults.isNotEmpty()&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
                    call()
                }else{
                    Toast.makeText(this,"You denied the permission",Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
    //执行相关打电话操作
    private fun call() {
        //
    }

}

可以看到,这种系统内置的运行时权限API的用法还是非常烦琐的,需要先判断用户是否授权我们拨打电话的权限,如果没有的话需要进行权限申请,然后还要在onRequestPermissionsResult()回调中处理权限申请的结果,最后才能去执行拨打电话的操作。
我们可以通过这个过程编写一个开源库PermissionX。
之前我们写的所有代码都是在app目录下进行的。这其实是一个专门用于开发应用程序的模块。而我们现在要开发的是一个库,因此我们需要新建一个模块。
实际上,一个Android项目中可以包含任意多个模块,并且模块与模块之间可以相互引用。比方说,我们在模块A中编写了一个功能,那么只需要在模块B中引入模块A,模块B就可以无缝地使用模块A中提供的所有功能。
在PermissionX项目中新建一个模块,并在这个模块中实现具体的功能。对着最顶层的PermissionX目录右击→New→Module,选择Android Library会弹出如下
在这里插入图片描述
点击“Finish”按钮完成创建,现在PermissionX工程目录下应该就有app和library两个模块了。
在这里插入图片描述
观察一下library模块中的build.gradle文件,其简化后的代码如下所示:

plugins {
    id 'com.android.library'
    id 'kotlin-android'
}

android {
    compileSdkVersion 32
    buildToolsVersion "30.0.3"

    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
    }
    ...
    }
    ...

会发现它和app模块中的build.gradle文件有两个重要的区别:第一,这里头部引入的插件是com.android.library,表示这是一个库模块,而app/build.gradle文件头部引入的插件是com.android.application,表示这是一个应用程序模块;第二,这里的defaultConfig闭包中是不可以配置applicationId属性的,而app/build.gradle中则必须配置这个属性,用于作为应用程序的唯一标识。

想要对运行时权限的API进行封装,这个操作是有特定的上下文依赖的,一般需要在Activity中接收onRequestPermissionsResult()方法的回调才行,所以不能简单地将整个操作封装到一个独立的类中。受此限制以往都是将运行权限的操作封装到BaseActivity中,或者提供一个透明的Activity来处理运行时权限等。
其实Google在Fragment中也提供了一份相同的API,使得我们在Fragment中也能申请运行时权限。不同的是,Fragment并不像Activity那样必须有界面,我们完全可以向Activity中添加一个隐藏的Fragment,然后在这个Fragment中对运行时权限的API进行封装。这是一种轻量级的做法,不用担心隐藏Fragment对Activity性能的影响。

package com.permission.yiran

import android.content.pm.PackageManager
import androidx.fragment.app.Fragment

class InvisibleFragment: Fragment() {
      //callback为函数类型变量,可为空
    private var callback: PermissionCallback? =null
    fun requestNow(cb:PermissionCallback,vararg permissions:String){
        callback=cb
        requestPermissions(permissions,1)
    }
    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if(requestCode==1){
            //使用deniedList列表来记录所有被用户拒绝的权限
            val  deniedList=ArrayList<String>()
            for((index,result) in grantResults.withIndex()){
                //如果发现某个权限未被用户授权
                if(result!=PackageManager.PERMISSION_GRANTED){
                    deniedList.add(permissions[index])
                }
            }
            //标识是否所有申请权限均已被授权,如果为空说明都已授权
            val allGranted=deniedList.isEmpty()
            //let函数用于判空,it代表callback对象
            callback?.let {
                it(allGranted,deniedList)
            }
        }
    }
}

首先我们定义了一个callback变量作为运行时权限申请结果的回调通知方式,并将它声明成了一种函数类型变量,该函数类型接收Boolean和List< String >这两种类型的参数,并且没有返回值。

然后定义一个requestNow()方法,该方法接收一个与callback变量类型相同的函数类型参数,同时还使用vararg关键字接收了一个可变长度的permissions参数列表。将传递进来的函数类型参数赋值给callback变量,然后调用Fragment中提供的requestPermissions()方法去立即申请运行时权限,并将permissions参数列表传递进去,这样就可以实现由外部调用方自主指定要申请哪些权限的功能了。

接下来还需要重写onRequestPermissionsResult()方法,并在这里处理运行时权限的申请结果。可以看到,我们使用了一个deniedList列表来记录所有被用户拒绝的权限,然后遍历grantResults数组,如果发现某个权限未被用户授权,就将它添加到deniedList中。遍历结束后使用了一个allGranted变量来标识是否所有申请的权限均已被授权,判断的依据就是deniedList列表是否为空。最后使用callback变量对运行时权限的申请结果进行回调。

typealias PermissionCallback=(Boolean, List<String>) -> Unit
class InvisibleFragment: Fragment() {
    //callback可为空
    private var callback: PermissionCallback? =null
    fun requestNow(cb:PermissionCallback,vararg permissions:String){
        callback=cb
        requestPermissions(permissions,1)
    }
    ...
    }

typealias 关键字可以用于给任意类型指定一个别名,比如我们将(Boolean, List< String >) -> Unit的别名指定成了PermissionCallback,这样就可以使用PermissionCallback来替代之前所有使用(Boolean, List< String >) -> Unit的地方。
接下来就是对外接口部分,新建一个PermissionX单例类

package com.permission.yiran

import androidx.fragment.app.FragmentActivity

object PermissionX {

    private const val TAG="InvisibleFragment"

    fun request(activity:FragmentActivity,vararg permissions:String,callback:
    PermissionCallback){
        val fragmentManager=activity.supportFragmentManager
        val existedFragment=fragmentManager.findFragmentByTag(TAG)
        val fragment=if(existedFragment!=null){
            existedFragment as InvisibleFragment//大到小强制转换
        }else{
            val invisibleFragment=InvisibleFragment()
            fragmentManager.beginTransaction().add(invisibleFragment,TAG).commitNow()
            invisibleFragment
        }
        fragment.requestNow(callback, *permissions)
    }
}

将PermissionX指定为单例类,是为了让PermissionX中的接口能够更加方便地被调用。我们在PermissionX中定义了一个request()方法,这个方法接收一个FragmentActivity参数、一个可变长度的permissions参数列表,以及一个callback回调。

在request()方法中,首先获取FragmentManager的实例,然后调用findFragmentByTag()方法来判断传入的Activity参数是否已经包含了指定TAG的Fragment,也就是我们刚才编写的InvisibleFragment。如果已经包含则直接使用该Fragment,否则就创建一个新的InvisibleFragment实例,并将它添加到Activity中,同时指定一个TAG。注意:在添加结束后一定要调用commitNow()方法,而不能调用commit()方法,因为commit()方法并不会立即执行添加操作,因而无法保证下一行代码执行时InvisibleFragment已经被添加到Activity中。

有了InvisibleFragment的实例之后,接下来我们只需要调用它的requestNow()方法就能去申请运行时权限了,申请结果会自动回调到callback参数中。需要注意的是,permissions参数在这里实际上是一个数组。对于数组,我们可以去遍历也可以通过下标访问,但是不可以直接将它传递给另外一个接收可变长度参数的方法。因为,这里在调用requestNow()方法时,在Permissions参数的前面加上一个*,这个符号表示将一个数组转换成可变长度参数传递过去。

对开源库进行测试

我们可以通过在app模块中引入library模块,然后在app模块中使用PermissionX提供的接口编写一些申请运行时权限的代码,看看能否正常工作,以此来验证PermissionX库的正确性。

dependencies {
...
    implementation project(':Library')
}

接下来编写activity_main.xml文件,在里面加入一个用于拨打电话的按钮

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Make Call"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</LinearLayout>

在MainActivity 中申请拨打电话的运行时权限,并实现拨打电话的功能

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        makeCallBtn.setOnClickListener { 
            PermissionX.request(this,
                Manifest.permission.CALL_PHONE){
                allGranted,deniedList->
                if(allGranted){
                    call()
                }else{
                    Toast.makeText(this,"You denied $deniedList",Toast.LENGTH_SHORT).show()
                }
            }
        }

    }
    private fun call(){
        try {
            val intent=Intent(Intent.ACTION_CALL)
            intent.data= Uri.parse("tel:10086")
            startActivity(intent)
        }catch (e:SecurityException){
            e.printStackTrace()
        }
    }
}

只需要调用PermissionX的request()方法,传入当前的Activity和要申请的权限名,然后再Lambda表达式中处理权限的申请结果就可以了。如果allGranted等于true,就说明所有申请的权限都被用户授权了,那么就执行拨打电话的操作,否则使用Toast弹出一条失败的提示。
另外,PermissionX也支持一次性申请多个权限,只需要将所有要申请的权限名都传入request()方法就可以了。
例如:

 PermissionX.request(this,
    Manifest.permission.CALL_PHONE,Manifest.permission.WRITE_EXTERNAL_STORAGE,Manifest.permission.READ_CONTACTS){
    allGranted,deniedList->
    if(allGranted){
    Toast.makeText(this,"All permissions are granted",Toast.LENGTH_SHORT).show()

    }else{
    Toast.makeText(this,"You denied $deniedList",Toast.LENGTH_SHORT).show()
    }
    }

还要记得在AndroidManifest.xml文件中添加拨打电话的权限声明

<uses-permission android:name="android.permission.CALL_PHONE"/>

运行效果
点击MAKE CALL按钮
在这里插入图片描述
点击Allow
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/446123.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

日撸 Java 三百行day35

文章目录 说明day35 图的 m 着色问题1.问题描述2.思路2.代码 说明 闵老师的文章链接&#xff1a; 日撸 Java 三百行&#xff08;总述&#xff09;_minfanphd的博客-CSDN博客 自己也把手敲的代码放在了github上维护&#xff1a;https://github.com/fulisha-ok/sampledata day3…

JUC安全/非安全容器

一、JUC java.util.concurrent下的类就叫JUC类&#xff0c;JUC下典型的类有&#xff1a; 1.ReentrantLock可重入锁 2.Semaphore信号量 3.CountDownLatch计数器 4.CyclicBarrier循环屏障 二、线程安全&非安全容器 2.1非线性安全容器 2.2线性安全容器 三、关于HashMap ha…

【谷歌浏览器 -- Vimium 常用快捷键】

文章目录 1.1.1 标签页管理1.1.2 网页操作1.1.3 打开链接1.1.4 搜索1.1.5 自定义搜索引擎短语1.1.6 Vimimu 使用注意事项 Vimium 是一款用键盘控制 Chrome 浏览器的 Chrome 插件, 可以在 Chrome 应用商店下载到. 下面列出个人比较习惯使用的几个快捷键。 1.1.1 标签页管理 [ x…

【C语言】基础语法4:函数和递归

上一篇&#xff1a;控制流程结构 下一篇&#xff1a;数组和指针 ❤️‍&#x1f525;前情提要❤️‍&#x1f525;   欢迎来到C语言基本语法教程   在本专栏结束后会将所有内容整理成思维导图&#xff08;结束换链接&#xff09;并免费提供给大家学习&#xff0c;希望大家…

Cookie、Session、Token的区别

1 网站交互体验升级 1.1 无状态的 http 协议 HTTP 无状态协议&#xff0c;是指协议对于业务处理没有记忆能力&#xff0c;之前做了啥完全记不住&#xff0c;每次请求都是完全独立互不影响的&#xff0c;没有任何上下文信息。 缺少状态意味着如果后续处理需要前面的信息&…

基于蛋白-配体复合物构建药效团的药物设计(Pharmacophore)

基于蛋白-配体复合物构建药效团的药物设计&#xff08;Pharmacophore&#xff09; step 1.蛋白-配体复合物准备 点击File-->Import Structures导入之前已经下载好的1IEP.pdb&#xff08;Abl蛋白和Imatinib的晶体复合物&#xff09; 蛋白准备&#xff1a;点击Tasks--->…

【数据集实例】CMIP6气候模式数据下载-以河东地区为例

1 数据准备 主要根据研究区域等,介绍下载数据需求。 1.1 研究区域 以甘肃省河东地区为例,分区图如下所示: 数据时间范围如下所示: 历史时段:1970-2014年(共44年)预估时段:2015-2100年此外,根据研究内容,确定下载的变量为: 日尺度降水:缩写为pr日尺度最高/最低温…

OrCAD创建原理图库

OrCAD创建原理图库 概述常规器件建库方法大封装器件建库基于pinout表格创建原理图库导入方法&#xff1a; 通过fsp软件导入fpga原理图库 概述 原理图库是硬件设计的基本工作&#xff0c;每个新人都要先学会建库&#xff0c;才能开始画图&#xff0c;本文主要介绍几种常用的建库…

Linux多线程-4

在了解完多线程的绝大部分概念之后&#xff0c;我们本篇博客作为Linux多线程中的最后一篇博客&#xff0c;来对其中剩余内进行一个收尾。 目录 1.线程池 1.1引入 1.2原理 1.3优点 1.4实现 2.单例模式 2.1内容 2.2原理 2.3实现 2.3.1饿汉模式实现 2.3.2懒汉模式实现…

Web3技术入门向科普

Web3是指下一代互联网&#xff0c;它基于区块链技术&#xff0c;将各种在线活动更加安全、透明和去中心化。Web3是一个广义的概念&#xff0c;它包括了很多方面&#xff0c;如数字货币、去中心化应用、智能合约等等。在这篇文章中&#xff0c;我们将重点讨论Web3的入门知识&…

Opencv+Python笔记(四)图像的形态学处理

1.腐蚀与膨胀 膨胀用来处理缺陷问题&#xff0c;把缺陷填补掉&#xff0c;提高亮区面积&#xff1b; 腐蚀用来处理毛刺问题&#xff0c;把毛刺腐蚀掉&#xff0c;降低亮区面积。 腐蚀操作可以消除噪点&#xff0c;同时消除部分边界值&#xff0c;导致目标图像整体缩小。 膨胀…

轻松掌握安装k8s官方可视化界面工具知识点

轻松掌握安装k8s官方可视化界面工具知识点 1、安装 1、安装资源 kubernetes官方提供的可视化界面 https://github.com/kubernetes/dashboard 执行以下命令 kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml…

RocketMq集群搭建

各个角色介绍&#xff1a; producer&#xff1a;消息的发送者&#xff1b;举例&#xff1a;发信者consumer&#xff1a;消息的接受者&#xff1b;举例&#xff1a;收信者broker&#xff1a;暂存和传输消息&#xff1b;举例&#xff1a;邮局NameServer&#xff1a;管理Broker&am…

PWM输出实验

实验内容 使用TIM3来产生PWM输出 使用TIM3的通道2&#xff0c;把通道2重映射到PB5.产生PWM来控制DS0的亮度。 PWM简介 脉冲宽度调制&#xff08;PWM&#xff09;&#xff0c;简称脉宽调制&#xff0c;是利用微处理器的数字输出来对模拟电路进行控制的一种有效方法。 脉冲波…

excle表格打印相关问题

ps&#xff1a;无论是打印word,还是打印excel, 最后最好都保存成pdf&#xff0c;再打印。 ps&#xff1a;无论是打印word,还是打印excel, 最后最好都保存成pdf&#xff0c;再打印。 ps&#xff1a;无论是打印word,还是打印excel, 最后最好都保存成pdf&#xff0c;再打印。 …

Android修改头像之拍照、从相册选择、裁剪

手写一个修改头像的需求&#xff0c;头像图片支持手机拍照裁剪和从相册选择图片裁剪&#xff1b; 实现效果&#xff1a; 本节主要内容&#xff1a; 1&#xff09;头像修改对话框实现&#xff1b; 2&#xff09;调用系统相机拍照&#xff1b; 3&#xff09;自定义图片裁剪页…

centos7 配置LNMP环境

文章目录 LNMP环境的搭建LNMP工作流程FastCGI接口配置LNMP部署环境配置环境测试安装 Discuz LNMP环境的搭建 随着我们 Nginx web 服务器的流行&#xff0c;又出现了我们叫做 LNMP 的一种新的 web 环境服务组合。LNMP 就是 Linux Nginx Mysql PHP 等首字母的缩写。现在&…

STL学习+acwing 67 数字在排序数组中出现的次数

题目链接 67. 数字在排序数组中出现的次数 传统暴力解法 class Solution { public:int getNumberOfK(vector<int>& nums , int k) {int c0;for(int i0;i<nums.size();i){if(nums[i]k)c;}return c;} };容器的应用 set和multiset两个容器有一个count函数 set 为…

eBPF 虚拟机是如何工作的?

eBPF 是一个运行在内核中的虚拟机&#xff0c;很多人在初次接触它时&#xff0c;会把它跟系统虚拟化&#xff08;比如 kvm&#xff09;中的虚拟机弄混。其实&#xff0c;虽然都被称为“虚拟机”&#xff0c;系统虚拟化和 eBPF 虚拟机还是有着本质不同的。 系统虚拟化基于 x86 …

vue 中的事件修饰符介绍+示例说明

vue 中的事件修饰符介绍示例说明 Start 最近使用到 vue 的事件修饰符&#xff0c;发现由于时间太过久远&#xff0c;今天快速的过一下 vue 中的事件修饰符. 1. 官方文档 vue2 v-on 点击这里 vue3 v-on 点击这里 vue2中有关 v-on 的介绍 vue3中有关 v-on 的介绍 初步看下…