丢掉 onActivityResult,拥抱 Result API
AndroidX 自
Activity:1.2.0-alpha02
和Fragment: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 != 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 内部调用 ActivityResultRegistry
的 register()
方法
//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/*")
}
}
}
通过RequestPermission
和RequestMultiplePermission
进行权限申请也将变得更加简单:
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 的使用流程
推荐阅读:
↓关注公众号↓ | ↓添加微信交流↓ |
---|---|