玩转ActivityResultLauncher领略设计之美
ActivityResultLauncher 作为新一代的 RsultApi,其目的用于简化页面中跳转获取返回值以及请求权限。
但 ActivityResultLauncher 在某些场景下确会出现模板过多,不好用的场景,譬如以下这一场景:ActivityResultLauncher 在一个页面中不同的业务场景请求不同的权限的场景。
面对这样的情况,我们是盲目使用第三库去完成请求权限功能?还是通过一些设计思维对封装 ActivityResultLauncher 使用?这一场景下的 ActivityResultLauncher 何去何从?面对这样的场景。我坚决的奉行自己动手丰衣足食,真的是丰衣给足食开门。
ActivityResultLauncher 优缺点
ActivityResultLauncher 作为新一代的 RsultApi,
优点在于通过单一原则,通过单独对应的回调专门处理对应业务逻辑;譬如下面这一段伪代码对比
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when(requestCode){
getPerssionPhoneCode->{
}
getImageCode->{
}
}
}
class LoginResultContractDemo : ActivityResultContract<Boolean, Boolean>() {
override fun createIntent(context: Context, input: Boolean?): Intent {
return Intent(context, ActivityResultLauncherLoginDemoActivity::class.java)
}
override fun parseResult(resultCode: Int, intent: Intent?): Boolean {
if (resultCode == Activity.RESULT_OK) {
return intent?.getBooleanExtra(
ActivityResultLauncherLoginDemoActivity.LOGIN_RESULT_TAG,
false) ?: false
}
return false
}
}
class ActivityResultLauncherDemoActivity : AppCompatActivity() {
private val loginLauncher =
registerForActivityResult(LoginResultContractDemo()) { result ->
Toast.makeText(this,
if (result) "登录成功" else "登录失败", Toast.LENGTH_SHORT).show()
}
override fun onCreate(savedInstanceState: Bundle?) {
toLogin.setOnClickListener {loginLauncher.launch(false)}
}
}
优点在于:
- ActivityResultContract 作为单一职责:负责处理页面跳转传参以及页面结果的回调处理
- ActivityResultCallback 仅负责将 ActivityResultContract 中的目标页面的期望结果进行输出回调即可
- ActivityResultLauncher 负责将 ActivityResultContract 以及 ActivityResultCallback 关联起来
- 使用这三者抛离旧时代API的中回调各种复杂判断通过使用这样的解耦关系使得每一个页面更加简便清爽,并可以进行抽离封装复用。
缺点在于面对通用场景时,单一原则会导致冗余的模板代码。
下面就用常见的请求权限场景展示这个缺点
class ActivityResultLauncherDemoActivity : AppCompatActivity() {
private val userLocationPermission = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()) { map ->
val failMultiplePermission = ArrayList<String>()
map.forEach { entry ->
if (!entry.value) {
failMultiplePermission.add(entry.key)
}
}
if (failMultiplePermission.isNullOrEmpty()) {
toUserLocationLogic()
return@registerForActivityResult
}
loseUserLocationLogic()
}
private val imagePermission = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()) { map ->
val failMultiplePermission = ArrayList<String>()
map.forEach { entry ->
if (!entry.value) {
failMultiplePermission.add(entry.key)
}
}
if (failMultiplePermission.isNullOrEmpty()) {
getImage()
return@registerForActivityResult
}
loseImage()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityResultLauncherDemoBinding.inflate(layoutInflater)
bingding.userLocation.setOnClickListenr{
userLocationPermission.launcher(arrayof())
}
bingding.image.setOnClickListenr{
imagePermission.launcher(arrayof())
}
}
}
面对上面弊端能不能通过一个抽象方式的思维去简化封装呢?
从抽象到实现的一小步
期望中的抽象
我希望这个权限请求库的流程时序是这样的:
Activity/Fragement
IPermissionHelper
IPermissionChecker
根据生命周期注册权限申请回调
请求权限
内部处理权限是否请求成功
请求权限成功
请求权限成功
请求权限失败
请求权限失败
根据生命周期解绑权限申请回调
Activity/Fragement
IPermissionHelper
IPermissionChecker
IPermissionHelper 的构建
Activity/Fragment绑定
请求权限
让checker判断权限请求成功与否
Activity/Fragment解绑
IPermissionHelper
attach
requestPermission
IPermissionChecker
detach
基于上述思路我们可以这样声明 IPermissionHelper:
interface IPermissionHelper {
fun attach(activity: ComponentActivity)
fun attach(fragment: Fragment)
fun detach(activity:ComponentActivity)
fun detach(fragment:Fragment)
fun requestPermission(
permission: String,call: ISinglePermissionCheckAction.() -> Unit)
fun requestPermission(
permissions:Array<String>,call: IMultilplePermissionCheckAction.() -> Unit)
}
写到这里的时候,同学们肯定有疑问为什么会有:
ISinglePermissionCheckAction、IMultilplePermissionCheckAction,这两个权限检查器?其实你可以这样子想在权限申请中有分为单权限请求以及多权限请求这时候,每个权限检查器的目的都是在于判断是否成功然后返回对应的结果。
单权限检查器仅仅关注与当前这一个权限检查器是否用户允许。
多权限检查器不仅需要关注是否全部权限被允许通过还需要告诉开发者有哪些权限用户不允许
IPermissionChecker 思维构建
在这里的权限检查器我希望它能做到这些事情
告知用户权限允许情况
检测判断当前权限情况
通过申请
失败回调
IPermissionHelper
IPermissionChecker
checkAction
okAction
failAction
ISinglePermissionCheckAction 抽象构建
interface ISinglePermissionCheckAction {
fun checkAction(result: Boolean) {
if (result) {
okAction?.invoke()
return
}
failAction?.invoke()
}
var okAction: (() -> Unit)?
var failAction: (() -> Unit)?
}
IMultilplePermissionCheckAction 抽象构建
interface IMultilplePermissionCheckAction {
fun checkAction(map: Map<String, Boolean>) {
val failMultiplePermission = ArrayList<String>()
map.forEach { entry ->
if (!entry.value) {
failMultiplePermission.add(entry.key)
}
}
if (failMultiplePermission.isNullOrEmpty()) {
okAction?.invoke()
return
}
failAction?.invoke(failMultiplePermission)
}
var okAction: (() -> Unit)?
var failAction: ((List<String>) -> Unit)?
}
贯穿抽象的实现
IPermissionHelper 与页面绑定解绑
ActivityResultLauncher 在使用过程中,需要注意的一点是必须在页面处于 Start 生命周期前进行绑定,否则会报错。
那么基于 IPermissionHelper 中的 attach以及detach 我们可以这样实现
class KTSupportPermission() : IPermissionHelper {
private var singlePermissionLauncher: ActivityResultLauncher<String>? = null
private var multiplePermissionLauncher: ActivityResultLauncher<Array<String>>? = null
@Override
fun attach(activity: ComponentActivity) {
singlePermissionLauncher = activity.registerForActivityResult(
ActivityResultContracts.RequestPermission(),
singlePermissionResultCallBack
)
multiplePermissionLauncher = activity.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions(),
multiplePermissionResultCallback
)
}
@Override
fun detach(activity: ComponentActivity) {
singlePermissionLauncher?.unregister()
multiplePermissionLauncher?.unregister()
singlePermissionLauncher = null
multiplePermissionLauncher = null
}
@Override
fun attach(fragment: Fragment) {
singlePermissionLauncher = fragment.registerForActivityResult(
ActivityResultContracts.RequestPermission(),
singlePermissionResultCallBack
)
multiplePermissionLauncher = fragment.registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions(),
multiplePermissionResultCallback
)
}
@Override
fun detach(fragment: Fragment) {
singlePermissionLauncher?.unregister()
multiplePermissionLauncher?.unregister()
singlePermissionLauncher = null
multiplePermissionLauncher = null
}
}
IPermissionHelper 与 IPermissionChecker 关联
在上面的小节中我们已经实现 IPermissionChecker,那么我们怎么将 IPermissionHelper 与IPermissionChecker 进行一个实际的关联吗?还记得上一小节中 ActivityResultLauncher 吗?每一个ActivityResultLauncher 其实都有一个对应的 ActivityResultCallback,那么我们就利用ActivityResultCallback 中的回调接口,将用户允许权限的情况告知到 IPermissionChecker,使用这样的一个方法将它们互相关联起来。
class KTSupportPermission() : IPermissionHelper {
private val multiplePermissionResultCallback = ActivityResultCallback<Map<String, Boolean>> {
currentMultilplePermissionCheckAction?.checkAction(it)
}
private val singlePermissionResultCallBack = ActivityResultCallback<Boolean> {
currentSingleCheckAction?.checkAction(it)
}
private var currentSingleCheckAction: ISinglePermissionCheckAction?
= null
private var currentMultilplePermissionCheckAction: IMultilplePermissionCheckAction?
= null
@Override
fun requestPermission(
permission: String,
call: ISinglePermissionCheckAction.() -> Unit) {
val imp = KTSupportSinglePermissionImp()
currentSingleCheckAction = imp
imp.call()
singlePermissionLauncher?.launch(permission)
}
@Override
fun requestPermission(
permissions: Array<String>,
call: IMultilplePermissionCheckAction.() -> Unit) {
val imp = KTSupportMultiplePermissionImp()
currentMultilplePermissionCheckAction = imp
imp.call()
multiplePermissionLauncher?.launch(permissions)
}
}
class KTSupportSinglePermissionImp(
override var okAction: (() -> Unit)? = null,
override var failAction: (() -> Unit)? = null,
) : ISinglePermissionCheckAction
class KTSupportMultiplePermissionImp(
override var okAction: (() -> Unit)?=null,
override var failAction: ((List<String>) -> Unit)?=null,
) : IMultilplePermissionCheckAction
实践调用示例
class MainActivity : AppCompatActivity() {
private val ktSupportPermission = KTSupportPermission()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
ktSupportPermission.attach(this)
binding.getMultipePermission.setOnClickListener {
val permissions = arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
ktSupportPermission
.requestPermission(permissions) {
okAction = {}
failAction = {}
}
}
binding.getSinglePermission.setOnClickListener {
ktSupportPermission
.requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) {
okAction = {}
failAction = {}
}
}
}
}
总结
通过本文的学习要区分出新旧时代的优缺点,如何利用抽象方式思维以及DSL语法糖简化代码的封装编写。告别笨重的代码编写方式。
本文中的编写的权限请求现已开源也可通过远程依赖方式进行依赖使用
项目地址:https://github.com/KilleTom/KtJetPackSupportApp
目前KtJetPackSupportApp 这个工具库会基于 jetpack 进行不定时更新拓展。方便大家以后的工作以及学习的使用
权限库的使用方式如下:
代码使用如上一小节中的实践调用使用就可
以下是配置依赖方式
allprojects {
repositories {
...
maven { url 'https://www.jitpack.io' }
}
}
dependencies {
implementation 'com.github.KilleTom:KtJetPackSupportApp:permission-beta_v1.0.3'
}
|