Android startActivityForResult的调用与封装详解

目录
  • 前言
  • 一、原生的使用
  • 二、对原生的封装Ghost
  • 三、Result Api 的使用
  • 四、Result Api 的封装
    • 4.1 封装简化创建方式
    • 4.2 自动注册/按需注册
  • 总结

前言

startActivityForResult 可以说是我们常用的一种操作了,用于启动新页面并拿到这个页面返回的数据,是两个 Activity 交互的基本操作。

虽然可以通过接口,消息总线,单例池,ViewModel 等多种方法来间接的实现这样一个功能,但是 startActivityForResult 还是使用最方便的。

目前有哪些方式实现 startActivityForResult 的功能呢?

有新老两种方式,过时的方法是原生Activity/Fragment的 startActivityForResult 方法。另一种方法是 Activity Result API 通过 registerForActivityResult 来注册回调。

我们一起看看都是如何使用,使用起来方便吗?通常我们又都是如何封装的呢?

一、原生的使用

不管是Activity还是Fragment,我们都可以使用 startActivityForResult

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == 120 && resultCode == -1) {
            toast("接收到返回的数据:" + data?.getStringExtra("text"))
        }
    }

可以看到虽然标记过时了,但是 startActivityForResult 这种方法是可以用的,我们一直这么用的,老项目中有很多页面都是这么定义的。也并没有什么问题。

不过既然谷歌推荐我们使用 Result Api 我们在以后使用 startActivityForResult 的时候还是推荐使用新的方式。

二、对原生的封装Ghost

在之前我们使用 startActivityForResult 这种方式的时候,为了更加方便的私有,有一种很流行的方式 Ghost 。

它使用一种 GhostFragment 的空视图当做一次中转,这种思路在现在看来已经不稀奇了,很多框架如Glide,权限申请等都是用的这种方案。

它的大致实现流程为:

Activty/Fragment -> add GhostFragment -> onAttach 中 startActivityForResult -> GhostFragment onActivityResult接收结果 -> callback回调给Activty/Fragment

总体需要两个类就可以完成这个逻辑,一个是中转Fragment,一个是管理类:

/**
 * 封装Activity Result的API
 * 使用空Fragemnt的形式调用startActivityForResult并返回回调
 *
 * Activty/Fragment——>add GhostFragment——>onAttach中startActivityForResult
 * ——>GhostFragment onActivityResult接收结果——>callback回调给Activty/Fragment
 */
class GhostFragment : Fragment() {

    private var requestCode = -1
    private var intent: Intent? = null
    private var callback: ((result: Intent?) -> Unit)? = null

    fun init(requestCode: Int, intent: Intent, callback: ((result: Intent?) -> Unit)) {
        this.requestCode = requestCode
        this.intent = intent
        this.callback = callback
    }

    private var activityStarted = false

    override fun onAttach(activity: Activity) {
        super.onAttach(activity)
        if (!activityStarted) {
            activityStarted = true
            intent?.let { startActivityForResult(it, requestCode) }
        }
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (!activityStarted) {
            activityStarted = true
            intent?.let { startActivityForResult(it, requestCode) }
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == Activity.RESULT_OK && requestCode == this.requestCode) {
            callback?.let { it1 -> it1(data) }
        }
    }

    override fun onDetach() {
        super.onDetach()
        intent = null
        callback = null
    }

}
/**
 * 管理GhostFragment用于StartActivityForResult
 * 启动的时候添加Fragment 返回的时移除Fragment
 */
object Ghost {
    var requestCode = 0
        set(value) {
            field = if (value >= Integer.MAX_VALUE) 1 else value
        }

    inline fun launchActivityForResult(
        starter: FragmentActivity?,
        intent: Intent,
        crossinline callback: ((result: Intent?) -> Unit)
    ) {
        starter ?: return
        val fm = starter.supportFragmentManager
        val fragment = GhostFragment()
        fragment.init(++requestCode, intent) { result ->
            callback(result)
            fm.beginTransaction().remove(fragment).commitAllowingStateLoss()
        }
        fm.beginTransaction().add(fragment, GhostFragment::class.java.simpleName)
            .commitAllowingStateLoss()
    }

}

如此我们就可以使用Kotlin的扩展方法来对它进行进一步的封装

//真正执行AcytivityForResult的方法,使用Ghost的方式执行
inline fun <reified T> FragmentActivity.gotoActivityForResult(
    flag: Int = -1,
    bundle: Array<out Pair<String, Any?>>? = null,
    crossinline callback: ((result: Intent?) -> Unit)
) {
    val intent = Intent(this, T::class.java).apply {
        if (flag != -1) {
            this.addFlags(flag)
        }
        if (bundle != null) {
            //调用自己的扩展方法-数组转Bundle
            putExtras(bundle.toBundle()!!)
        }
    }
    Ghost.launchActivityForResult(this, intent, callback)
}

使用起来就超级简单了:

    gotoActivityForResult<Demo10Activity> {
        val text = it?.getStringExtra("text")
        toast("拿到返回数据:$text")
    }

    gotoActivityForResult<Demo10Activity>(bundle = arrayOf("id" to "123", "name" to "zhangsan")) {
        val text = it?.getStringExtra("text")
        toast("拿到返回数据:$text")
    }

三、Result Api 的使用

其实看Ghost的原来就看得出,他本质上还是对 startActivityForResult 的调用与封装,还是过期的方法,那么如何使用新的方式,谷歌推荐我们怎么用?

Activity Result API :

它是 Jetpack 的一个组件,这是官方用于替代 startActivityForResult() 和 onActivityResult() 的工具,我们以Activity 1.2.4版本为例:

implementation "androidx.activity:activity-ktx:1.2.4"

那么如何基础的使用它呢:

    private val safLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
        if (result.resultCode == RESULT_OK) {
            val data = result.data?.getStringExtra("text")
            toast("拿到返回数据:$data")
        }
    }

    //在方法中使用
    safLauncher?.launch(Intent(mActivity, Demo10Activity::class.java))

看起来实现很简单,但是有几点要注意,Launcher 的创建需要在onStart生命周期之前,并且回调是在 Launcher 中处理的。并且 这些 Launcher 并不是只能返回Activity的Result的,还有其他的启动方式:

StartActivityForResult()
StartIntentSenderForResult()
RequestMultiplePermissions()
RequestPermission()
TakePicturePreview()
TakePicture()
TakeVideo()
PickContact()
GetContent()
GetMultipleContents()
OpenDocument()
OpenMultipleDocuments()
OpenDocumentTree()
CreateDocument()

可以看到这些方式其实对我们来说很多没必要,在真正的开发中只有 StartActivityForResult 这一种方式是我们的刚需。

为什么?毕竟现在谁还用这种方式申请权限,操作多媒体文件。相信大家也都是使用框架来处理了,所以我们这里只对 StartActivityForResult 这一种方式做处理。毕竟这才是我们使用场景最多的,也是我们比较需要的。

经过分析,对Result Api的封装,我们就剩下的两个重点问题:

  • 我们把 Launcher 的回调能在启动的方法中触发。
  • 实现 Launcher 在 Activity/Fragment 中的自动注册。

下面我们就来实现吧。

四、Result Api 的封装

我们需要做的是:

第一步我们把回调封装到launch方法中,并简化创建的对象方式

第二步我们尝试自动注册的功能

4.1 封装简化创建方式

首先第一步,我们对 Launcher 对象做一个封装, 把 ActivityResultCallback 回调方法在 launch 方法中调用。

/**
 * 对Result-Api的封装,支持各种输入与输出,使用泛型定义
 */
@SuppressWarnings("unused")
public class BaseResultLauncher<I, O> {

    private final androidx.activity.result.ActivityResultLauncher<I> launcher;
    private final ActivityResultCaller caller;
    private ActivityResultCallback<O> callback;
    private MutableLiveData<O> unprocessedResult;

    public BaseResultLauncher(@NonNull ActivityResultCaller caller, @NonNull ActivityResultContract<I, O> contract) {
        this.caller = caller;
        launcher = caller.registerForActivityResult(contract, (result) -> {
            if (callback != null) {
                callback.onActivityResult(result);
                callback = null;
            }
        });
    }

    public void launch(@SuppressLint("UnknownNullness") I input, @NonNull ActivityResultCallback<O> callback) {
        launch(input, null, callback);
    }

    public void launch(@SuppressLint("UnknownNullness") I input, @Nullable ActivityOptionsCompat options, @NonNull ActivityResultCallback<O> callback) {
        this.callback = callback;
        launcher.launch(input, options);
    }

}

上门是对Result的基本封装,由于我们只想要 StartActivityForResult 这一种方式,所以我们定义一个特定的 GetSAFLauncher

/**
 * 一般我们用这一个-StartActivityForResult 的 Launcher
 */
class GetSAFLauncher(caller: ActivityResultCaller) :
    BaseResultLauncher<Intent, ActivityResult>(caller, ActivityResultContracts.StartActivityForResult()) {

    //封装另一种Intent的启动方式
    inline fun <reified T> launch(
        bundle: Array<out Pair<String, Any?>>? = null,
        @NonNull callback: ActivityResultCallback<ActivityResult>
    ) {

        val intent = Intent(commContext(), T::class.java).apply {
            if (bundle != null) {
                //调用自己的扩展方法-数组转Bundle
                putExtras(bundle.toBundle()!!)
            }
        }

        launch(intent, null, callback)

    }

}

注意这里调用的是 ActivityResultContracts.StartActivityForResult() 并且泛型的两个参数是 Intent 和 ActivityResult。

如果大家想获取文件,可以使用 GetContent() 泛型的参数就要变成 String 和 Uri 。由于我们通常不使用这种方式,所以这里不做演示。

封装第一步之后我们就能这么使用了。

    var safLauncher: GetSAFLauncher? = null

    //其实就是 onCreate 方法
    override fun init() {
        safLauncher = GetSAFLauncher(this@Demo16RecordActivity)
    }

    //AFR
    fun resultTest() {

        safLauncher?.launch(Intent(mActivity, Demo10Activity::class.java)) { result ->
            val data = result.data?.getStringExtra("text")
            toast("拿到返回数据:$data")
        }
    }

//或者使用我们自定义的简洁方式

    fun resultTest() {

       safLauncher?.launch<Demo10Activity> { result ->
            val data = result.data?.getStringExtra("text")
            toast("拿到返回数据:$data")
        }

        safLauncher?.launch<Demo10Activity>(arrayOf("id" to "123", "name" to "zhangsan")) { result ->
            val data = result.data?.getStringExtra("text")
            toast("拿到返回数据:$data")
        }
    }

使用下来是不是简单了很多了,我们只需要创建一个对象就可以了,拿到这个对象调用launch即可实现 startActivityForResult 的功能呢!

4.2 自动注册/按需注册

可以看到相比原始的用法,虽然我们现在的用法就简单了很多,但是我们还是要在oncreate生命周期中创建 Launcher 对象,不然会报错:

LifecycleOwners must call register before they are STARTED.

那我们有哪些方法处理这个问题?

1)基类定义

我们都已经封装成对象使用了,我们把创建的逻辑定义到BaseActivity/BaseFragment不就行了吗?

abstract class AbsActivity() : AppCompatActivity(){

    protected var safLauncher: GetSAFLauncher? = null

    ...

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView()

        //Result-Api
        safLauncher = GetSAFLauncher(this)

        ...
    }

}

这样不就行了吗?可以正常使用的。那有人可能说,你这个对象可能用不到,又不是每一个Activity都会用到 Launcher 对象,你这么无脑创建出来消耗内存。

有办法,按需加载!

2).懒加载

懒加载可以吧,我需要的时候就创建。

abstract class AbsActivity() : AppCompatActivity(){

    val safLauncher by lazy { GetSAFLauncher(this) }

    ...
}

额,等等,这样的懒加载貌似是不行的,这在用的时候才初始化,一样会报错:

LifecycleOwners must call register before they are STARTED.

我们只能在页面创建的时候就要明确,这个页面是否需要这个 Launcher 对象,如果要就要在onCreate中创建对象,如果确定不要 Launcher 对象,那么就不必创建对象。

那我们就这么做:

abstract class AbsActivity() : AppCompatActivity(){

    protected var safLauncher: GetSAFLauncher? = null

    ...

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView()

        if (needLauncher()) {
            //Result-Api
            safLauncher = GetSAFLauncher(this)
        }

        ...
    }

    open protected fun needLauncher(): Boolean = false

}

我们使用一个flag判断不就行了吗?这个页面如果需要 Launcher 对象,重写方法返回true就行了。默认是不创建这个对象的。

3).Kotlin委托

我们可以使用Kotlin的委托方式,把初始化的代码和 Launcher 的对象获取用接口封装,然后提供对应的实现类,不就可以完成按需添加 Launcher 的效果了吗?

我们定义一个接口,由于逻辑都封装在了别处,这里就尽量不改动之前的代码,只是定义初始化和提供对象两种方法。

/**
 * 定义是否需要SAFLauncher
 */
interface ISAFLauncher {

    fun <T : ActivityResultCaller> T.initLauncher()

    fun getLauncher(): GetSAFLauncher?

}

接着定义这个实现类

class SAFLauncher : ISAFLauncher {

    private var safLauncher: GetSAFLauncher? = null

    override fun <T : ActivityResultCaller> T.initLauncher() {
        safLauncher = GetSAFLauncher(this)
    }

    override fun getLauncher(): GetSAFLauncher? = safLauncher

}

然后我们就可以使用了:

class Demo16RecordActivity : BaseActivity, ISAFLauncher by SAFLauncher() {

    //onCreate中直接初始化对象
    override fun init() {
        initLauncher()
    }

    //获取到对象直接用即可,还是之前的几个方法,没有变。
    fun resultTest() {

       getLauncher()?.launch<Demo10Activity> { result ->
            val data = result.data?.getStringExtra("text")
            toast("拿到返回数据:$data")
        }
    }

}

效果都是一样的:

这样通过委托的方式,我们就能自己管理初始化,自己随时获取到对象调用launch方法。

如果你当前的Activity不需要 startActivityForResult 这种功能,那么你不实现这个接口即可,如果想要 startActivityForResult 的功能,就实现接口委托实现,从而实现按需加载的逻辑。

我们再回顾一下 Result Api 需要封装的两个痛点与优化步骤:

  • 第一步我们把回调封装到launch方法中,并简化创建的对象方式
  • 第二步我们尝试自动注册的功能

同时我们还对一些步骤做了更多的可能性分析,对主动注册的方式我们有三种方式,(当然其实还有更多别的方式来实现,我只写了我认为比较简单方便的几种方式)。

到此对 Result Api的封装就此结束。

总结

总的来说 Result Api 的封装其实也不难,使用起来也是很简单了。如果大家是Kotlin项目我推荐使用委托的方式,如果是Java语言开发的也可以用flag的方式实现按需加载的逻辑。

而不想使用 Result Api 那么使用原始的 startActivityForResult 也能实现,那么我推荐你使用 Ghost 框架,可以更加方便快速的实现返回的功能。

本文对于 Result Api 的封装也只是限于 startActivityForResult 这一个场景,不过我们这种方式是很方便扩展的,如果大家想使用Result Api的方式来操作权限,文件等,都可以在 BaseResultLauncher 基础上进行扩展。

到此这篇关于Android startActivityForResult的调用与封装详解的文章就介绍到这了,更多相关Android startActivityForResult内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 基于android startActivityForResult的学习心得总结

    从昨晚到现在终于调试通了一个startActivityForResult的例子,网上要么有些说的太复杂了,要么说的含糊,搞的我走了很多弯路,所以写篇心得.在一个主界面(主Activity)上能连接往许多不同子功能模块(子Activity上去),当子模块的事情做完之后就回到主界面,还同时返回一些子模块完成的数据交给主Activity处理.  用startActivity启动主界面是一个新的Intent实例,而访问的主界面还在activity栈的下面没有调出来,这样做的一个最大的问题是,回不到原界面

  • Android基础之startActivityForResult()的用法详解

    前言 安卓开发中一个很基础的操作就是打开一个 Activity ,另一个很必要的操作就是,打开一个 Activity ,在打开的 Activity 中操作之后并获得返回结果. 两个 Activity 为了演示这个操作,我们需要两个 Activity : MainActivity 和 OtherActivity ,在 MainActivity 中通过 Intent 启动 OtherActivity ,并获得 OtherActivity 结束后返回的结果,例子仅作演示,为了方便,我直接在 Java

  • Android startActivityForResult实例详解

    Android startActivityForResult实例详解 startActivityForResult用于两个activity之间的数据传递,Activity1传值给Activity2,Activity2再返回值给Activity1. 第一步:Activity1中:startActivityForResult(Intent intent, Int requestCode) Intent intent = new Intent(); intent.setClass(Activity1.

  • android startActivityForResult的使用方法介绍

    Activity 跳转 都知道用startActivity(Intent)但是如果下面情况呢?Activity1 跳转到 Activity2  但是还需要在Activity2 再回到 Activity1呢? 可能有人说: 那我在Activity2  再使用 startActivity() 不就可以了 是的 但是 startActivityForResult() 能够直接完成这项工作[示例]Activity1: 有2个EditText 用于接收用户输入的2个字符串 要求把这2个字符串连接起来 我现

  • android开发教程之startActivityForResult使用方法

    最近做的一个小东西遇到这样的情况,我从一个页面MainActivity修改一些内容,需要跳转到一个新的EditActivity去做修改操作,修改完成后就回到之前的MainActivity,因为信息被修改了,最好还是从服务器上重新获取一下,那么就要在MainActivity里重新获取,假如把重新获取数据的操作放在MainActivity的onStart()方法里,那么MainActivity跳转到其它的地方再回来不需要请求数据的,它也必然会又请求一次,这样就白白的增大了服务器的压力. 也就是说,要

  • Android startActivityForResult的基本用法详解

    知识准备: 1.startActivityForResult(Intent intent, int requestCode):启动 Activity,同时等待该 Activity 返回数据.只有该 Activity 销毁时数据才会被返回. 参数 1:意图,封装要启动的 Activity,当然也可以携带数据 参数 2:请求码,如果是大于 0 的整数,那么该请求码会在 onActivityResult 中的 requestCode中出现,如果小于等于 0,则不会被返回. 2.onActivityRe

  • Android startActivityForResult的调用与封装详解

    目录 前言 一.原生的使用 二.对原生的封装Ghost 三.Result Api 的使用 四.Result Api 的封装 4.1 封装简化创建方式 4.2 自动注册/按需注册 总结 前言 startActivityForResult 可以说是我们常用的一种操作了,用于启动新页面并拿到这个页面返回的数据,是两个 Activity 交互的基本操作. 虽然可以通过接口,消息总线,单例池,ViewModel 等多种方法来间接的实现这样一个功能,但是 startActivityForResult 还是使

  • Android SharedPreferences存取操作以及封装详解

    存 首先初始化 private SP sp; sp = new SP( context ); 存入数据 第一个参数为上下文,第二个参数为key,第三个参数为要存入的数据Value sp.PutData( context,"AccessToken",AccessToken ); 取 初始化 private SP sp; sp = new SP( context ); 取出数据 第一个参数为上下文,第二个参数为存入数据时定义的名称,第三个数据为取出数据的默认类型(这个参数很关键,再封装类转

  • Android Xutils3网络请求的封装详解及实例代码

     Xutils3网络请求的封装详解 封装了一个Xutil3的网络请求工具类,分享给大家,本人水平有限,不足之处欢迎指出. 使用前先配置xutils3: 1.gradle中添加 compile 'org.xutils:xutils:3.3.40' 2.自定义Application /** * Created by Joe on 2016/9/25. */ public class MyApp extends Application { @Override public void onCreate(

  • Android 7.0调用相机崩溃详解及解决办法

    Android 7.0调用相机崩溃解决办法 错误提示: android.os.FileUriExposedException: file:///storage/emulated/0/DCIM/IMG_1041503431.jpg exposed beyond app through ClipData.Item.getUri() 处理方式 /** * Open camera */ private void showCameraAction() { if (ContextCompat.checkSe

  • Android OkHttp的简单使用和封装详解

    Android OkHttp的简单使用和封装详解 1,昨天把okHttp仔细的看了一下,以前都是调用同事封装好了的网络框架,直接使用很容易,但自己封装却不是那么简单,还好,今天就来自我救赎一把,就和大家写写从最基础的OKHttp的简单get.post的使用,再到它的封装. 2,OkHttp的简单使用 首先我们创建一个工程,并在布局文件中添加三个控件,TextView(用于展示获取到json后的信息).Button(点击开始请求网络).ProgressBar(网络加载提示框) ①简单的异步Get请

  • Android Bluetooth蓝牙技术使用流程详解

    在上篇文章给大家介绍了Android Bluetooth蓝牙技术初体验相关内容,感兴趣的朋友可以点击了解详情. 一:蓝牙设备之间的通信主要包括了四个步骤 设置蓝牙设备 寻找局域网内可能或者匹配的设备 连接设备 设备之间的数据传输 二:具体编程实现 1. 启动蓝牙功能 首先通过调用静态方法getDefaultAdapter()获取蓝牙适配器BluetoothAdapter,如果返回为空,则无法继续执行了.例如: BluetoothAdapter mBluetoothAdapter = Blueto

  • Android 中RecyclerView顶部刷新实现详解

    Android 中RecyclerView顶部刷新实现详解 1. RecyclerView顶部刷新的原理 RecyclerView顶部刷新的实现通常都是在RecyclerView外部再包裹一层布局.在这个外层布局中,还包含一个自定义的View,作为顶部刷新时的指示View.也就是说,外层布局中包含两个child,一个顶部刷新View,一个RecyclerView,顶部刷新View默认是隐藏不可见的.在外层布局中对滑动事件进行处理,当RecyclerView滑动到顶部并继续下滑的时候,根据滑动的距

  • Android Canvas方法总结最全面详解API(小结)

    本篇文章主要介绍了Android Canvas方法总结最全面详解API,分享给大家,具体如下: 常用方法 drawXxx方法族:以一定的坐标值在当前画图区域画图,另外图层会叠加, 即后面绘画的图层会覆盖前面绘画的图层. clipXXX方法族:在当前的画图区域裁剪(clip)出一个新的画图区域,这个 画图区域就是canvas对象的当前画图区域了.比如:clipRect(new Rect()), 那么该矩形区域就是canvas的当前画图区域 getXxx方法族:获得与Canvas相关一些值,比如宽高

  • Android Broadcast原理分析之registerReceiver详解

    目录 1. BroadcastReceiver概述 2. BroadcastReceiver分类 3. registerReceiver流程图 4. 源码解析 4.1 ContextImpl.registerReceiverInternal 4.2 LoadedApk.getReceiverDispatcher 4.3 ActivityManagerService.registerReceiver 5. 总结 1. BroadcastReceiver概述 广播作为四大组件之一,在平时开发过程中会

  • ​​​​​​​Android H5通用容器架构设计详解

    目录 背景 术语对齐 探索 如何优雅地提供接口调用? 怎样封装多个不同类型的H5容器容器? 整体架构 通用容器 框架容器 基础组件 这样的架构能带来什么样的好处? 背景 大家如果经历过Hybrid项目的开发,即项目中涉及到H5与Native之间的交互,那么很有可能会遇到各种各样的H5容器.为什么会有那么多各种各样的容器呢...这也是轮子多的通病了,轮子多到业务方不知道选哪个.当然,也有可能大家压根就不会使用到H5容器,直接用系统WebView就完事儿了,比如我的前东家就是这样做的.那这篇文章的主

随机推荐