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 删除。

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

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
CentOS 安装 GCC【在线 + 离线】
在线安装很简单一个命令搞定 yum -y install make zlib zlib-devel gcc-c++ libtool openssl openssl-devel
Demo_Null
2020/12/09
2.7K0
CentOS  安装 GCC【在线 + 离线】
screen离线安装
此时会报一个错误 configure: error: !!! no tgetent - no screen
华创信息技术
2022/05/28
1.9K0
搭建 LNMP 环境
LNMP是建立web应用的平台,是Linux、NGINX,MySQL(有时也指MariaDB,数据库软件) 和PHP(有时也是指Perl或Python) 的简称。
剧终
2020/08/27
1.2K0
搭建 LNMP 环境
【解决方案】Vue项目部署指南:Nginx部署前端Vue.js项目
首先查找Nginx安装的路径,查看是否安装过nginx: 输入:whereis nginx 如果显示地址,说明已经安装过nginx了。可以跳到2.2步。
中杯可乐多加冰
2024/09/12
3.1K0
CentOS 7 安装Mono 和 MonoDevelop
【原文来自:http://www.linuxidc.com/Linux/2015-01/112350.htm】 好几年前安装过mono,后来一直没有再用过。最近购买了新服务器,想再安装一个试试,先安装
用户1177503
2018/02/26
2.4K0
Linux编译安装Python3.9——以CentOS7为例
在CentOS中使用yum安装python3时,总是安装不到需要的版本,在一些特定的环境下又需要特定版本的Python。然而网上的各种教程又良莠不齐(在此特别diss某某DN),故本篇将详细整理一遍在CentOS7上编译安装Python3的过程。
MoyiTech
2023/02/27
1.7K0
Linux编译安装Python3.9——以CentOS7为例
nginx 手动离线安装 及依赖包安装(pcre+zlib+openssl)
本文介绍了使用源码编译安装 Nginx 的详细步骤及编译参数信息。首先,提到了必需的依赖包,包括 PCRE、Zlib 和 OpenSSL,并提供了它们的下载链接。随后,详细说明了编译环境的准备工作,包括检查和安装 gcc、g++、以及 make。接着,逐步介绍了如何安装各个依赖包以及 Nginx 的过程,包括解压、配置、编译和安装。最后,给出了启动 Nginx 的命令,并提示如何验证安装是否成功。
默 语
2024/11/20
1.7K0
nginx 手动离线安装 及依赖包安装(pcre+zlib+openssl)
Centos7安装Python3并安装Requests的方法
由于centos7原本就安装了Python2,而且这个Python2不能被删除,因为有很多系统命令,比如yum都要用到。
Erwin
2020/03/02
6K0
Centos7安装Python3并安装Requests的方法
【Nginx】在线安装与离线安装
第三步 ./configure完成后,回到pcre目录下执行make,最后执行make install
陶然同学
2024/01/05
1.7K0
【Nginx】在线安装与离线安装
Linux下配置安装PHP环境
一、安装Apache2.2.22 1、到官网下载  http://httpd.apache.org/download.cgi     2、解压     tar  -zxvf httpd-2.2.22.tar.gz 3、建立目标文件夹(注意以下所有操作都时在root用户下执行的)     mkdir /usr/local/apache2     也就是说等下安装的apache2要安装到这个文件夹里面 4、配置     回到原来解压之后产生的文件夹     ./configure --prefix=/usr/local/apache2 --enable-module=shared     要加上后面的参数,否则无法使用php,-enable-module=shared表示Apache可以动态的加载模块
用户1685462
2021/07/16
15.9K0
Linux安装Nginx 在线&离线
【yum 安装最新版nginx:https://www.cnblogs.com/xxoome/p/7256214.html】 在安装nginx前首先要确认系统中安装了gcc、pcre-devel、zlib-devel、openssl-devel。 Linux下检查是否安装过某软件包:http://www.cnblogs.com/xxoome/p/5866553.html 安装命令:
JokerDJ
2023/11/27
1.9K0
Linux安装Nginx 在线&离线
CentOS 7 安装Mono 和 MonoDevelop
MonoDevelop 是个Linux平台上的开放源代码集成开发环境,主要用来开发Mono与.NET Framework软件。MonoDevelop 整合了很多Eclipse与Microsoft Visual Studio的特性,像是 Intellisense、版本控制还有 GUI 与 Web 设计工具。另外还整合了GTK# GUI设计工具(叫做Stetic)。目前支援的语言有C#、Java、BOO、Nemerle、Visual Basic .NET、CIL、C与C++ 。
星哥玩云
2022/07/04
8980
centos7安装python 3.7_python安装后如何使用
RHEL以及他的衍生发行版如CentOS、Scientific Linux为了稳定,官方的rpm repository提供的rpm包往往是很滞后的,当然了,这样做这是无可厚非的,毕竟这是服务器版本,安全稳定是重点,官方的rpm repository提供的rpm包也不够丰富,很多时候需要自己编译那太辛苦了,而EPEL恰恰可以解决这两方面的问题。
全栈程序员站长
2022/11/08
6860
Centos7下nginx的安装与配置
说明:软件安装的基础目录路径:/usr/local 所以下载软件的时候切换到此目录下下载直接解压即可
sunny1009
2019/09/04
1.7K0
Centos7下nginx的安装与配置
单机部署 LNMP
``关闭SELinux # 临时关闭selinux setenforce 0 # 永久关闭selinux sed -i 's#SELINUX=enforcing#SELINUX=disabled#g' /etc/selinux/config 关闭防火墙 # 临时关闭防火墙: systemctl stop firewalld # 永久关闭防火墙: systemctl disable firewalld 1. 源码部署nginx 1.1 下载源码 # -c 是断点续传 wget -c http://
萌海无涯
2021/03/16
9100
Linux编译安装Python3.9——以CentOS7为例
在CentOS中使用yum安装python3时,总是安装不到需要的版本,在一些特定的环境下又需要特定版本的Python。然而网上的各种教程又良莠不齐(在此特别diss某某DN),故本篇将详细整理一遍在CentOS7上编译安装Python3的过程。
MoyiTech
2023/01/18
2.7K0
保姆级python项目离线部署服务器教程只需这一篇就够了(建议收藏)
服务器为linux的centos系统具体7还是8我不太清楚,全程为没有网络环境,所以环境全部需要离线安装.这里不我建议使用Anaconda虚拟环境进行安装,因为是离线环境.最好是能够准备一台有网络的centos虚拟机进行安装包的下载,方便环境一致这样项目部署后环境一般不会出现问题.
全干程序员demo
2024/04/13
1.7K0
保姆级python项目离线部署服务器教程只需这一篇就够了(建议收藏)
Centos7 安装Python3.6.
1、在安装Python之前,需要先安装一些后面遇到的依赖问题(如果有依赖问题,按照提示安装):
py3study
2020/01/08
9530
CentOS7 安装 Python 3.9.0[通俗易懂]
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
全栈程序员站长
2022/11/08
4.3K0
Python离线项目迁移部署
最近遇到了一个场景:需要将Python项目文件打包到无法联网的主机上部署执行,本篇文章记录针对于该场景的处理方案。
oYabea
2020/09/07
1.5K0
推荐阅读
相关推荐
CentOS 安装 GCC【在线 + 离线】
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验