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

1,989 阅读6分钟

起因

  • 最近在对公司项目做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);
        }
    }
}
  • 到此简单的权限请求就封装好了,不过如果用到线上的话还是需要进一步完善的;

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