前言
当一个活动结束时,要传递数据给上一个活动,如在活动一(Activity1
)中启动活动二(Activity2
),活动二结束时返回数据给活动一,可以在活动一中利用startActivityForResult
方式启动活动二,如下:
val intent = Intent(this@Activity1, Activity2::class.java).also {
it.putExtra("key1", "value1")
it.putExtra("key2", "value2")
}
startActivityForResult(intent, 1)
活动二结束前,相应调用setResult
方法设置要传回的值,然后在活动一中实现onActivityResult
方法进行接收。但如果活动的启动不是在活动类中,startActivityForResult
就调用不了了,例如活动一是一个列表视图,活动二是点击活动一列表中某个条目时,展现该条目的详情,则活动二的启动是在活动一列表视图的适配器中进行的,这时我们若想活动二结束时传值回活动一,启动活动二时,适配器中期待的代码如下:
class AdapterForActivity1(private val sourceList: MutableList<Recorder>): RecyclerView.Adapter<ListViewHolder>() {
private var mContext: Context? = null // 活动一上下文
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
if (mContext == null) {
mContext = parent.context
}
// 列表条目布局
val view = LayoutInflater.from(mContext).inflate(R.layout.recorder_item, parent, false)
val holder = ListViewHolder(view)
// 列表条目被点击时的监听器
holder.itemView.setOnClickListener { v ->
val position = holder.adapterPosition //被点击条目的位置索引
val curRecord = sourceList[position] // 被点击条目对应的数据实例
//要传给活动二的值
val intent = Intent(mContext, Activity2::class.java).also {
it.putExtra("key1", curRecord.value1)
it.putExtra("key2", curRecord.value2)
}
// 期待的活动启动方式
mContext?.startActivityForResult(intent, 1)
}
return holder
}
override fun getItemCount(): Int {
return sourceList.size
}
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
// ...
}
}
class ListViewHolder(view: View): RecyclerView.ViewHolder(view) {
// ...
}
然后照常在活动二中通过setResult
函数设置返回值并在活动一中实现onActivityResult
方法接收返回值。但是这是会发现mContext?.startActivityForResult(intent, 1)
这句是不通过的,会发现根本没有该方法,只能用如下常规方式启动:
mContext?.startActivity(intent)
但是以这种方式启动就不能接收活动二的返回值了。
剖析
startActivity
与startActivityForResult
为何一个能用一个不能用呢?startActivity
是在Activity
类中实现的,看startActivity
方法的源码如下:
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}
原来其实际调用的就是startActivityForResult
方法,只不过请求码默认给个-1
,再看startActivityForResult
的源码,如下:
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
// ...
} else {
// ...
}
}
但是可以注意到,startActivity
前有一个@Override
注解,证明这是方法的实现,向上需找,方法的定义是在Context
抽象类中,如下:
public abstract void startActivity(@RequiresPermission Intent intent,
@Nullable Bundle options);
而startActivityForResult
方法前没有@Override
注解,这证明startActivityForResult
只是Activity
类中的一个局部方法,Context
中是没有该方法的定义的,因此,在适配器中mContext
是调用不到startActivityForResult
的。那如何进行呢?看Activity
的继承关系如下:
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback,
AutofillManager.AutofillClient {
// ...
}
可以看到,其继承了ContextThemeWrapper
这个类,再看ContextThemeWrapper
的继承关系如下:
public class ContextThemeWrapper extends ContextWrapper {
// ...
}
可以看到其继承了ContextWrapper
这个类,再看ContextWrapper
的继承关系:
public class ContextWrapper extends Context {
// ...
}
可以看到,其实现了Context
这一抽象类,饶了这么一大圈,我们可以知道,实际上Activity
是实现了Context
的,也就是Context
是Activity
的上层抽象,根据里氏替换原则,Context
是可以通过强制类型转换转为Activity
类型的。因此,明白了这一点,要通过mContext
调用startActivityForResult
,只需将mContext
做一个强制类型转换,转换成Activity
类型再调用即可,如下:
// 做一个强制类型转换
(mContext!! as Activity).startActivityForResult(intent, 1)
这样就可以达成最初的目的了。然后其它环节照常。如活动二要在点击返回按钮被销毁时传递数据给活动一,适配器中的活动启动方式就如上所示,活动二中需要实现监听返回按钮点击事件的函数,并设置返回值,如下:
// 实现onBackPressed函数
override fun onBackPressed() {
// 设置要传回的数据
val intent = Intent().also {
putExtra("key1", "value1")
}
setResult(RESULT_OK, intent)
super.onBackPressed()
}
这里要注意的是,onBackPressed()
会自动执行finish()
方法销毁当前活动,因此super.onBackPressed()
方法调用实际上包含了finish()
的调用,因此,setResult()
要放在其之前。
最后在活动一种实现onActivityResult
方法接收传回的数据,如下:
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when(requestCode) {
1 -> { // 匹配请求码
if (resultCode == Activity.RESULT_OK) {
// 获取活动二传回的值
val value1 = data?.getStringExtra("key1")
// ...
}
}
}
}
至此,就实现了在非活动类中用startActivityForResult
方式启动活动的目的。