草庐IT

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

今阳说点啥 2023-03-28 原文

起因

  • 最近在对公司项目做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的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby - 在 jRuby 中使用 'fork' 生成进程的替代方案? - 2

    在MRIRuby中我可以这样做:deftransferinternal_server=self.init_serverpid=forkdointernal_server.runend#Maketheserverprocessrunindependently.Process.detach(pid)internal_client=self.init_client#Dootherstuffwithconnectingtointernal_server...internal_client.post('somedata')ensure#KillserverProcess.kill('KILL',

  3. ruby-on-rails - 使用一系列等级计算字母等级 - 2

    这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,

  4. ruby-on-rails - Rails HTML 请求渲染 JSON - 2

    在我的Controller中,我通过以下方式在我的index方法中支持HTML和JSON:respond_todo|format|format.htmlformat.json{renderjson:@user}end在浏览器中拉起它时,它会自然地以HTML呈现。但是,当我对/user资源进行内容类型为application/json的curl调用时(因为它是索引方法),我仍然将HTML作为响应。如何获取JSON作为响应?我还需要说明什么? 最佳答案 您应该将.json附加到请求的url,提供的格式在routes.rb的路径中定义。这

  5. ruby-on-rails - 更好的替代方法 try( :output). try( :data). try( :name)? - 2

    “输出”是一个序列化的OpenStruct。定义标题try(:output).try(:data).try(:title)结束什么会更好?:) 最佳答案 或者只是这样:deftitleoutput.data.titlerescuenilend 关于ruby-on-rails-更好的替代方法try(:output).try(:data).try(:name)?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.c

  6. jquery - 我的 jquery AJAX POST 请求无需发送 Authenticity Token (Rails) - 2

    rails中是否有任何规定允许站点的所有AJAXPOST请求在没有authenticity_token的情况下通过?我有一个调用Controller方法的JqueryPOSTajax调用,但我没有在其中放置任何真实性代码,但调用成功。我的ApplicationController确实有'request_forgery_protection'并且我已经改变了config.action_controller.consider_all_requests_local在我的environments/development.rb中为false我还搜索了我的代码以确保我没有重载ajaxSend来发送

  7. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  8. 阿里云RDS——产品系列概述 - 2

    基础版云数据库RDS的产品系列包括基础版、高可用版、集群版、三节点企业版,本文介绍基础版实例的相关信息。RDS基础版实例也称为单机版实例,只有单个数据库节点,计算与存储分离,性价比超高。说明RDS基础版实例只有一个数据库节点,没有备节点作为热备份,因此当该节点意外宕机或者执行重启实例、变更配置、版本升级等任务时,会出现较长时间的不可用。如果业务对数据库的可用性要求较高,不建议使用基础版实例,可选择其他系列(如高可用版),部分基础版实例也支持升级为高可用版。基础版与高可用版的对比拓扑图如下所示。优势 性能由于不提供备节点,主节点不会因为实时的数据库复制而产生额外的性能开销,因此基础版的性能相对于

  9. ruby - rbenv 安装权限被拒绝 - 2

    大家好,我正在尝试设置一个开发环境,并且我一直在关注以下教程:Linktotutorial我做得不是很好,除了最基本的版本控制内容外,我对终端命令没有任何实际经验。我点击了第一个链接并尝试运行source~/.bash_profile我得到了错误;mkdir:/usr/local/rbenv/shims:权限被拒绝mkdir:/usr/local/rbenv/versions:权限被拒绝现在每次我加载终端时都会出现错误。bash_profile的内容;exportPATH=/usr/local/rbenv/bin:$PATHexportRBENV_ROOT=/usr/local/rbe

  10. ruby - HTTP 请求中的用户代理,Ruby - 2

    我是Ruby的新手。我试过查看在线文档,但没有找到任何有效的方法。我想在以下HTTP请求botget_response()和get()中包含一个用户代理。有人可以指出我正确的方向吗?#PreliminarycheckthatProggitisupcheck=Net::HTTP.get_response(URI.parse(proggit_url))ifcheck.code!="200"puts"ErrorcontactingProggit"returnend#Attempttogetthejsonresponse=Net::HTTP.get(URI.parse(proggit_url)

随机推荐