查看原文
其他

丢掉 onActivityResult,拥抱 Result API

fundroid AndroidPub 2022-05-15


AndroidX 自 Activity:1.2.0-alpha02Fragment:1.3.0-alpha02 起追加了 Result API ,使用 ActivityResultContract 替代 startActivityForResult,更加高效且typesafe地处理跨 Activity 通信。

如何使用

在 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 != nulldata
        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 的使用流程

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



推荐阅读:


Compose 1.0 即将发布,你准备好了吗?


MVI 架构:从双向绑定到单向数据流


入木三分:从设计者角度看Retrofit原理


打造一个 Compose 版的俄罗斯方块



↓关注公众号↓↓添加微信交流↓



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存