深入理解Activity Result API:ActivityResultContract的实现原理

简介: AndroidX从Activity:1.2.0-alpha02 和 Fragment:1.3.0-alpha02 起追加了Result API,使用ActivityResultContract替代st

在这里插入图片描述
AndroidX从Activity:1.2.0-alpha02Fragment:1.3.0-alpha02 起追加了Result API,使用ActivityResultContract替代startActivityForResult,更加高效且typesafe的处理跨Activity通信。目前Result API已升级到rc版,相对于alpha版API上有所变化,本文内容基于1.2.0-rc-01

参考:alpha版的Result API

如何使用

在AppCompatActivity或者Fragment中通过registerForActivityResult()创建ActivityResultLauncher,然后调用launch(...)替代startActivityForResult启动目标Activity。

//MainActivity.kt
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
      
        fab.setOnClickListener { view ->
            val intent = Intent(this, MainActivity::class.java)
            launcher.launch(intent)
        }
    }
  
    // Create ActivityResultLauncher
    private val launcher : ActivityResultLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
            activityResult ->
                   // activityResult will return this as ActivityResult                                                
                    Log.d("MainActivity", activityResult.toString())
                //  D/MainActivity: ActivityResult{resultCode=RESULT_OK, data=Intent { (has extras) }}
            }
}
//SecondActivity.kt
class SecondActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setResult(Activity.RESULT_OK, Intent().putExtra("my-data", "data"))
        finish()
    }
}

源码分析

Result API能以近似回调的形式替代startActivityResult,核心是ActivityResultContract这个协议类。ActivityResultContracts中预置了多个ActivityResultContract的实现,StartActivityForResul便是其中之一。

StartActivityForResult

//ActivityResultContracts.java
public static final class StartActivityForResult
            extends ActivityResultContract<Intent, ActivityResult> {

        @NonNull
        @Override
        public Intent createIntent(@NonNull Context context, @NonNull Intent input) {
            return input;
        }

        @NonNull
        @Override
        public ActivityResult parseResult(
                int resultCode, @Nullable Intent intent) {
            return new ActivityResult(resultCode, intent);
        }
    }

ActivityResultContract接受两个泛型,分别表示ActivityResultLauncher的启动参数类型以及onActivityResult返回的结果类型。对于StartActivityForResult来说,通过Intent启动,等待目标Activity返回ActivityResult

自定义ActivityResultContract

当然,你可以自定义ActivityResultContract,接受任意类型的启动参数。然后在createIntent中构造Intent,例如:

//PostActivityContract.kt
class PostActivityContract : ActivityResultContract<Int, String?>() {
    override fun createIntent(context: Context, input: Int): Intent {
        return Intent(context, PostActivity::class.java).apply {
            putExtra("PostActivity.ID", input)
        }
    }

    override fun parseResult(resultCode: Int, intent: Intent?): String? {
        val data = intent?.getStringExtra(PostActivity.TITLE)
        return if (resultCode == Activity.RESULT_OK && data != null) data
        else null
    }
}
// MainActivity.kt
launcher.launch(100089) //launch with Int

private val launcher =
    registerForActivityResult(PostActivityContract()) { result ->
        // Result will return this as string?                                              
        if (result != null) toast("Result : $result")
        else toast("No Result")
    }

ActivityResultRegistry

registerForActivityResult内调用ActivityResultRegistryregister()方法

//ComponentActivity.java

      @NonNull
    @Override
    public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final ActivityResultRegistry registry,
            @NonNull final ActivityResultCallback<O> callback) {
        return registry.register(
                "activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);
    }

    @NonNull
    @Override
    public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
            @NonNull ActivityResultContract<I, O> contract,
            @NonNull ActivityResultCallback<O> callback) {
        return registerForActivityResult(contract, mActivityResultRegistry, callback);
    }

    @NonNull
    @Override
    public final ActivityResultRegistry getActivityResultRegistry() {
        return mActivityResultRegistry;
    }

register()内部会将ActivityResultContract存入一个HashMap中。你也可以不使用mActivityResultRegistry,使用自定义的ActivityResultRegistry,例如

val launcher: ActivityResultLauncher<Intent> = activityResultRegistry
        .register(ActivityResultContracts.StartActivityForResult(),
                "activity_rq#0",
                this, //LifecyleOwner: ComponentActivity
        ) { activityResult: ActivityResult ->
            Log.d("MainActivity", activityResult.toString())
            //  D/MainActivity: ActivityResult{resultCode=RESULT_OK, data=Intent { (has extras) }}
        }

register接受一个LifecycleOwner(ComponentActivity自身),在合适的生命周期将回调存入/移除Map,保证回调响应的时机正确。

LifecycleEventObserver observer = new LifecycleEventObserver() {
            @Override
            public void onStateChanged(
                    @NonNull LifecycleOwner lifecycleOwner,
                    @NonNull Lifecycle.Event event) {
                if (Lifecycle.Event.ON_START.equals(event)) {
                    mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));
                    ...
                } else if (Lifecycle.Event.ON_STOP.equals(event)) {
                    mKeyToCallback.remove(key);
                } else if (Lifecycle.Event.ON_DESTROY.equals(event)) {
                    unregister(key);
                }
            }
        };

ComponentActivity相关实现

ComponentActivity比较简单,持有ActivityResultRegistry,并提供registerForActivityResult方法。ActivityResultRegistry中通过HashMap存储回调,HashMap的key是自增的。

 "activity_rq#" + mNextLocalRequestCode.getAndIncrement()

在这里插入图片描述

还需要requestCode吗

以往onActivityResult需要通过requestCode来识别是哪个startActivityForResult的返回,现在可以通过AutoIncrement来管理。而且当进程被杀时onSaveInstanceState会自动保存requestCode和ActivityResultRegistry的key的pair对,当onActivityResult返回requestCode时,可以通过对应关系找到key,然后找到ActivityResultCallback.

//ActivityResultRegistry.java
private int registerKey(String key) {
        Integer existing = mKeyToRc.get(key);
        if (existing != null) {
            return existing;
        }
        int rc = mNextRc.getAndIncrement();
        bindRcKey(rc, key);
        return rc;
    }

Fragment相关实现

Fragment.registerForActivityResult()内部,在onAttach的时候,会调用getActivity().getActivityResultRegistry()进行register

//Fragment.java
    public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final ActivityResultCallback<O> callback) {
        return prepareCallInternal(contract, new Function<Void, ActivityResultRegistry>() {
            @Override
            public ActivityResultRegistry apply(Void input) {
                if (mHost instanceof ActivityResultRegistryOwner) {
                    return ((ActivityResultRegistryOwner) mHost).getActivityResultRegistry();
                }
                return requireActivity().getActivityResultRegistry();
            }
        }, callback);
    }


    @NonNull
    private <I, O> ActivityResultLauncher<I> prepareCallInternal(
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final Function<Void, ActivityResultRegistry> registryProvider,
            @NonNull final ActivityResultCallback<O> callback) {
...
        registerOnPreAttachListener(new OnPreAttachedListener() {
            @Override
            void onPreAttached() {
                final String key = generateActivityResultKey();
                ActivityResultRegistry registry = registryProvider.apply(null);
                ref.set(registry.register(key, Fragment.this, contract, callback));
            }
        });
        
        return new ActivityResultLauncher<I>() {
            @Override
            public void launch(I input) {
...
            }
        };
    }

    private void registerOnPreAttachListener(@NonNull final OnPreAttachedListener callback) {
        //If we are already attached, we can register immediately
        if (mState >= ATTACHED) {
            callback.onPreAttached();
        } else {
            // else we need to wait until we are attached
            mOnPreAttachedListeners.add(callback);
        }
    }

register虽然将framgent实例注入到上级持有的HashMap,但是此时使用的LifecycleOwner是Fragment自身,前文分析可知,在其ON_DESTROY的时候会进行对应的后处理,因此不必担心造成内存泄漏。

更多预置ActivityResultContract

除了StartActivityFroResult以外,还有很多预知的ActivityResultContract:
在这里插入图片描述

例如,通过GetContent打开文件管理器选择图片并返回URI:

class MainActivity : AppCompatActivity() {

    private val launcher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
        Log.d("MainActivity", "uri: $uri")
    }

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

        button_get_content.setOnClickListener {
            launcher.launch("image/*")
        }
    }
}

通过RequestPermissionRequestMultiplePermission 进行权限申请也将变得更加简单:

request_permission.setOnClickListener {
    requestPermission.launch(permission.BLUETOOTH)
}

request_multiple_permission.setOnClickListener {
    requestMultiplePermissions.launch(
        arrayOf(
            permission.BLUETOOTH,
            permission.NFC,
            permission.ACCESS_FINE_LOCATION
        )
    )
}

// Request permission contract
private val requestPermission =
    registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
        // Do something if permission granted
        if (isGranted) toast("Permission is granted")
        else toast("Permission is denied")
    }

// Request multiple permissions contract
private val requestMultiplePermissions =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions : Map<String, Boolean> ->
        // Do something if some permissions granted or denied
        permissions.entries.forEach {
            // Do checking here
        }                                                                             
}

最后

在这里插入图片描述
通过对比可以清楚看到Result API好处:通过ActivityResultContract避免了对requestCode、onActivityResult的感知,减少了模板代码。更重要的是多种预置ActivityResultContract,极大降低了系统API的通信成本,为日常开发提供了便利,是时候跟startActivityForResult说再见了!

目录
相关文章
|
10月前
|
API Android开发
Android Activity Result API
Android Activity Result API
98 0
|
10天前
|
缓存 前端开发 API
API接口封装系列
API(Application Programming Interface)接口封装是将系统内部的功能封装成可复用的程序接口并向外部提供,以便其他系统调用和使用这些功能,通过这种方式实现系统之间的通信和协作。下面将介绍API接口封装的一些关键步骤和注意事项。
|
17天前
|
监控 前端开发 JavaScript
实战篇:商品API接口在跨平台销售中的有效运用与案例解析
随着电子商务的蓬勃发展,企业为了扩大市场覆盖面,经常需要在多个在线平台上展示和销售产品。然而,手工管理多个平台的库存、价格、商品描述等信息既耗时又容易出错。商品API接口在这一背景下显得尤为重要,它能够帮助企业在不同的销售平台之间实现商品信息的高效同步和管理。本文将通过具体的淘宝API接口使用案例,展示如何在跨平台销售中有效利用商品API接口,以及如何通过代码实现数据的统一管理。
|
29天前
|
安全 算法 API
产品经理必备知识——API接口
前言 在古代,我们的传输信息的方式有很多,比如写信、飞鸽传书,以及在战争中使用的烽烟,才有了著名的烽火戏诸侯,但这些方式传输信息的效率终究还是无法满足高速发展的社会需要。如今万物互联的时代,我通过一部手机就可以实现衣食住行的方方面面,比如:在家购物、远程控制家电、自动驾驶等等,背后都离不开我们今天要聊的API接口。
|
29天前
|
数据采集 JSON API
如何实现高效率超简洁的实时数据采集?——Python实战电商数据采集API接口
你是否曾为获取重要数据而感到困扰?是否因为数据封锁而无法获取所需信息?是否因为数据格式混乱而头疼?现在,所有这些问题都可以迎刃而解。让我为大家介绍一款强大的数据采集API接口。
|
1月前
|
安全 API 数据安全/隐私保护
API接口知识小结
应用程序接口API(Application Programming Interface),是提供特定业务输出能力、连接不同系统的一种约定。这里包括外部系统与提供服务的系统(中后台系统)或后台不同系统之间的交互点。包括外部接口、内部接口,内部接口又包括:上层服务与下层服务接口、同级接口。
|
4天前
|
人工智能 API 开发者
免费使用Kimi的API接口,kimi-free-api真香
今年AI应用兴起,各类智能体涌现,但API免费额度有限。为解决这一问题,GitHub上的[kimi-free-api](https://github.com/LLM-Red-Team/kimi-free-api)项目提供了方便,支持高速流式输出、多轮对话等,与ChatGPT接口兼容。此外,还有其他大模型的免费API转换项目,如跃问StepChat、阿里通义Qwen等。该项目可帮助用户免费体验,通过Docker-compose轻松部署。只需获取refresh_token,即可开始使用。这个开源项目促进了AI学习和开发,为探索AI潜力提供了新途径。
173 2
|
9天前
|
JSON 监控 API
在API接口对接中关键示例问题(1)
在API接口对接中,有几个关键的问题需要注意,以确保接口的稳定性、安全性和易用性。以下是这些问题及部分示例代码的简要概述
|
18天前
|
XML API 网络架构
API 常用的接口类型都有哪些?
在软件开发的宏大舞台上,接口充当着不可或缺的角色,确保了不同的软件模块能够高效、无缝地沟通和协作。
|
18天前
|
监控 API 开发者
邮件发送API接口配置步骤?
`邮件发送API让开发者轻松集成邮件功能。选择服务提供商如SendGrid、Mailgun或AWS SES,注册获取API密钥。配置发件人、收件人、主题和内容,调用API发送邮件。处理响应以确认发送成功,并监控性能进行优化。API简化了邮件发送,提升开发效率。`