前言

当一个活动结束时,要传递数据给上一个活动,如在活动一(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)

但是以这种方式启动就不能接收活动二的返回值了。

剖析

startActivitystartActivityForResult为何一个能用一个不能用呢?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的,也就是ContextActivity的上层抽象,根据里氏替换原则,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方式启动活动的目的。