精华内容
下载资源
问答
  • PermissionX 中文文档 PermissionX is an extension Android library that makes Android runtime permission request extremely easy. You can use it for basic permission request occasions or handle more ...
  • PermissionX 运行时权限

    2020-10-30 14:02:23
    PermissionX 借鉴郭霖大神打博客............ 郭霖博客 PermissionX github

    PermissionX  借鉴郭霖大神打博客............

    郭霖博客

    PermissionX github

     

    展开全文
  • 全书都看完的朋友一定知道,《第三行代码》的最后一章是带着大家一起开发了一个开源库:PermissionX。这一章的主旨是为了让你了解一个开源库整体的开发与发布过程,为了更好地演示这个过程,我想到了去写PermissionX...
  • PermissionX重磅更新,支持自定义权限提醒对话框

    万次阅读 多人点赞 2020-07-21 07:14:34
    截至目前为止,PermissionX已经迭代更新了三个版本,而最新的1.3.0版本更是加入了非常重要的自定义权限提醒对话框的功能。如果你觉得之前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 {
    
    
        /**
         * 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()方法即可,代码如下所示:

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

    一切搞定!

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

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

    <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:

    <?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开源库地址:https://github.com/guolindev/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版》点击此处查看详情


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

    微信扫一扫下方二维码即可关注:

    展开全文
  • PermissionX现在支持Java了!还有Android 11权限变更讲解

    万次阅读 多人点赞 2020-06-16 08:05:32
    各位小伙伴们早上好,不知道你们有没有惊讶于我的速度,因为不久之前我才新发布的开源库PermissionX今天又更新了。 是的,在不到一个月的时间里,PermissionX又迎来了一次重大的版本更新。如果你觉得一个月还不算快...

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

    各位小伙伴们早上好,不知道你们有没有惊讶于我的速度,因为不久之前我才新发布的开源库PermissionX今天又更新了。

    是的,在不到一个月的时间里,PermissionX又迎来了一次重大的版本更新。如果你觉得一个月还不算快的话,可别忘了,两周之前我还发布了LitePal的新版本。对于我来说,这个速度已经是相当极限了。

    不过,可能还有不少朋友不知道PermissionX是什么,这里我给出上一篇文章的链接,还没看过的小伙伴先去补补课 Android运行时权限终极方案,用PermissionX吧

    本来按照迭代计划,下一个版本中,我是准备给PermissionX增加自定义权限提示对话框样式的功能。然而随着第一个版本的发布,根据大家的反馈,我意识到了另一个更加紧急的需求,就是对Java语言的支持。

    真的很遗憾看到,即使在今天,Kotlin在国内仍然还只是少部分开发者群体使用的语言,然而这就是现实。因此,如果PermissionX只支持Kotlin语言的话,势必将大部分的开发者都拒之了门外。

    其实最初我让PermissionX只支持Kotlin语言,是因为我实在不想同时维护两个版本,这样修改任何功能都需要在两个地方各改一遍,维护成本过高。

    然而后面我又做了一些更全面的思考,发现只需要稍微付出一点点语法方面的代价,就可以让一份代码同时支持Java和Kotlin两种语言,那么本篇文章我们就来学习一下是如何实现的。


    兼容Java和Kotlin

    首先我们来回顾一下PermissionX的基本用法,这段代码在上一篇文章中我们是见过的:

    PermissionX.init(this)
        .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE)
        .onExplainRequestReason { deniedList ->
            showRequestReasonDialog(deniedList, "即将申请的权限是程序必须依赖的权限", "我已明白")
        }
        .onForwardToSettings { deniedList ->
            showForwardToSettingsDialog(deniedList, "您需要去应用程序设置当中手动开启权限", "我已明白")
        }
        .request { allGranted, grantedList, deniedList ->
            if (allGranted) {
                Toast.makeText(this, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show()
            }
        }
    

    是的,在Android程序的权限申请变得如此简单。

    首先调用init()方法进行初始化,调用permissions()方法来指定要申请哪些权限,在onExplainRequestReason()方法中针对那些被拒绝的权限向用户解释申请的原因并重新申请,在onForwardToSettings()方法中针对那些被永久拒绝的权限向用户解释为什么它们是必须的,并自动跳转到应用设置当中提醒用户手动开启权限。最后调用request()方法开始请求权限,并接收申请的结果。

    整段用法简洁明了,而且PermissionX帮助开发者解决了权限申请过程中最痛苦的一些逻辑处理,比如权限被拒绝了怎么办?权限被永久拒绝了怎么办?

    那么之所以能将PermissionX的用法设计得这么简单明了,主要得感谢Kotlin的高阶函数功能。上述代码示例当中的onExplainRequestReason()方法、onForwardToSettings()方法、request()方法,实际上都是高阶函数。对于高阶函数中接收的函数类型参数,我们可以直接传入一个Lambda表达式,然后在Lambda表达式当中处理回调逻辑即可。

    然而问题也就出现在了这里,由于Java是没有高阶函数这个概念的,因此这种便捷性的语法在Java语言当中并不适用,所以也就导致了PermissionX不支持Java的情况。

    不过,这个问题是可以解决的!

    事实上,在Kotlin语言当中,我们除了可以向高阶函数传递Lambda表达式,还可以向另一种SAM函数传递Lambda表达式。SAM的全称是Single Abstract Method,又叫做单抽象方法。具体来讲,如果Java中定义的某个接口,里面只有一个待实现方法(也就是所谓的单抽象方法),那么此时我们也可以向其传递Lambda表达式。

    举一个具体的例子,所有Android开发者一定都调用过setOnClickListener()方法,这个方法可以用于给一个控件注册点击事件。

    在Java当中我们会这样写:

    button.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            
        }
    });
    

    这段代码因为所有人都实在是太熟悉了,因此没什么解释的必要。

    但是可以看到,在setOnClickListener()方法中,我们创建了一个View.OnClickListener的匿名类,那么View.OnClickListener的代码是什么样的呢?点击查看其源码,如下所示:

    public class View implements Callback, android.view.KeyEvent.Callback, AccessibilityEventSource {
    
        public interface OnClickListener {
            void onClick(View view);
        }

    可以看到,OnClickListener是一个接口,并且这个接口当中只有一个onClick()方法,因此这就是一个单抽象方法接口。

    那么根据上面的规则,Kotlin允许我们向一个接收单抽象方法接口的函数传递Lambda表达式。因此,在Kotlin当中,我们给一个按钮注册点击事件通常都是这么写的:

    button.setOnClickListener {
    
    }
    

    看到这里,有没有受到点启发呢?反正我是受到了。也就是说,如果PermissionX想要同时兼容Java和Kotlin语言的话,可以很好地利用单抽象方法接口这个特性。将原本的高阶函数都改成这种SAM函数,那么不就自然可以兼容两种语言了吗?

    没错,我也确实是这样做的,不过具体在实现的过程中还是遇到了一点问题。

    因为高阶函数的功能是十分强大的,我们除了可以定义一个函数类型的参数列表以及它的返回值,还可以定义它的所属类。来看PermissionX中的一段示例代码:

    fun onExplainRequestReason(callback: ExplainScope.(deniedList: MutableList<String>) -> Unit): PermissionBuilder {
    	explainReasonCallback = callback
    	return this
    }
    

    以上代码对于没接触过Kotlin的朋友来说,可能会像天书一样难以理解,然而如果你学过Kotlin的话,就知道这只是定义了一个简单的函数类型参数。是的,这里我又要推荐我写的新书《第一行代码 第3版》了,还没有阅读过的朋友可以认真考虑一下,能在很大程序上帮助你轻松上手Kotlin语言。

    那么上述代码中,我们将函数类型的所属类定义在了ExplainScope当中,这意味着什么?意味着,在Lambda表达式当中,我们就自动拥有了ExplainScope的上下文,因此可以直接调用ExplainScope类中的任何方法。所以,你也已经猜到了,本篇文章第一段示例代码中调用的showRequestReasonDialog()方法就是定义在ExplainScope类当中的。

    然而Kotlin中这个非常棒的特性,很遗憾,在Java当中也没有,而且即使通过SAM函数也无法实现。

    所以,这里我不得不付出一点语法特性的代价,将Kotlin这种定义所属类上下文的特性改成了传递参数的方式。也因为这个原因,新版PermissionX的语法无法做到和上一个版本百分百兼容,而是要稍微做出一点点修改。

    那么新版的PermissionX中实现和刚才同样的功能需要这样写:

    PermissionX.init(this)
        .permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE)
        .onExplainRequestReason { scope, deniedList ->
            scope.showRequestReasonDialog(deniedList, "即将申请的权限是程序必须依赖的权限", "我已明白")
        }
        .onForwardToSettings { scope, deniedList ->
            scope.showForwardToSettingsDialog(deniedList, "您需要去应用程序设置当中手动开启权限", "我已明白")
        }
        .request { allGranted, grantedList, deniedList ->
            if (allGranted) {
                Toast.makeText(this, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this, "您拒绝了如下权限:$deniedList", Toast.LENGTH_SHORT).show()
            }
        }
    

    可以看到,只是在onExplainRequestReason()和onForwardToSettings()方法的Lambda表达式参数列表中增加了一个scope参数,然后调用解释权限申请原因对话框的时候,前面也要加上scope对象,仅此一点点变化,其他用法部分和之前是完全一模一样的。

    而Kotlin在用法层面做出这一点点的牺牲,带来的却是Java语言的全面支持,使用Java实现同样的功能只需要这样写:

    PermissionX.init(this)
    	.permissions(Manifest.permission.CAMERA, Manifest.permission.READ_CONTACTS, Manifest.permission.CALL_PHONE)
    	.onExplainRequestReason(new ExplainReasonCallbackWithBeforeParam() {
    		@Override
    		public void onExplainReason(ExplainScope scope, List<String> deniedList, boolean beforeRequest) {
    			scope.showRequestReasonDialog(deniedList, "即将申请的权限是程序必须依赖的权限", "我已明白");
    		}
    	})
    	.onForwardToSettings(new ForwardToSettingsCallback() {
    		@Override
    		public void onForwardToSettings(ForwardScope scope, List<String> deniedList) {
    			scope.showForwardToSettingsDialog(deniedList, "您需要去应用程序设置当中手动开启权限", "我已明白");
    		}
    	})
    	.request(new RequestCallback() {
    		@Override
    		public void onResult(boolean allGranted, List<String> grantedList, List<String> deniedList) {
    			if (allGranted) {
    				Toast.makeText(MainActivity.this, "所有申请的权限都已通过", Toast.LENGTH_SHORT).show();
    			} else {
    				Toast.makeText(MainActivity.this, "您拒绝了如下权限:" + deniedList, Toast.LENGTH_SHORT).show();
    			}
    		}
    	});
    

    单纯从两种语言上来对比,Kotlin版的代码肯定是要远比Java版的更简洁,但是很多朋友或许就是更加习惯Java的这种语法结构吧,看起来可能也更加亲切一些。


    支持Android 11

    目前Android 11的Beta版本已在上周四正式发布了,我这次也算是走在了时代的前沿,第一时间研究了Android 11中的各种新特性。

    其中,权限相关的部分有了较大的变化,不过大家也不用担心,需要我们开发者进行适配的地方并不多,只是你应该了解这些变化。

    首先,那个让无数开发者极其讨厌的“拒绝并不再询问”选项没有了。但是别高兴的太早,Android 11只是将它换成了另外一种展现形式。假如应用程序申请的某个权限被用户拒绝了两次,那么Android系统会自动将其视为“拒绝并不再询问”来处理。

    另外权限申请对话框现在允许取消了,如果用户取消了权限对话框,将会视为一次拒绝。

    Android 11中还引入了权限过期的机制,本来用户授予了应用程序某个权限,该权限会一直有效,现在如果某应用程序很长时间没有启动,Android系统会自动收回用户授予的权限,下次启动需要重新请求授权。

    另外,Android 11针对摄像机、麦克风、地理定位这3种权限提供了单次授权的选项。因为这3种权限都是属于隐私敏感的权限,如果像过去一样用户同意一次就代表永久授权,可能某些恶意应用会无节制地采集用户信息。在Android 11中请求摄像机权限,界面如下图所示。

    可以看到,图中多了一个“仅限这一次”的选项。如果用户选择了这个选项,那么在整个应用程序的生命周期内,我们都是可以获取到摄像机数据的。但是当下次启动程序时,则需要再次请求权限。

    以上部分就是Android 11中权限相关的主要变化,你会发现,这些变化其实并没有影响到我们的代码编写,也不用做什么额外的适配,所以只需要了解一下就行了。

    不过接下来的部分,就是我们需要进行适配的地方了。

    Android 10系统首次引入了android:foregroundServiceType属性,如果你想要在前台Service中获取用户的位置信息,那么必须在AndroidManifest.xml中进行以下配置声明:

    <manifest>
        ...
        <service ... 
    		android:foregroundServiceType="location" />
    </manifest>
    

    而在Android 11系统中,这个要求扩展到了摄像机和麦克风权限。也就是说,如果你想要在前台Service中获取设备的摄像机和麦克风数据,那么也需要在AndroidManifest.xml中进行声明:

    <manifest>
        ...
        <service ...
    		android:foregroundServiceType="location|camera|microphone" />
    </manifest>
    

    接下来再来看另外一个需要适配的地方。

    Android 10系统中引入了一个新的权限:ACCESS_BACKGROUND_LOCATION,用于允许应用程序在后台请求设备的位置信息。不过这个权限是不可以单独申请的,而是要和ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION一起申请才行。这个也很好理解,怎么可能连前台请求位置信息都没同意呢,就允许在后台请求位置信息了。

    在Android 10系统中,如果我们同时申请前台和后台定位权限,那么将会出现如下界面:

    可以看到,界面上的选项有些不同,“始终允许”表示同时允许了前台和后台定位权限,“仅在使用此应用时允许”表示只允许前台定位权限,“拒绝”表示都不允许。

    但是如果我们在Android 11系统中同时申请前台和后台定位权限会怎么样呢?很遗憾地告诉你,会崩溃。

    因为Android 11系统要求,ACCESS_BACKGROUND_LOCATION权限必须单独申请,并且在那之前,应用程序还必须已经获得了ACCESS_FINE_LOCATION或ACCESS_COARSE_LOCATION权限才行。

    这个规则其实PermissionX是可以不用考虑的,如果开发者在Android 11中同时申请前台和后台定位权限 ,那么就让系统直接抛出异常也是合理的,因为这种请求方式违反了Android 11的规则。

    然而为了让开发者更方便地使用PermissionX,减少这种差异化编程的的场景,我还是决定对Android 11的这个新规则进行适配。

    具体思路也是比较简单的,如果应用程序同时申请了前台和后台定位权限,那么就先忽略后台定位权限,只申请前台定位以及其他权限,等所有权限都申请完毕后再单独去申请后台定位权限。

    看上去很简单是不是?可是当我具体去实现的时候差点没把我累死,同时也暴露出了PermissionX的扩展性设计得非常糟糕的问题。

    其实本来我一直觉得PermissionX的代码写得非常出色,还鼓励大家去阅读源码,然而这次为了兼容Android 11我才发现,有多个地方的耦合性太高,牵一发而动全身,导致难以扩展功能。

    PermissionX中有很多可以注册回调监听的地方,权限被拒绝时有回调,权限被永久拒绝时有回调,权限申请结束时有回调。而在代码逻辑中去通知这些回调的地方就更多了,传入一个空权限列表是不会进行权限请求的,直接回调结束。传入的权限列表如果全部都已经授权了,也会直接回调结束。还有点击解释权限申请原因对话框上的取消按钮,也要终止后续的权限请求。

    以上还只是处理了一些边界情况,都不是正式的权限请求流程,正式请求之后的回调逻辑就更多了。

    那么如此复杂的回调逻辑带来了一个什么问题?我很难找到一个切入点去判断除了后台定位权限之外的其他权限都处理完了(那么多的回调点都需要处理),然后再单独去申请后台定位权限。另外,后台定位权限还要复用之前的逻辑,这样每个回调的地方我都要知道当前是在请求非后台定位权限,还是后台定位权限(否则将无法知道接下来应该是去请求后台定位权限,还是结束请求回调给开发者)。

    我大概尝试了两种不同的if else设计思路来实现兼容Android 11系统的功能,最终都失败了。写到后面逻辑越来越复杂,改了这个bug出现那个bug,实在无法继续。

    最终我决定将PermissionX的整体架构全部推翻重来。这是一个不容易的决定,但是既然已经知道PermissionX的扩展性设计得非常糟糕,早晚我都是要解决这个问题的。

    新版PermissionX的整体架构改成了链式任务的执行模式,根据不同的权限类型将请求分成两种任务,权限的请求以及结果的回调都是封装在任务当中的。当一个任务执行结束之后会判断是否还有下一个任务要执行,如果有的话就执行下一个任务,没有的话就回调结束。示意图如下所示:

    部分链式任务的实现代码如下:

    /**
     * Maintain the task chain of permission request process.
     * @author guolin
     * @since 2020/6/10
     */
    public class RequestChain {
    
        /**
         * Holds the first task of request process. Permissions request begins here.
         */
        private BaseTask headTask;
    
        /**
         * Holds the last task of request process. Permissions request ends here.
         */
        private BaseTask tailTask;
    
        /**
         * Add a task into task chain.
         * @param task  task to add.
         */
        public void addTaskToChain(BaseTask task) {
            if (headTask == null) {
                headTask = task;
            }
            // add task to the tail
            if (tailTask != null) {
                tailTask.next = task;
            }
            tailTask = task;
        }
    
        /**
         * Run this task chain from the first task.
         */
        public void runTask() {
            headTask.request();
        }
    
    }
    

    这里我使用了链表这种数据结构来实现,每当新增一个任务的时候,就将它添加到链表的尾部。执行任务的时候则从第一个任务开始执行,然后依次向后,直到所有任务执行结束才回调给开发者。

    然后在请求权限的request()方法中,我构建了这样一条任务链:

    /**
     * Request permissions at once, and handle request result in the callback.
     *
     * @param callback Callback with 3 params. allGranted, grantedList, deniedList.
     */
    public void request(RequestCallback callback) {
        requestCallback = callback;
        // Build the request chain.
        // RequestNormalPermissions runs first.
        // Then RequestBackgroundLocationPermission runs.
        RequestChain requestChain = new RequestChain();
        requestChain.addTaskToChain(new RequestNormalPermissions(this));
        requestChain.addTaskToChain(new RequestBackgroundLocationPermission(this));
        requestChain.runTask();
    }
    

    可以看到,这里先是创建了RequestChain的实例,然后向链表中添加一个RequestNormalPermissions任务用于请求普通的权限,又添加了一个RequestBackgroundLocationPermission任务用于请求后台定位权限,接着调用runTask()方法就可以从链表头部依次向后执行任务了。

    现在,当你使用PermissionX来进行权限处理,可以完全不用理会Android 11上的权限机制差异,所有判断逻辑PermissionX都会在内部帮你处理好。假如你同时请求了前台和后台定位权限,在Android 10系统中会将它们一起申请,在Android 11系统中会将它们分开申请,在Android 9或以下系统,则不会去申请后台定位权限,因为那个时候还没有这个权限。

    另外,使用这种链式任务的执行模式之后,PermissionX未来的扩展性会变得非常好。因为除了上述我们讨论的权限之外,Android系统还有一些更加特殊的权限,比如悬浮窗权限。这种权限是不可以调用代码来进行申请的,而是要跳转到一个专门的设置界面,提醒用户手动开启。而现在的PermissionX,想要支持这种权限,其实只需要再添加一个新的任务就行了。当然,这个功能是相对比较靠后的版本计划,下一个版本的重点还是自定义权限提示对话框样式的功能。


    如何升级

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

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

    尤其是还在使用Java语言的开发者们,这次的版本更新是非常值得一试的。

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

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

    最后附上PermissionX的开源库地址,欢迎大家star和fork。

    https://github.com/guolindev/PermissionX

    本篇文章的内容还是比较充实的,既讲了PermissionX的新版用法,又讲了一些Kotlin的知识,还讲了Android 11的权限变更,当然最后还有新版PermissionX的架构设计思路,希望大家都有学到一些知识吧。

    下个版本中,PermissionX将会加入自定义权限提醒对话框的功能,详情请参阅 PermissionX重磅更新,支持自定义权限提醒对话框


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


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

    微信扫一扫下方二维码即可关注:

    展开全文
  • 最近我看到郭霖大佬又出了个框架~~PermissionX,大家知道,Android运行时权限的代码时真的太繁琐了。PermissionX时一个真正用于简化Android运行时权限处理的库,这个是真的方便 github地址:...

    最近我看到郭霖大佬又出了个框架~~PermissionX,大家知道,Android运行时权限的代码时真的太繁琐了。PermissionX时一个真正用于简化Android运行时权限处理的库,这个是真的方便
    github地址:https://github.com/guolindev/PermissionX
    首先引入

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

    AndroidManifest.xml

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.permissionx.app">
    	<uses-permission android:name="android.permission.READ_CONTACTS" />
    	<uses-permission android:name="android.permission.CAMERA" />
    	<uses-permission android:name="android.permission.CALL_PHONE" />
    </manifest>
    

    然后使用以下代码就行

    PermissionX.init(activity)
        .permissions(Manifest.permission.READ_CONTACTS, Manifest.permission.CAMERA, Manifest.permission.CALL_PHONE)
        .request { allGranted, grantedList, deniedList ->
            if (allGranted) {
                Toast.makeText(this, "All permissions are granted", Toast.LENGTH_LONG).show()
            } else {
                Toast.makeText(this, "These permissions are denied: $deniedList", Toast.LENGTH_LONG).show()
            }
        }
    

    假如用户拒绝了某个权限,在下次申请之前,我们最好弹出一个对话框来向用户解释申请这个权限的原因

    PermissionX.init(activity)
        .permissions(Manifest.permission.READ_CONTACTS, Manifest.permission.CAMERA, Manifest.permission.CALL_PHONE)
        .onExplainRequestReason { scope, deniedList ->
            scope.showRequestReasonDialog(deniedList, "Core fundamental are based on these permissions", "OK", "Cancel")
        }
        .request { allGranted, grantedList, deniedList ->
            if (allGranted) {
                Toast.makeText(this, "All permissions are granted", Toast.LENGTH_LONG).show()
            } else {
                Toast.makeText(this, "These permissions are denied: $deniedList", Toast.LENGTH_LONG).show()
            }
        }
    
    展开全文
  • <div><p>我添加权限Manifest.permission.SYSTEM_ALERT_WINDOW,允许访问权限后走的是onForwardToSettings()方法,不是走request()方法,如果不添加Manifest....guolindev/PermissionX</p></div>
  • PermissionX开源框架的使用 背景 动态申请权限是一件特别繁琐的事情,涉及的权限一多,就让人头疼。Android提供给我们的运行时权限申请的API并不是很友好。 比如我们需要动态申请打电话的功能。除了在roidManifest....
  • PermissionX是Android库的扩展,它使Android运行时权限请求变得非常容易。 您可以将其用于基本权限请求场合,也可以处理更复杂的条件,例如显示“基本原理”对话框或手动转到应用程序设置以获取津贴。 快速设置 编辑...
  • PermissionX-源码

    2021-03-21 02:15:50
    权限X
  • PermissionX是Android库的扩展,它使Android运行时权限请求变得非常容易。 您可以将其用于基本权限请求场合,也可以处理更复杂的条件,例如显示“基本原理”对话框或手动转到应用设置以获取津贴。 快速设置 编辑您的...
  • 各位小伙伴们早上好,不知道你们有没有惊讶于我的速度,因为不久之前我才新发布的开源库PermissionX今天又更新了。是的,在不到一个月的时间里,PermissionX又迎来了一次重大的...
  • <div><p>因为PermissionX依赖特定的kotlin版本,可能出现引入的PermissionX所依赖的kotlin版本和当前工程已经在用的kotlin版本不一致,导致编译失败,出现 <strong>Cause: duplicate entry: ...
  • 先使用了PermissionX进行请求可以读写通讯录的操作权限 <pre><code>java PermissionUtil.checkContacts(activity,(allGranted, grantedList, deniedList) -> { if (allGranted){ Log.i(TAG,"权限通过")...
  • PermissionX:一种-源码

    2021-03-19 16:43:44
    权限X 一种
  • Android运行时权限终极方案,用PermissionX

    万次阅读 多人点赞 2020-05-19 08:03:13
    所以,后期我又对PermissionX进行了诸多功能拓展,现在已经达到对外发布的标准了,那么今天正式向大家宣布:PermissionX已经上线! 开源库的地址是:https://github.com/guolindev/PermissionX 痛点在哪里? 没有人...
  • <div><p>java.lang.NullPointerException: Attempt to read from field 'java.util.Set com.permissionx.guolindev.request.PermissionBuilder.grantedPermissions'...guolindev/PermissionX</p></div>
  • <div><p>很感谢郭神写的库。 上面引起的原因是因为使用Kotlin 会自动创建META-INF/library_release.kotlin_module 文件。可能会和其他库出现冲突。有的解决方案是 ...guolindev/PermissionX</p></div>

空空如也

空空如也

1 2 3 4 5 6
收藏数 107
精华内容 42
关键字:

permissionX