少年击剑更吹箫,剑气箫心一例消。这篇文章主要讲述干掉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 ... mavenurl https://jitpack.io
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 instancecompanion 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.filterValuesit .mapNotNullit.key
//是否所有权限都通过
val allGranted = grantedList.size == it.size
val list = (it - grantedList.toSet()).mapit.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)
.mapit.value
.reducea, b ->
a &
&
baccept?.invoke(allGranted)
it.iterator()
.asFlow()
.flowOn(Dispatchers.Main)
.onEachentry ->
log("所有权限:" + entry.key)
permissionResult?.invoke(
Permission(
entry.key,
entry.value,
ActivityCompat.shouldShowRequestPermissionRationale(
requireActivity(),
entry.key
)
)
).filter!it.value
.onEachentry ->
log("拒绝的权限:" + entry.key)
denied?.invoke(entry.key).filter
!ActivityCompat.shouldShowRequestPermissionRationale(
requireActivity(),
it.key
).onEachentry ->
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 instancecompanion 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);
- 到此简单的权限请求就封装好了,不过如果用到线上的话还是需要进一步完善的;### 我是今阳,如果想要进阶和了解更多的干货,欢迎关注微信公众号 “今阳说” 接收我的最新文章
推荐阅读
- #yyds干货盘点#HCIE-Security Day8(3个实验理解双向NAT)
- #yyds干货盘点# HCIE-Security Day10(6个实验理解VRRP与可靠性)
- win7系统windows 电脑公司Win7系统windows技巧显示空白的应对办法
- win7系统运用记录 迅速清除番茄花园win7系统运用记录的办法
- win7系统怎样设置主题 让大地Win7切换主题时定时提醒的攻略
- win7系统玩游戏延迟 深度技术Win7降低WOW游戏延迟优化的办法
- win7系统的剪贴板怎样打开 打开Win7笔记本剪贴板的办法
- win7系统检测 关闭系统之家win7系统交互式服务检测弹窗的办法
- win7系统缓存文件夹 技术员联盟Win7巧妙清除arp缓存文件的办法