阅读 2500

PermissionX重磅更新,支持自定义权限提醒对话框

本文同步发表于我的微信公众号,在微信搜索 郭霖 即可关注,每个工作日都有文章更新。

大家好,今天带来一篇原创。很高兴告诉大家,PermissionX 又出新版本了。

之前因为很长一段时间都在准备 GDG 的演讲,手头上的不少工作都暂时放了一放。而 GDG 结束之后,我又立马恢复了之前的工作状态,以最快的速度发布了新版的 PermissionX。

从我对这个项目的更新频率上大家应该就可以看出,这并不是我随便写着玩的一个项目,而是真的准备长期维护下去的开源项目。大家在使用过程中如果发现了什么问题,也都可以反馈给我。

截至目前为止,PermissionX 已经迭代更新了三个版本,而最新的 1.3.0 版本更是加入了非常重要的自定义权限提醒对话框的功能。如果你觉得之前 PermissionX 自带的权限提醒对话框太丑,从而无法投入正式的生产环境,那么这次你将可以充分发挥自己的 UI 实力,打造出一个漂亮的权限提醒界面。

如果你对 PermissionX 的用法还完全没有了解,可以先去参考之前我发布的两篇文章 Android 运行时权限终极方案,用 PermissionX 吧PermissionX 现在支持 Java 了!还有 Android 11 权限变更讲解

下面我们就来看一下 1.3.0 版本到底增加了哪些新特性。

后台定位权限的正确写法

在上一个版本当中,PermissionX 引入了对 Android 11 权限变更的支持。为了更好地保护用户隐私,在 Android 11 系统当中,ACCESS_BACKGROUND_LOCATION 权限变成了一个要去单独申请的权限,如果你将它和前台定位权限一起申请,则会产生崩溃。

但是在 Android 10 当中,前台定位权限和后台定位权限却是可以一起申请,分开申请虽然也是可以的,但是用户体验方面较差,因为要弹两次权限申请对话框。

为了减少这种系统差异型的适配,PermissionX 专门针对 Android 11 这部分的权限变更做了适配,大家不需要单独去为 Android 10 和 Android 11 系统分别写一套权限处理的逻辑,而是统统交给 PermissionX 就行了,PermissionX 内部会自动针对不同的系统版本做出相应的逻辑处理。

不过,我发现在实际的使用过程中,有一些开发者还是没能搞清楚 Android 11 权限适配这部分的正确用法,并且向我提出了一些问题。因此在开始介绍 1.3.0 新版功能之前,我先来请大家演示一下后台定位权限的正确申请方式。

首先来看问题是什么,这个问题我被问了不止一次。

这位朋友说,PermissionX 在 8.0 系统中获取后台定位权限,该权限会直接进入 deniedList,也就是拒绝列表当中。

为什么会出现这个现象呢?因为 ACCESS_BACKGROUND_LOCATION 是在 Android 10 系统中引入的新权限,8.0 系统中并没有这个权限。

API level 29 就是 Android 10 系统的意思。

那么 8.0 系统中没有 ACCESS_BACKGROUND_LOCATION 这个权限,但是我却去申请了这个权限,进入到拒绝列表当中也就是自然而然的事情了。

虽说是自然而然的事情,但是这样的请求结果会给一些朋友的使用造成困扰。我们来看如下一段代码:

PermissionX.init(this)
    .permissions(Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_BACKGROUND_LOCATION)
    .request { allGranted, grantedList, deniedList ->
        if (allGranted) {
            Toast.makeText(activity, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(activity, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show()
        }
    }

复制代码

这里我们同时请求了 ACCESS_FINE_LOCATION 和 ACCESS_BACKGROUND_LOCATION 两个权限,如果在 Android 10 以上系统运行的话,只要用户同时将前台定位和后台定位权限都授权给了我们,那么最终回调时 allGranted 就会是 true。但是在 Android 10 以下系统运行的话,由于 ACCESS_BACKGROUND_LOCATION 是永远不会授权的,所以 allGranted 也就一定会是 false。

这种请求结果确实会给一些开发者的编码逻辑造成困扰,有些朋友认为这是一个 bug,应该在 Android 10 以下的系统版本中自动授权 ACCESS_BACKGROUND_LOCATION 权限,因为在低于 Android 10 的系统版本中,本身就是允许后台定位功能的。

关于这个建议我也思考了很久,在低于 Android 10 系统版本的时候 ACCESS_BACKGROUND_LOCATION 权限到底应该是进入授权列表还是拒绝列表?

最终我还是保留了现有的逻辑,原因也很简单,因为如果你在低于 Android 10 系统中调用系统的 API 来判断 ACCESS_BACKGROUND_LOCATION 权限是否授权,答案也是否定的。因此,保持和系统 API 一致的返回结果对我来说更加重要,因为 PermissionX 本质上还是对系统权限 API 的封装,我不应该擅自篡改系统返回的授权结果。

但是刚才提到的,如果同时申请了前台和后台权限,不同系统版本中的逻辑处理要怎么办呢?因为低于 Android 10 系统时,allGranted 一定会是 false。

这个问题其实并不难解决,我们先来看一下按照上述的写法,Android Studio 是否认为是完全正确的呢?

可以看到,当申请 ACCESS_BACKGROUND_LOCATION 权限时,Android Studio 给出了一个警告提示,说我们调用的 API 是在 level 29(Android 10.0)时才加入的,而当前项目工程兼容的最低系统版本是 15(Android 4.0)。

也就是说,这种申请权限的写法本身就是不合理的,在低版本的手机系统当中,系统根本就不能认别 ACCESS_BACKGROUND_LOCATION 到底是个什么东西。

因此,最正确的做法是,当我们的程序运行在低于 Android 10 系统的手机上时,就不应该去申请 ACCESS_BACKGROUND_LOCATION 权限,而不是纠结为什么 ACCESS_BACKGROUND_LOCATION 的返回结果是未授权。

下面我来改造一下上述的代码:

val permissionList = ArrayList<String>()
permissionList.add(Manifest.permission.ACCESS_FINE_LOCATION)
if (Build.VERSION.SDK_INT >= 29) {
    permissionList.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
}
PermissionX.init(this)
    .permissions(permissionList)
    .request { allGranted, grantedList, deniedList ->
        if (allGranted) {
            Toast.makeText(activity, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(activity, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show()
        }
    }

复制代码

可以看到,这里我将即将申请的权限放到了一个 List 集合当中,但是只有在系统版本大于等于 29 时,才会将 ACCESS_BACKGROUND_LOCATION 加入集合。因此,在低版本的手机系统当中,是不会申请后台定位权限的。这样,allGranted 变量的值也就不会再受到影响了。

另外,使用这种写法后,Android Studio 也不会再给我们警告提示了。

支持 Fragment

现在 Fragment 的使用貌似比 Activity 还要普遍,而上个版本的 PermissionX 在初始化时只支持传入 Activity 类型的实例,确实是我考虑不周了。

有好几位朋友请我询问,在 Fragment 中要如何使用 PermissionX 来申请权限?这个问题说实话,一下子把我问懵了,好像我之前确实没考虑过这个问题。

不过后来我反应过来之后想到,在 Fragment 中不是也可以获取到 Activity 的实例吗?那么 getActivity() 之后再传给 PermissionX 不就可以了嘛。

我认为这样是可以解决问题的,但是根据目前得到的一些反馈,在 Fragment 中使用 PermissionX 可能会造成一种 IllegalStateException。

这个问题因为也是不止有一个人遇到了,所以我认为可能并不是一种偶然的现象。

但奇怪的是,我自己想尽了各种办法去重现这个问题,都始终没能重现,不知道是不是和使用的 Fragment 版本有关。

不过没关系,即使没能重现这个问题,也并不影响我解决它。根据 stackoverflow 上的解答(解决 Android 问题的神网站),当我们在 Fragment 中再去添加另一个子 Fragment 时,应该使用 ChildFragmentManager 而不是 FragmentManager。

那么很明显,如果使用刚才 getActivity() 的方式,PermissionX 在内部去添加请求权限的隐藏 Fragment 时,使用的肯定还是 FragmentManager,我想这大概就是造成问题的原因。

而 1.3.0 版本的 PermissionX 引入了对 Fragment 的原生支持,当我们在 Fragment 中使用 PermissionX 时不需要再调用 getActivity() 了,而是可以直接传入 this,示例写法如下:

class MainFragment : Fragment() {

    ...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        PermissionX.init(this)
            .permissions(Manifest.permission.ACCESS_FINE_LOCATION)
            .request { allGranted, grantedList, deniedList -> 
                
            }
    }

}

复制代码

PermissionX 在内部会自动判断,如果开发者初始化时传入的是 Activity,那么将会自动使用 FragmentManager。而如果传入的是 Fragment,那么将会自动使用 ChildFragmentManager。部分源码实现如下所示:

private InvisibleFragment getInvisibleFragment() {
    FragmentManager fragmentManager;
    if (fragment != null) {
        fragmentManager = fragment.getChildFragmentManager();
    } else {
        fragmentManager = activity.getSupportFragmentManager();
    }
    ...
}

复制代码

当然,这只是我根据有限的错误信息以及 stackoverflow 上的解答,推断出来的一种解决方案。我自己这边是无从验证的,因为我本身就没能重现这个问题。

如果大家在使用 1.3.0 版本的 PermissionX 之后还是有遇到这个问题,那么请继续反馈给我,并且最好能指导我一下如何将这个问题重现。

自定义权限提醒对话框

自定义权限提醒对话框应该是 1.3.0 版本最重磅的一个功能了。

之前的 PermissionX 虽然在权限处理流程方面考虑的非常周全,比如说我们申请的权限被拒绝了怎么办?我们申请的权限被永久拒绝了怎么办?但是,PermissionX 在权限被拒绝时的提醒对话框是系统默认的样式,而且只能输入文字内容,满足不了很多开发者的要求。如下图所示。

无法任意地定制自己想要的界面,可能是限制 PermissionX 投入正式生产环境的最大因素。

而 1.3.0 版本则完全解决了这个问题,现在大家可以自定义各种各样的对话框界面,使其与你的项目 UI 风格完全一致。

至于这部分的用法也非常简单,PermissionX 1.3.0 版本提供了一个 RationalDialog 的抽象类,当你需要自定义权限提醒对话框的时候,只需要继承自这个类即可。而 RationaleDialog 实际上继承的也是系统的 Dialog 类,因此在自定义对话框的用法上面,和你平时编写的代码并没有什么两样。

只不过由于我们这个对话框的作用是为了向用户解释为什么我们需要申请这些权限,以及让用户理解原因之后同意申请。因此,对话框上面必须要有一个确定按钮,以及一个可选的取消按钮(如果是必须授予的权限,可不提供取消按钮)。另外,我们还必须要知道即将申请哪些权限,否则界面上不知该显示什么样的提示信息。

因此,RationaleDialog 类中定义了三个抽象方法,这三个抽象方法是你在自定义对话框的时候必须要实现的,如下所示:

public abstract class RationaleDialog extends Dialog {


    
    abstract public @NonNull View getPositiveButton();

    
    abstract public @Nullable View getNegativeButton();

    
    abstract public @NonNull List<String> getPermissionsToRequest();

}

复制代码

getPositiveButton() 方法用于返回当前自定义对话框上的确定按钮;getNegativeButton() 方法用于返回当前自定义对话框上的取消按钮,如果对话框不可取消的话,直接返回 null 即可;getPermissionsToRequest() 方法用于返回即将申请哪些权限。

RationaleDialog 只强制要求你实现以上三个方法,至于其他的自定义界面部分完全由你自由发挥,怎样实现都可以。

现在,当权限被拒绝时,我们只需要将自定义的对话框传给 showRequestReasonDialog() 方法即可,代码如下所示:

val myRationaleDialog = ...
scope.showRequestReasonDialog(myRationaleDialog)

复制代码

一切搞定!

这样看下来,自定义权限提醒对话框这个功能,PermissionX 的工作倒是非常简单,最难的还是在于自定义 UI 界面这部分。因此,下面我来演示一种自定义对话框的实现方法,供大家参考。

一个好看的自定义对框界面需要分为很多步去完成,这里我向大家一步步进行展示。首先第一步要定义一个主题,编辑 styles.xml 文件,并添加如下内容:

<resources>
    ...
    <style >
        <!--背景颜色及和透明程度-->
        <item >@android:color/transparent</item>
        <!--是否去除标题 -->
        <item >true</item>
        <!--是否去除边框-->
        <item >@null</item>
        <!--是否浮现在activity之上-->
        <item >true</item>
        <!--是否模糊-->
        <item >true</item>
    </style>
</resources>

复制代码

接下来我们要提供对话框的背景样式,以及确定按钮和取消按钮的背景样式。在 drawable 目录下新建 custom_dialog_bg.xml、positive_button_bg.xml、negative_button_bg.xml 三个文件,代码分别如下所示。

custom_dialog_bg.xml:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#272727" />
    <corners android:radius="20dp" />
</shape>

复制代码

positive_button_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#2084c2" />
    <corners android:radius="20dp" />
</shape>

复制代码

negative_button_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <solid android:color="#7d7d7d" />
    <corners android:radius="20dp" />
</shape>

复制代码

然后在 layout 目录下新建 custom_dialog_layout.xml 文件,用于作为我们自定义对话框的布局,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/custom_dialog_bg"
    android:orientation="vertical"
    >

    <TextView
        android:id="@+id/messageText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:textColor="#fff"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginTop="20dp"
        />

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_margin="20dp"
        android:scrollbars="none"
        android:layout_weight="1">

        <LinearLayout
            android:id="@+id/permissionsLayout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            />

    </ScrollView>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        android:layout_marginBottom="10dp">

        <Button
            android:id="@+id/negativeBtn"
            android:layout_width="120dp"
            android:layout_height="46dp"
            android:background="@drawable/negative_button_bg"
            android:textColor="#fff"
            android:text="拒绝"/>

        <Button
            android:id="@+id/positiveBtn"
            android:layout_width="120dp"
            android:layout_height="46dp"
            android:layout_marginStart="30dp"
            android:layout_marginLeft="30dp"
            android:background="@drawable/positive_button_bg"
            android:textColor="#fff"
            android:text="开启"/>

    </LinearLayout>

</LinearLayout>

复制代码

一个很简单的界面,这里就不具体解释了。

另外,由于我们会在对话框当中动态显示要申请哪些权限,因此还需要定义一个额外的布局来显示动态内容。在 layout 目录下新建一个 permissions_item.xml 文件,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/bodyItem"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    android:textSize="16sp"
    android:textColor="#fff">
</TextView>

复制代码

动态内容不用多复杂,直接使用一个 TextView 即可。

好了,将上述布局文件都定义好了之后,接下来我们就可以进行编码实现了。新建一个 CustomDialog 类,并让它继承自 RationaleDialog,然后编写如下代码:

@TargetApi(30)
class CustomDialog(context: Context, val message: String, val permissions: List<String>) : RationaleDialog(context, R.style.CustomDialog) {

    private val permissionMap = mapOf(Manifest.permission.READ_CALENDAR to Manifest.permission_group.CALENDAR,
        Manifest.permission.WRITE_CALENDAR to Manifest.permission_group.CALENDAR,
        Manifest.permission.READ_CALL_LOG to Manifest.permission_group.CALL_LOG,
        Manifest.permission.WRITE_CALL_LOG to Manifest.permission_group.CALL_LOG,
        Manifest.permission.PROCESS_OUTGOING_CALLS to Manifest.permission_group.CALL_LOG,
        Manifest.permission.CAMERA to Manifest.permission_group.CAMERA,
        Manifest.permission.READ_CONTACTS to Manifest.permission_group.CONTACTS,
        Manifest.permission.WRITE_CONTACTS to Manifest.permission_group.CONTACTS,
        Manifest.permission.GET_ACCOUNTS to Manifest.permission_group.CONTACTS,
        Manifest.permission.ACCESS_FINE_LOCATION to Manifest.permission_group.LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION to Manifest.permission_group.LOCATION,
        Manifest.permission.ACCESS_BACKGROUND_LOCATION to Manifest.permission_group.LOCATION,
        Manifest.permission.RECORD_AUDIO to Manifest.permission_group.MICROPHONE,
        Manifest.permission.READ_PHONE_STATE to Manifest.permission_group.PHONE,
        Manifest.permission.READ_PHONE_NUMBERS to Manifest.permission_group.PHONE,
        Manifest.permission.CALL_PHONE to Manifest.permission_group.PHONE,
        Manifest.permission.ANSWER_PHONE_CALLS to Manifest.permission_group.PHONE,
        Manifest.permission.ADD_VOICEMAIL to Manifest.permission_group.PHONE,
        Manifest.permission.USE_SIP to Manifest.permission_group.PHONE,
        Manifest.permission.ACCEPT_HANDOVER to Manifest.permission_group.PHONE,
        Manifest.permission.BODY_SENSORS to Manifest.permission_group.SENSORS,
        Manifest.permission.ACTIVITY_RECOGNITION to Manifest.permission_group.ACTIVITY_RECOGNITION,
        Manifest.permission.SEND_SMS to Manifest.permission_group.SMS,
        Manifest.permission.RECEIVE_SMS to Manifest.permission_group.SMS,
        Manifest.permission.READ_SMS to Manifest.permission_group.SMS,
        Manifest.permission.RECEIVE_WAP_PUSH to Manifest.permission_group.SMS,
        Manifest.permission.RECEIVE_MMS to Manifest.permission_group.SMS,
        Manifest.permission.READ_EXTERNAL_STORAGE to Manifest.permission_group.STORAGE,
        Manifest.permission.WRITE_EXTERNAL_STORAGE to Manifest.permission_group.STORAGE,
        Manifest.permission.ACCESS_MEDIA_LOCATION to Manifest.permission_group.STORAGE
    )

    private val groupSet = HashSet<String>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.custom_dialog_layout)
        messageText.text = message
        buildPermissionsLayout()
        window?.let {
            val param = it.attributes
            val width = (context.resources.displayMetrics.widthPixels * 0.8).toInt()
            val height = param.height
            it.setLayout(width, height)
        }
    }

    override fun getNegativeButton(): View? {
        return negativeBtn
    }

    override fun getPositiveButton(): View {
        return positiveBtn
    }

    override fun getPermissionsToRequest(): List<String> {
        return permissions;
    }

    private fun buildPermissionsLayout() {
        for (permission in permissions) {
            val permissionGroup = permissionMap[permission]
            if (permissionGroup != null && !groupSet.contains(permissionGroup)) {
                val textView = LayoutInflater.from(context).inflate(R.layout.permissions_item, permissionsLayout, false) as TextView
                textView.text = context.packageManager.getPermissionGroupInfo(permissionGroup, 0).loadLabel(context.packageManager)
                permissionsLayout.addView(textView)
                groupSet.add(permissionGroup)
            }
        }
    }

}

复制代码

这段代码其实在自定义界面部分的篇幅并不多,就是在 onCreate() 方法当中通过 setContentView() 显示了我们刚才自定义的布局而已。

但是 permissionMap 这部分代码所占的篇幅却比较大,为什么要写这段代码呢?我来向大家解释一下。

Android 的权限机制其实是由权限和权限组共同组成的。一个权限组中可能会包含多个权限,比如 CALENDAR 权限组中就包含了 READ_CALENDAR 和 WRITE_CALENDAR 这两个权限。

我们平时在申请权限时,需要使用权限名来申请,而不能使用权限组名,但是当权限组中的某个权限被授权之后,同组的其他权限也会被自动授权,不需要再去逐个申请。

因此,当我们收到了一个要申请的权限列表时,其实并不需要将这个列表中的权限全部显示到界面上,而是只显示要申请的权限组名即可,这样可以让界面更精简。根据我之前的统计,Android 10 系统中的运行时权限有 30 个,而权限组只有 11 个。

上述代码中的 permissionMap 以及 buildPermissionsLayout() 方法其实就是在处理这个逻辑,根据传入的权限来获取其相应的权限组,然后动态添加到对话框当中。

除此之外,getPositiveButton()、getNegativeButton()、getPermissionsToRequest() 这三个方法都是进行了最基本的实现,将对话框中的确定按钮、取消按钮、以及要申请的权限列表返回即可。

这样我们就将自定义权限提醒对话框完成了!接下来的工作就是如何使用它,这就非常简单了,代码如下所示:

PermissionX.init(this)
    .permissions(Manifest.permission.CAMERA, Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.RECORD_AUDIO)
    .onExplainRequestReason { scope, deniedList, beforeRequest ->
        val message = "PermissionX需要您同意以下权限才能正常使用"
        val dialog = CustomDialog(context, message, deniedList)
        scope.showRequestReasonDialog(dialog)
    }
    .onForwardToSettings { scope, deniedList ->
        val message = "您需要去设置中手动开启以下权限"
        val dialog = CustomDialog(context, message, deniedList)
        scope.showForwardToSettingsDialog(dialog)
    }
    .request { allGranted, grantedList, deniedList ->
        if (allGranted) {
            Toast.makeText(activity, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show()
        } else {
            Toast.makeText(activity, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show()
        }
    }

复制代码

绝大部分的用法和之前版本的 PermissionX 并没有什么区别,我就不详细解释了。最需要关注的点在 onExplainRequestReason 和 onForwardToSettings 这两个方法的 Lambda 表达式中,这里我们创建了 CustomDialog 的实例,然后分别调用 scope.showRequestReasonDialog() 和 scope.showForwardToSettingsDialog() 方法,并将 CustomDialog 的实例传入即可。

大功告成!现在运行一下程序,你将会体验到非常棒的权限请求流程,如下图所示。

当然,这还只是我实现的一个比较基础的自定义权限提醒对话框,现在充分发挥你的 UI 实力的时候到了。

上述自定义对话框的完整代码实现,我都放到了 PermissionX 的开源项目工程当中,下载之后直接运行就可以看到上图中的效果了。

如何升级

关于 PermissionX 新版本的内容变化就介绍到这里,升级的方式非常简单,修改一下 dependencies 当中的版本号即可:

dependencies {
    ...
    implementation 'com.permissionx.guolindev:permissionx:1.3.0'
}

复制代码

另外,如果你的项目还没有升级到 AndroidX,那么可以使用 Permission-Support 这个版本,用法都是一模一样的,只是 dependencies 中的依赖声明需要改成:

dependencies {
    ...
    implementation 'com.permissionx.guolindev:permission-support:1.3.0'
}

复制代码

总体让我评价一下的话,自定义权限提醒对话框给大家带来了更多的可能性,但是在易用性方面还是有些不足,因为自定义一个对话框总体还是比较麻烦的。因此,下个版本当中,我准备内置一些对话框的实现,从而让那些对界面要求不太高的朋友们可以更加轻松地使用 PermissionX,详情请阅读 原来在 Android 中请求权限也可以有这么棒的用户体验

如果想要学习 Kotlin 和最新的 Android 知识,可以参考我的新书 《第一行代码 第 3 版》点击此处查看详情

关注我的技术公众号“郭霖”,每天都有优质技术文章推送。

文章分类
Android
文章标签