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

Posted 今阳说点啥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了干掉RxJava系列--1. 手写权限请求替代RxPermission相关的知识,希望对你有一定的参考价值。

起因

  • 最近在对公司项目做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. 手写权限请求替代RxPermission的主要内容,如果未能解决你的问题,请参考以下文章

手写Rxjava了解核心实现原理

Rx系列---响应式编程

框架手写系列---RxJava之从零开始

框架手写系列---RxJava之从零开始

RxJava系列1(简介)

RxJava系列1(简介)