0x00 写在前面
WMRouter 是美团出品的一款 Android 路由框架,它存在的一个问题就是启动 Activity 后的返回数据很难处理,还是要在 Activity 中去写 onActivityResult()
方法。比如下面这样:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// ...
val button = findViewById<Button>(R.id.main_to_second)
button.setOnClickListener(v -> DefaultUriRequest(this, "second")
.activityRequestCode(100)
.start())
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 100) {
// ...
}
}
}
这样的好处是,如果你的项目原先不是使用 WMRouter 的话,那么接入 WMRouter 以后只需要修改启动部分的代码,而不需要修改接收部分的逻辑。但是现在 onActivityResult()
已经被官方标记为废弃了,所以当然要尽快迁移到新的 API 中。而 WMRouter 又不支持新的 Activity Result API,这就变得有些尴尬。
![看着一行被划掉的线,终究还是觉得不爽](https://maronyea.me/wp-content/uploads/2022/02/DraggedImage.png)
这篇文章介绍了一种新的封装方式,使用了 Activity Result API 进行封装,通过一个不可见的 Fragment 来启动 Activity 和接收来自 Activity 返回的数据,再通过 WMRouter 的接口来替换默认的启动 Activity 相关的代码。
0x01 封装 Activity Result API
首先需要封装 Activity Result API。封装方式可以参考 Android 权限请求封装,同样是创建一个 BaseActivityResultLauncher
和它的子类,用于提供一个方便可用的 ActivityResultLauncher
。
abstract class BaseActivityResultLauncher<I, O>(
caller: ActivityResultCaller, contract: ActivityResultContract<I, O>) {
private var callback: ActivityResultCallback<O>? = null
// 注册 ActivityResultLauncher
private var launcher: ActivityResultLauncher<I> = caller.registerForActivityResult(contract) {
callback?.onActivityResult(it)
callback = null
}
protected fun launchInternal(input: I, callback: ActivityResultCallback<O>) {
this.callback = callback
launcher.launch(input)
}
}
class StartActivityLauncher(caller: ActivityResultCaller) : BaseActivityResultLauncher<Intent, ActivityResult>(
caller, ActivityResultContracts.StartActivityForResult()
) {
/**
* 启动 Activity
*
* @param intent 启动 Activity 的 Intent
* @param callback 结果回调
*/
fun launch(intent: Intent, callback: (Int, Intent?) -> Unit) {
launchInternal(intent) { result ->
callback(result.resultCode, result.data)
}
}
}
0x02 使用 Fragment 接收结果
接下来创建一个不可见的 Fragment,这个 Fragment 主要是用来启动 Activity 以及接收来自 Activity 返回的信息的。它实际上就是使用了 StartActivityLauncher
来实现的启动以及结果回调的。
class ForResultFragment : Fragment() {
private val launcher = StartActivityLauncher(this)
private lateinit var intent: Intent
private lateinit var callback: (Int, Intent?) -> Unit
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launcher.launch(intent) { code, data ->
callback(code, data)
removeFragment()
}
}
private fun removeFragment() {
try {
parentFragmentManager.beginTransaction().remove(this).commitAllowingStateLoss()
} catch (e: Exception) {
LogUtils.e("ForResultFragment", "remove fragment failed.", e)
}
}
fun setIntent(intent: Intent): ForResultFragment {
this.intent = intent
return this
}
fun setCallback(callback: (Int, Intent?) -> Unit): ForResultFragment {
this.callback = callback
return this
}
}
接下来是把通过 ForResultFragment
来启动 Activity
以及接收结果的逻辑集成到 WMRouter 中,这样就可以保留使用路径来启动 Activity
的功能,同时又能在发起请求时自定义接收的逻辑。
0x03 将自定义逻辑集成到 WMRouter
如何集成到 WMRouter
现在已经准备好了启动 Activity 和接收 Activity 返回的结果的 API 了,接下来要做的就是把这套逻辑集成到 WMRouter 里面去。而这个集成也无非就是两个关键点:
- 替换掉默认的启动 Activity 逻辑,换成由
ForResultFragment
来启动; - 给
UriRequest
加上接收结果的回调。
有关 WMRouter 是如何实现启动 Activity
的,请参考 WMRouter 启动 Activity 源码解析。从该篇分析中可以得到两个简单的结论:
- 启动 Activity 的地方是
DefaultActivityLauncher
类,实现了默认的 Activity 启动逻辑; - 通过
RouterComponents.setActivityLauncher()
方法可以替换ActivityLauncher
,实现自定义的启动逻辑。
要把 ForResultFragment
启动 Activity 的逻辑集成到 WMRouter 中,就需要通过 RouterComponents.setActivityLauncher()
方法来设置自定义的 ActivityLauncher
。所以我们可以继承 DefaultActivityLauncher
类并重写相应的方法,实现自定义启动逻辑。
ActivityLauncher
是 WMRouter 中的一个接口,不是 Activity Result API 中的类。而至于给 UriRequest
加上回调,我则是通过扩展函数来扩展 DefaultUriRequest
。当然,也可以选择继承 DefaultUriRequest
,效果都是差不多的。
扩展 DefaultUriRequest
创建一个 ForResultUriRequest.kt
,代码如下:
// ForResultUriRequest.kt
private const val ON_RESULT_CALLBACK = "StartActivityLauncher.OnResultCallback"
private interface OnResultCallback {
fun onResult(code: Int, data: Intent?)
}
fun DefaultUriRequest.hasOnResultCallback(): Boolean {
val callback = getField(OnResultCallback::class.java, ON_RESULT_CALLBACK)
return callback != null
}
fun DefaultUriRequest.getOnResultCallback(): ((Int, Intent?) -> Unit)? {
val callback = getField(OnResultCallback::class.java, ON_RESULT_CALLBACK) ?: return null
return { code, data ->
callback.onResult(code, data)
}
}
fun DefaultUriRequest.onResult(callback: (Int, Intent?) -> Unit): DefaultUriRequest {
putField(ON_RESULT_CALLBACK, object : OnResultCallback {
override fun onResult(code: Int, data: Intent?) {
callback(code, data)
}
})
return this
}
实现自定义的 ActivityLauncher
重写的 ActivityLauncher
实现类如下:
object ForResultActivityLauncher : DefaultActivityLauncher() {
override fun startActivityByAction(
request: UriRequest,
intent: Intent,
internal: Boolean
): Int {
return UriResult.CODE_NOT_FOUND
}
override fun startActivityByDefault(
request: UriRequest?,
context: Context,
intent: Intent,
requestCode: Int?,
internal: Boolean
): Int {
return try {
if (request == null) {
return UriResult.CODE_BAD_REQUEST
}
val options = request.getField(Bundle::class.java, ActivityLauncher.FIELD_START_ACTIVITY_OPTIONS)
if (context is FragmentActivity && request is DefaultUriRequest && request.hasOnResultCallback()) {
startForResult(context, intent) { code, data ->
request.getOnResultCallback()?.invoke(code, data)
}
} else {
ActivityCompat.startActivity(context, intent, options)
}
doAnimation(request)
request.putField(ActivityLauncher.FIELD_STARTED_ACTIVITY, if (internal) {
ActivityLauncher.INTERNAL_ACTIVITY
} else {
ActivityLauncher.EXTERNAL_ACTIVITY
})
UriResult.CODE_SUCCESS
} catch (e: ActivityNotFoundException) {
Debugger.e(e)
UriResult.CODE_NOT_FOUND
} catch (e: SecurityException) {
Debugger.e(e)
UriResult.CODE_FORBIDDEN
} catch (e: Exception) {
Debugger.e(e)
UriResult.CODE_ERROR
}
}
private fun startForResult(
activity: FragmentActivity,
intent: Intent,
callback: (Int, Intent?) -> Unit
) {
val fragment = ForResultFragment()
.setIntent(intent)
.setCallback(callback)
val transaction = activity.supportFragmentManager
.beginTransaction()
if (fragment.isAdded) {
transaction.remove(fragment)
}
transaction.add(fragment, fragment.toString())
.commitAllowingStateLoss()
}
}
在 Application 中使用自定义的 ActivityLauncher
最后在 Application 中设置一下即可。
class App : Application() {
override fun onCreate() {
// ...
// 用 RouterComponents 设置自定义的 ActivityLauncher
RouterComponents.setActivityLauncher(ForResultActivityLauncher)
Router.init(DefaultRootUriHandler(this))
}
}
0x04 使用效果
现在就可以试试看效果了。首先看一个 MainActivity
如何启动 SecondActivity
并处理返回的结果:
class MainActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.main_go).setOnClickListener {
DefaultUriRequest(this, "/second")
.onResult { i, _ ->
Log.d("MainActivity", "receive data!!! code: $i")
}.start()
}
}
}
然后是 SecondActivity
如何返回结果:
@RouterUri(path = ["/second"])
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
findViewById<Button>(R.id.second_back).setOnClickListener {
setResult(RESULT_OK)
finish()
}
}
}
0xFF 写在最后
这篇文章算是补上了之前留的坑,也就是分析了 WMRouter 的启动 Activity 源码以后,根据分析的结果来自定义一下启动 Activity 的逻辑。
下一篇应该会分享一下 WMRouter 的 ServiceLoader 部分的源码。看看啥时候有空吧。