美文网首页Android进阶笔记
干掉RxJava系列--1. 手写权限请求替代RxPermiss

干掉RxJava系列--1. 手写权限请求替代RxPermiss

作者: 今阳说 | 来源:发表于2021-12-29 19:06 被阅读0次

    起因

    • 最近在对公司项目做APK包体积优化, 其中少不了对一些不必要的三方库的移除,在此过程中发现项目中居然有一系列的Rx相关库,RxJava,RxBus,RxPermission,于是心中起了一丝杀意。
    • 当然RxJava还是相当强大的,基于事件流的链式调用,进行耗时任务,线程切换,是一个很好的异步操作库,毕竟我上一个系列文章才写过探索Android开源框架 - 3. RxJava使用及源码解析,如果想更深入的了解RxJava的话可以看一下,不过随着现在kotlin的普及,其协程和Flow基本也可以替代RxJava,所以准备尝试移除RxJava及其相关的一系列库,毕竟还是可以减少不小的包体积的。
    • 那么先拿RxPermission开刀吧。

    RxPermission的简单使用

    • 先来介绍一下RxPermission的使用吧,毕竟背靠RxJava这颗大树,它还是使得我们的运行时权限请求变得简洁了很多的;
    //1. build.gradle中添加依赖
    allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }
    
    dependencies {
        implementation 'com.github.tbruyelle:rxpermissions:0.12'
    }
    
    //注意:最新版rxpermission需要配合RxJava3使用
    implementation 'io.reactivex.rxjava3:rxjava:3.0.4'
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
    
    //2.调用
    //所有权限统一结果
    new RxPermissions(this)
        .request(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CALL_PHONE, Manifest.permission.CAMERA)
        .subscribe(accept -> {
            if (accept) {
                LjyLogUtil.d("允许了权限申请");
            } else {
                LjyLogUtil.d("拒绝了权限申请");
            }
        });
    
    //将权限申请结果逐一返回
    new RxPermissions(this)
        .requestEach(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CALL_PHONE, Manifest.permission.CAMERA)
        .subscribe(permission -> {
            // will emit 2 Permission objects
            LjyLogUtil.d(permission.toString());
            if (permission.granted) {
                // `permission.name` is granted !
                LjyLogUtil.d("允许了权限申请:" + permission.name);
            } else if (permission.shouldShowRequestPermissionRationale) {
                // Denied permission without ask never again
                LjyLogUtil.d("取消了权限申请:" + permission.name);
            } else {
                // Denied permission with ask never again
                // Need to go to the settings
                LjyLogUtil.d("权限被拒绝,将导致APP无法正常使用,请前往设置中修改:" + permission.name);
            }
        });
    
    • 如上,使用了RxPermissions就可以将权限请求和结果回调放到一个链式代码中了,而且支持 所有权限统一返回结果(request) 和 将权限申请结果逐一返回(requestEach),
    • 其主要原理是:新建RxPermissions类的时候,框架会悄悄的新建一个RxPermissionsFragment类,也就是说框架在内部封装了一个没有界面的fragment,这样做的好处是请求权限的回调可以在Fragment中实现,不需要用户再去调用onRequestPermissionsResult,再结合RxJava的各种操作符,使用起来就会非常方便。

    简单的封装

    • 之前也自己封装过一个权限请求的工具类,代码如下
    public class LjyPermissionUtil {
        private PermissionResultListener permissionResultListener;
    
        private LjyPermissionUtil() {
        }
    
        public static LjyPermissionUtil getInstance() {
            return PermissionUtilHolder.instancePermissionUtil;
        }
    
        private static class PermissionUtilHolder {
            private static final LjyPermissionUtil instancePermissionUtil = new LjyPermissionUtil();
        }
    
        /**
         * 判断当前应用是否有指定权限,运行时权限的检测
         */
        public boolean hasPermissions(Context context, String[] permissions) {
            if (permissions == null || permissions.length == 0) {
                return true;
            }
            boolean ifSdk = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
            for (String permission : permissions) {
                if (ifSdk && !hasPermission(context, permission)) {
                    return false;
                }
            }
            return true;
        }
    
        private boolean hasPermission(Context context, String permission) {
            return ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
        }
    
        /**
         * 动态申请指定权限,配合hasPermission使用,注意在使用的activity中调用onRequestPermissionsResult权限申请结果的回调
         *
         * @param activity
         * @param permissions
         * @param requestCode
         */
        public void requestPermission(final Activity activity, final String[] permissions, final int requestCode, final PermissionResultListener permissionResultListener) {
            this.permissionResultListener = permissionResultListener;
            ActivityCompat.requestPermissions(activity, permissions, requestCode);
        }
    
        /**
         * 申请权限的结果回调,需要在Activity的onRequestPermissionsResult中调用
         *
         * @param grantResults
         */
        public void onPermissionResult(Activity activity, final int requestCode, String[] permissions, int[] grantResults) {
            boolean hasPermission = true;
            List<String> deniedList = new ArrayList<>();
            List<String> cancelList = new ArrayList<>();
            for (int i = 0; i < grantResults.length; i++) {
                boolean isAllow = grantResults[i] == PackageManager.PERMISSION_GRANTED;
                hasPermission &= isAllow;
                if (!isAllow) {
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || !activity.shouldShowRequestPermissionRationale(permissions[i])) {
                        deniedList.add(permissions[i]);
                    } else {
                        cancelList.add(permissions[i]);
                    }
                }
            }
            if (permissionResultListener != null) {
                if (hasPermission) {
                    permissionResultListener.onSuccess(requestCode);
                } else {
                    if (deniedList.size() > 0) {
                        permissionResultListener.onDenied(deniedList);
                    }
                    if (cancelList.size() > 0) {
                        permissionResultListener.onCancel(cancelList);
                    }
                }
            }
    
        }
    
        /**
         * 权限申请结果的回调接口
         */
        public interface PermissionResultListener {
            /**
             * 申请成功
             */
            void onSuccess(final int requestCode);
    
            /**
             * 拒绝的权限集合(不在弹框提醒)
             */
            void onDenied(@NonNull List<String> deniedList);
    
            /**
             * 取消的权限集合
             */
            void onCancel(@NonNull List<String> cancelList);
        }
    }
    
    • 使用如下
    //baseActivity的onRequestPermissionsResult中
    open class BaseActivity : AppCompatActivity() {
        override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
            if (grantResults.isNotEmpty()) {
                LjyPermissionUtil.getInstance().onPermissionResult(this, requestCode, permissions, grantResults)
            }
        }
    }
    
    class PermissionActivity : BaseActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_permission)
    
        }
    
        fun onBtnClick(view: View) {
            when (view.id) {
                //原生获取运行时权限
                R.id.button_perm_1 -> requestPermission()
            }
        }
    
        private fun requestPermission() {
            val permissions = arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.CALL_PHONE, Manifest.permission.CAMERA)
            if (LjyPermissionUtil.getInstance().hasPermissions(this@PermissionActivity, permissions)) {
                printFileName()
            } else {
                val mRequestCode=1002
                LjyPermissionUtil.getInstance().requestPermission(
                        this@PermissionActivity,
                        permissions,
                        mRequestCode,
                        object : LjyPermissionUtil.PermissionResultListener {
                            override fun onSuccess(requestCode: Int) {
                                if (requestCode == mRequestCode) {
                                    printFileName()
                                }
                            }
    
                            override fun onDenied(deniedList: MutableList<String>) {
                                LjyToastUtil.toast(this@PermissionActivity, "权限被拒绝,将导致APP无法正常使用,请前往设置中修改")
                                for (it in deniedList) {
                                    LjyLogUtil.d("deniedList:$it")
                                }
                            }
    
                            override fun onCancel(cancelList: MutableList<String>) {
                                LjyToastUtil.toast(this@PermissionActivity, "取消了权限申请")
                                for (it in cancelList) {
                                    LjyLogUtil.d("failList:$it")
                                }
                            }
                        }
                )
            }
        }
    
        private fun printFileName() {
            LjyLogUtil.d("操作文件...")
        }
    }
    
    • 倒也勉强能用,但是不够优雅, 一个是入侵了基类BaseActivity,另外写出来的代码看起来也比较繁琐,那么就来参考RxPermission重写一下吧

    创建PermissionUtil

    • 这次我们用kotlin来实现,这样就可以借助其各种语法糖使得代码写起来更优雅一些,

    kotlin单例

    • 既然是工具类,那么先来搞个单例吧, 关于kotlin的一些特性和单例的实现方式,以后有时间会单独出一篇文章汇总一下;
    class PermissionUtil private constructor() : Serializable {//构造器私有化
    
        private fun readResolve(): Any {//防止单例对象在反序列化时重新生成对象
            return instance
        }
    
        companion object {
            @JvmStatic
            //使用lazy属性代理,并指定LazyThreadSafetyMode为SYNCHRONIZED模式保证线程安全
            val instance: PermissionUtil by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { PermissionUtil() }
        }
    }
    

    封装PermissionFragment

    • 创建一个请求结果的数据类
    data class Permission(
        var name: String?,
        var granted: Boolean,
        var shouldShowRequestPermissionRationale: Boolean
    )
    
    • 然后我们也来封装一个没有界面的fragment,用来实现真正的权限请求,避免用户再去调用onRequestPermissionsResult,也就不用像上面那样入侵BaseActivity了
    class PermissionFragment : Fragment() {
        private val PERMISSIONS_REQUEST_CODE = 520
        var permissions: Array<String>? = null
        var callBack: ((Boolean) -> Unit)? = null
    
        fun removeFragment() {
            val fragmentTransaction: FragmentTransaction = parentFragmentManager.beginTransaction()
            fragmentTransaction.remove(this).commit()
        }
    
        @TargetApi(Build.VERSION_CODES.M)
        fun requestPermissions(permissions: Array<String?>) {
            requestPermissions(
                permissions,
                PERMISSIONS_REQUEST_CODE
            )
        }
    
        @TargetApi(Build.VERSION_CODES.M)
        override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<out String>,
            grantResults: IntArray
        ) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }
    }
    

    使用registerForActivityResult

    • 如上代码,我们发现requestPermissions和onRequestPermissionsResult已经过时了,点进去看看,提示要有registerForActivityResult替换,代码如下
    class PermissionFragment : Fragment() {
        private lateinit var requestMultiplePermissionsLauncher:
                ActivityResultLauncher<Array<String>>
        var permissions: Array<String>? = null
        var callBack: ((Boolean) -> Unit)? = null
    
        fun removeFragment() {
            val fragmentTransaction: FragmentTransaction = parentFragmentManager.beginTransaction()
            fragmentTransaction.remove(this).commit()
        }
    
    
        override fun onAttach(context: Context) {
            super.onAttach(context)
            requestMultiplePermissionsLauncher =
                registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { it ->
                    //通过的权限
                    val grantedList = it.filterValues { it }.mapNotNull { it.key }
                    //是否所有权限都通过
                    val allGranted = grantedList.size == it.size
                    val list = (it - grantedList.toSet()).map { it.key }
                    //未通过的权限
                    val deniedList =
                        list.filter {
                            ActivityCompat.shouldShowRequestPermissionRationale(
                                requireActivity(),
                                it
                            )
                        }
                    //拒绝并且点了“不再询问”权限
                    val alwaysDeniedList = list - deniedList.toSet()
                    callBack?.invoke(allGranted)
                    removeFragment()
                }
            if (permissions?.isNotEmpty() == true)
                requestMultiplePermissionsLauncher.launch(permissions)
        }
    }
    

    使用Flow

    • 上面基本已经实现了权限请求,不过还可以使用kotlin的flow来进一步优化一下
    class PermissionFragment : Fragment() {
        private lateinit var requestMultiplePermissionsLauncher:
                ActivityResultLauncher<Array<String>>
        var permissions: Array<String>? = null
        var accept: ((Boolean) -> Unit)? = null
        var permissionResult: ((Permission) -> Unit)? = null
        var denied: ((String) -> Unit)? = null
        var alwaysDenied: ((String) -> Unit)? = null
    
        fun removeFragment() {
            val fragmentTransaction: FragmentTransaction = parentFragmentManager.beginTransaction()
            fragmentTransaction.remove(this).commit()
        }
    
        override fun onAttach(context: Context) {
            super.onAttach(context)
            requestMultiplePermissionsLauncher =
                registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { it ->
    
                    lifecycleScope.launch {
                        //是否所有权限都通过
                        val allGranted = it.iterator()
                            .asFlow()
                            .flowOn(Dispatchers.Main)
                            .map { it.value }
                            .reduce { a, b ->
                                a && b
                            }
                        accept?.invoke(allGranted)
                        it.iterator()
                            .asFlow()
                            .flowOn(Dispatchers.Main)
                            .onEach { entry ->
                                log("所有权限:" + entry.key)
                                permissionResult?.invoke(
                                    Permission(
                                        entry.key,
                                        entry.value,
                                        ActivityCompat.shouldShowRequestPermissionRationale(
                                            requireActivity(),
                                            entry.key
                                        )
                                    )
                                )
                            }
                            .filter { !it.value }
                            .onEach { entry ->
                                log("拒绝的权限:" + entry.key)
                                denied?.invoke(entry.key)
                            }
                            .filter {
                                !ActivityCompat.shouldShowRequestPermissionRationale(
                                    requireActivity(),
                                    it.key
                                )
                            }
                            .onEach { entry ->
                                log("拒绝并且点了“不再询问”的权限:" + entry.key)
                                alwaysDenied?.invoke(entry.key)
                            }
                            .collect()
                        if (isAdded) {
                            removeFragment()
                        }
                    }
    
                }
            if (permissions?.isNotEmpty() == true)
                requestMultiplePermissionsLauncher.launch(permissions)
        }
    }
    
    

    实现PermissionUtil

    • 已经有了PermissionFragment,接下来就是在PermissionUtil中使用了
    class PermissionUtil private constructor() : Serializable {
    
        private fun readResolve(): Any {
            return instance
        }
    
        companion object {
            @JvmStatic
            val instance: PermissionUtil by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { PermissionUtil() }
        }
    
       val permissionFragment: PermissionFragment = PermissionFragment()
    
       /**
        * 所有权限统一返回结果
        */
       fun permissionsRequest(
           activity: FragmentActivity,
           permissions: Array<String>,
           accept: (allGranted: Boolean) -> Unit
       ) {
           permissionFragment.permissions = permissions
           permissionFragment.accept = accept
           val fragmentTransaction = activity.supportFragmentManager.beginTransaction()
           fragmentTransaction.add(permissionFragment, "permissionFragment@LJY").commit()
       }
    
       /**
        * 所有权限统一返回结果 && getTopActivity
        */
       fun permissionsRequest(permissions: Array<String>, accept: (allGranted: Boolean) -> Unit) {
           val context: Activity = ApplicationUtil.instance.getTopActivity()
               ?: throw java.lang.NullPointerException("Top Activity is Null!")
           permissionsRequest(context as FragmentActivity, permissions, accept)
       }
    
       /**
        * 将权限申请结果逐一返回
        */
       fun permissionsRequestEach(
           activity: FragmentActivity,
           permissions: Array<String>,
           permissionResult: (Permission) -> Unit
       ) {
           permissionFragment.permissions = permissions
           permissionFragment.permissionResult = permissionResult
           val fragmentTransaction = activity.supportFragmentManager.beginTransaction()
           fragmentTransaction.add(permissionFragment, "permissionFragment@LJY").commit()
       }
    
       /**
        * 将权限申请结果逐一返回 && getTopActivity
        */
       fun permissionsRequestEach(permissions: Array<String>, permissionResult: (Permission) -> Unit) {
           val context: Activity = ApplicationUtil.instance.getTopActivity()
               ?: throw java.lang.NullPointerException("Top Activity is Null!")
           permissionsRequestEach(context as FragmentActivity, permissions, permissionResult)
       }
    }
    

    使用 PermissionUtil

    //所有权限统一返回结果
    permissionsRequest(
        arrayOf(
            Manifest.permission.CAMERA,
            Manifest.permission.SEND_SMS,
            Manifest.permission.CALL_PHONE,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
        )
    ){
        if (it) {
            todoSomething()
        }else {
            Toast.makeText(this, "我们需要相应的权限,请允许权限申请", Toast.LENGTH_LONG).show()
        }
    }
    
    //将权限申请结果逐一返回
    permissionsRequestEach(
        arrayOf(
            Manifest.permission.CAMERA,
            Manifest.permission.SEND_SMS,
            Manifest.permission.CALL_PHONE,
            Manifest.permission.READ_EXTERNAL_STORAGE,
            Manifest.permission.WRITE_EXTERNAL_STORAGE,
        )
    ){
        when {
            it.granted -> {
                // `permission.name` is granted !
                log("Activity.允许了权限申请:" + it.name);
            }
            it.shouldShowRequestPermissionRationale -> {
                // Denied permission without ask never again
                log("Activity.取消了权限申请:" + it.name);
            }
            else -> {
                // Denied permission with ask never again
                // Need to go to the settings
                log("Activity.拒绝并且点了“不再询问”的权限:" + it.name);
            }
        }
    }
    
    • 到此简单的权限请求就封装好了,不过如果用到线上的话还是需要进一步完善的;

    我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 “今阳说” 接收我的最新文章

    相关文章

      网友评论

        本文标题:干掉RxJava系列--1. 手写权限请求替代RxPermiss

        本文链接:https://www.haomeiwen.com/subject/nrqbqrtx.html