Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >PermissionX重磅更新,支持自定义权限提醒对话框

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

作者头像
用户1158055
发布于 2020-07-21 15:36:29
发布于 2020-07-21 15:36:29
1.8K00
代码可运行
举报
文章被收录于专栏:郭霖郭霖
运行总次数:0
代码可运行

大家早上好,今天带来一篇原创。很高兴告诉大家,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这个权限,但是我却去申请了这个权限,进入到拒绝列表当中也就是自然而然的事情了。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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的返回结果是未授权。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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,示例写法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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。部分源码实现如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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类中定义了三个抽象方法,这三个抽象方法是你在自定义对话框的时候必须要实现的,如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public abstract class RationaleDialog extends Dialog {


    /**
     * Return the instance of positive button on the dialog. Your dialog must have a positive button to proceed request.
     * @return The instance of positive button on the dialog.
     */
    abstract public @NonNull View getPositiveButton();

    /**
     * Return the instance of negative button on the dialog.
     * If the permissions that you request are mandatory, your dialog can have no negative button.
     * In this case, you can simply return null.
     * @return The instance of positive button on the dialog, or null if your dialog has no negative button.
     */
    abstract public @Nullable View getNegativeButton();

    /**
     * Provide permissions to request. These permissions should be the ones that shows on your rationale dialog.
     * @return Permissions list to request.
     */
    abstract public @NonNull List<String> getPermissionsToRequest();

}

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

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
val myRationaleDialog = ...
scope.showRequestReasonDialog(myRationaleDialog)

一切搞定!

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

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<resources>
    ...
    <style name="CustomDialog" parent="android:style/Theme.Dialog">
        <!--背景颜色及和透明程度-->
        <item name="android:windowBackground">@android:color/transparent</item>
        <!--是否去除标题 -->
        <item name="android:windowNoTitle">true</item>
        <!--是否去除边框-->
        <item name="android:windowFrame">@null</item>
        <!--是否浮现在activity之上-->
        <item name="android:windowIsFloating">true</item>
        <!--是否模糊-->
        <item name="android:backgroundDimEnabled">true</item>
    </style>
</resources>

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

custom_dialog_bg.xml:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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文件,用于作为我们自定义对话框的布局,代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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文件,代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<?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,然后编写如下代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@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()这三个方法都是进行了最基本的实现,将对话框中的确定按钮、取消按钮、以及要申请的权限列表返回即可。

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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开源库地址:https://github.com/guolindev/PermissionX

如何升级

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dependencies {
    ...
    implementation 'com.permissionx.guolindev:permissionx:1.3.0'
}

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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
dependencies {
    ...
    implementation 'com.permissionx.guolindev:permission-support:1.3.0'
}

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020/07/21 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
原来在Android中请求权限也可以有这么棒的用户体验
PermissionX这个开源项目起源于我今年出版的新书《第一行代码 第3版》,本来的主要目的只是为了带领读者朋友们学习如何开发并发布一个开源库。然而随着我发现这个项目不仅有学习的价值,还可以真正投入到实际项目的使用当中,于是后面又对PermissionX进行了多个版本的迭代,目前已经成为了一个非常稳定和方便的权限请求库。
用户1158055
2020/09/16
2.7K0
原来在Android中请求权限也可以有这么棒的用户体验
Android运行时权限终极方案,用PermissionX吧
有些朋友的阅读速度真是令人印象深刻,我记得在《第三行代码》刚刚发售一周不到的时间里,竟然就有人已经读到第9章了(因为公众号后台有人回复第9章里隐藏的关键字)。现在,《第三行代码》已经出版一个月有余了,相信已经有不少朋友将全本书都看完了。
用户1158055
2020/05/27
1.3K0
Android运行时权限终极方案,用PermissionX吧
PermissionX现在支持Java了!还有Android 11权限变更讲解
各位小伙伴们早上好,不知道你们有没有惊讶于我的速度,因为不久之前我才新发布的开源库PermissionX今天又更新了。
用户1158055
2020/06/16
1.7K0
PermissionX现在支持Java了!还有Android 11权限变更讲解
PermissionX 1.7发布,全面支持Android 13运行时权限
还记得上次发布PermissionX 1.6版本还是在去年10月份的时候,当时是对Android 12系统进行了支持。详情可以参考这篇文章 PermissionX 1.6发布,支持Android 12,可能是今年最大的版本升级 。
用户1158055
2022/11/07
3.8K0
PermissionX 1.7发布,全面支持Android 13运行时权限
为什么说在Android中请求权限从来都不是一件简单的事情?
周末时间参加了东莞和深圳的两场GDG,因为都是线上参与,所以时间上并不赶,我只需要坐在家里等活动开始就行了。
用户1158055
2020/07/29
1.4K0
为什么说在Android中请求权限从来都不是一件简单的事情?
Android 6.0申请权限工具类
android 6.0以后,新增加了动态申请权限这一要求,具体是怎么回事呢? 1.概述 关于运行时权限 在Android6.0开始,App可以直接安装,App在运行时一个一个询问用户授予权限,系统会弹出一个对话框让用户选择是否授权某个权限给App(这个Dialog不能由开发者定制),当App需要用户授予不恰当的权限的时候,用户可以拒绝,用户也可以在设置页面对每个App的权限进行管理。 特别注意:这个对话框不是开发者调用某个权限的功能时由系统自动弹出,而是需要开发者手动调用,如果你直接调用而没有去申请权限的话
CatEatFish
2020/07/09
1.3K0
Android 6.0申请权限工具类
【Android 应用开发】Google 官方 EasyPermissions 权限申请库 ( 完整代码示例 | 申请权限 | 申请权限原理对话框 | 引导用户手动设置权限对话框 )
权限判定 : 首先要判定是否已经授权指定的权限数组 ; 调用 EasyPermissions.hasPermissions 方法 , 进行判定 ;
韩曙亮
2023/03/28
2.4K0
【Android 应用开发】Google 官方 EasyPermissions 权限申请库 ( 完整代码示例 | 申请权限 | 申请权限原理对话框 | 引导用户手动设置权限对话框 )
PermissionX 1.5发布,支持申请Android特殊权限啦
Hello大家早上好,说起PermissionX,其实我已经有段时间没有更新这个框架了。一是因为现在工作确实比较忙,没有过去那么多的闲暇时间来写开源项目,二是因为,PermissionX的主体功能已经相当稳定,并不需要频繁对其进行变更。
用户1158055
2021/07/29
1.1K0
PermissionX 1.5发布,支持申请Android特殊权限啦
Android自定义Dialog对话框
前言 Android项目经常需要使用对话框来进行交互,本文将介绍一个简单自定义的Dialog案例 效果图 代码部分 自定义Dialog布局 <?xml version="1.0" encodin
sr
2019/05/23
1.5K0
PermissionX 1.6发布,支持Android 12,可能是今年最大的版本升级
没错,PermissionX又升级了,并且这次版本变化非常大,很有可能是今年最大幅度的一次升级。
用户1158055
2021/10/13
9410
项目需求讨论-Android 自定义Dialog实现步骤及封装
在项目中,我们会遇到各种各样的界面需求,比如对话框和选择框,都是会配合具体项目的UI界面来做,而不是说用自带的弹出框。比如下面在登录界面的二个对话框效果。都是我在做具体项目中所要求实现的:
用户2802329
2018/08/07
1.5K0
项目需求讨论-Android 自定义Dialog实现步骤及封装
Android开发笔记(六十六)自定义对话框
Android中最常用的对话框是AlertDialog,它可以完成常见的交互操作,如提示、确认、选择等等,然后就是进度对话框ProgressDialog(参见《Android开发笔记(四十九)异步任务处理AsyncTask》)。 AlertDialog没有公开的构造函数,必须借助于AlertDialog.Builder才能完成参数设置。Builder的常用方法如下: setIcon : 设置标题的图标。 setTitle : 设置标题的文本。 setCustomTitle : 设置自定义的标题视图。 --以上方法用于设置标题部分。注意setTitle和setCustomTitle只能设置其一,不能重复设置。 setMessage : 设置内容的文本。 setView : 设置自定义的内容视图。 setAdapter : 设置List方式的内容视图。使用较麻烦,一般不用。 setItems : 设置Spinner方式的内容视图。窗口显示与对话框模式的Spinner极为相似,没有底部的按钮,一旦选中某项就立即关闭对话框。 setSingleChoiceItems : 设置单选列表的内容视图。与setItems的区别在于有显示底部的交互按钮,并且每项右边有单选按钮。 setMultiChoiceItems : 设置多选列表的内容视图。底部有交互按钮,并且每项右边有复选按钮。 --以上方法用于设置内容部分。注意这些方法互相冲突,同时只能设置其一。 setPositiveButton : 设置肯定按钮的信息,如文本、点击监听器。 setNegativeButton : 设置否定按钮的信息,如文本、点击监听器。 setNeutralButton : 设置中性按钮的信息,如文本、点击监听器。 --以上方法用于设置交互按钮。 通过Builder设置完参数,还需调用create方法才能生成AlertDialog对象。不过要想在页面上显示AlertDialog,还得调用该对象的show方法。
aqi00
2019/01/18
2.8K0
Android权限管理PermissionsDispatcher2.3.2使用+原生6.0权限使用
PermissionsDispatcher2.3.2使用 Android6.0权限官网 https://developer.android.com/about/versions/marshmallo
庞小明
2018/03/07
1.6K0
Android权限管理PermissionsDispatcher2.3.2使用+原生6.0权限使用
动画对话框实现
炫酷的动画对话框 关键技术 AlertDialog的自定义 代码 //创建对话框实例 dlg = new AlertDialog.Builder(this).create(); dlg.show(); //显示对话框 Window window = dlg.getWindow(); //获取对话框窗口 window.setGravity
对话、
2022/02/22
1.8K0
动画对话框实现
安卓6.0申请多个动态权限
说来惭愧,最近在测试一个客户端文件上传的功能;一直在拿模拟器做的调试,对接成功后,文件上传没问题,相安无事;刚好自己用的是安卓机,就直接打包发到真机调试了,文件竟然一直上传不到服务器后面用了旧手机发现又能够上传成功,结果被直接蠢哭;一个是安卓8.0,一个是安卓5.0。果然,获取动态权限,调试成功。
木溪bo
2018/12/27
1.7K0
Android常用对话框大全——Dialog「建议收藏」
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/137960.html原文链接:https://javaforall.cn
全栈程序员站长
2022/08/23
4.4K0
Android常用对话框大全——Dialog「建议收藏」
Android M 权限最佳实践
前言 Google在Android 6.0 上开始原生支持应用权限管理,再不是安装应用时的一刀切。权限管理虽然很大程度上增加了用户的可操作性,但是却苦了广大Android开发者。由于权限管理涉及到应用
非著名程序员
2018/02/02
1.4K0
Android M 权限最佳实践
信息提醒之对话框(AlertDialog + ProgressDialog)-更新中
Android中的对话框需要使用AlertDialog类来显示,主要用于显示提醒信息,不过这个对话框类可不仅仅能用来显示一些信息,我们可以在对话框中防止任何的控件,使其成为一个复杂且功能强大的用户接口。一个典型的例子就是使用AlertDialog做一个登录对话框。
小小工匠
2021/08/16
4.7K0
安卓开发_使用AlertDialog实现对话框
示例: 一、确定对话框 1 AlertDialog.Builder builder = new AlertDialog.Builder(this); 2 builder.setTitle("确认对话框"); 3 builder.setIcon(R.drawable.icon_72); 4 builder.setMessage("这里是对话框内容"); 5 builder.setPositiveButto
听着music睡
2018/05/18
1.1K0
推荐阅读
相关推荐
原来在Android中请求权限也可以有这么棒的用户体验
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验