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,这就变得有些尴尬。
这篇文章介绍了一种新的封装方式,使用了 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 部分的源码。看看啥时候有空吧。