Android Activity Result API使用

2022-07-20  本文已影响0人  愿天深海

在Android开发中时常需要用到跳转新页面获取结果回传数据,一直以来使用的方法就是startActivityForResult和onActivityResult两个方法,但是startActivityForResult方法却已经被deprecation,官方推荐使用Activity Result API。

跳转新页面回传数据之startActivityForResult

操作步骤一般为三步:
1、定义REQUEST_CODE,同一个页面有多个数据时,避免重复;
2、调用 startActivityForResult(Intent, REQUEST_CODE)进行新页面的跳转;
3、重写 onActivityResult(),判断requestCode和resultCode,获取到回传数据执行后续逻辑。

示例:

class MainActivity : AppCompatActivity() {
    companion object {
        private const val REQUEST_CODE_1 = 20
        private const val REQUEST_CODE_2 = 21
    }

    private val binding: ActivityMainBinding by lazy {
        ActivityMainBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportActionBar?.hide()
        setContentView(binding.root)
        binding.click1.setOnClickListener {
            startActivityForResult(Intent(this, SecondActivity::class.java), REQUEST_CODE_1)
        }
        binding.click2.setOnClickListener {
            //ARouter里仍然是使用startActivityForResult
            ARouter.getInstance()
                .build(ARouterPath.SecondActivity)
                .navigation(this, REQUEST_CODE_2)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == RESULT_OK) {
            when (requestCode) {
                REQUEST_CODE_1 -> {
                    Toast.makeText(
                        this,
                        "startActivityForResult回调:${data?.getStringExtra("data")}",
                        Toast.LENGTH_SHORT
                    )
                        .show()
                }
                REQUEST_CODE_2 -> {
                    Toast.makeText(
                        this,
                        "ARouter回调:${data?.getStringExtra("data")}",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
        }
    }
}

即使是大家常用的使用ARouter管理页面路由的方式,通过navigation跳转,也是塞入了REQUEST_CODE,其内部也是使用startActivityForResult方法跳转。

private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) {
        if (requestCode >= 0) {  // Need start for result
            if (currentContext instanceof Activity) {
                ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
            } else {
                logger.warning(Consts.TAG, "Must use [navigation(activity, ...)] to support [startActivityForResult]");
            }
        } else {
            ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
        }

        if ((-1 != postcard.getEnterAnim() && -1 != postcard.getExitAnim()) && currentContext instanceof Activity) {    // Old version.
            ((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
        }

        if (null != callback) { // Navigation over.
            callback.onArrival(postcard);
        }
    }

在新页面通过一个简单的setResult(int resultCode, Intent data),在关闭新页面回到原页面时,在原页面的onActivityResult方法就能拿到回传数据。

跳转新页面回传数据之Activity Result API

使用Activity Result API进行跳转新页面回传数据,操作分为两步:
1、通过registerForActivityResult方法定义一个函数处理回传数据;
2、通过launch()进行新页面的跳转。

示例:

//使用registerForActivityResult
        val secondLauncher =
            registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
                if (it.resultCode == RESULT_OK) {
                    it.data?.getStringExtra("data")?.let {
                        Toast.makeText(
                            this,
                            "registerForActivityResult回调:${it}",
                            Toast.LENGTH_SHORT
                        )
                            .show()
                    }
                }
            }
        binding.click3.setOnClickListener {
            secondLauncher.launch(Intent(this, SecondActivity::class.java))
        }

新页面仍然是通过setResult(int resultCode, Intent data)回传数据,可以看到原页面上移除了onActivityResult()的重写,并且少写了一个REQUEST_CODE。

探查Activity Result API原理

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

Activity Results API由三个要素组成,Launcher、Contract、Callback

ActivityResultLauncher

public abstract class ActivityResultLauncher<I> {

    /**
     * Executes an {@link ActivityResultContract}.
     *
     * <p>This method throws {@link android.content.ActivityNotFoundException}
     * if there was no Activity found to run the given Intent.

     * @param input the input required to execute an {@link ActivityResultContract}.
     *
     * @throws android.content.ActivityNotFoundException
     */
    public void launch(@SuppressLint("UnknownNullness") I input) {
        launch(input, null);
    }

    /**
     * Executes an {@link ActivityResultContract}.
     *
     * <p>This method throws {@link android.content.ActivityNotFoundException}
     * if there was no Activity found to run the given Intent.
     *
     * @param input the input required to execute an {@link ActivityResultContract}.
     * @param options Additional options for how the Activity should be started.
     *
     * @throws android.content.ActivityNotFoundException
     */
    public abstract void launch(@SuppressLint("UnknownNullness") I input,
            @Nullable ActivityOptionsCompat options);

    /**
     * Unregisters this launcher, releasing the underlying result callback, and any references
     * captured within it.
     *
     * You should call this if the registry may live longer than the callback registered for this
     * launcher.
     */
    @MainThread
    public abstract void unregister();

    /**
     * Get the {@link ActivityResultContract} that was used to create this launcher.
     *
     * @return the contract that was used to create this launcher
     */
    @NonNull
    public abstract ActivityResultContract<I, ?> getContract();
}

ActivityResultLauncher是registerForActivityResult的返回值,用于连接启动对象和返回对象的。

ActivityResultContract

ActivityResultContract是registerForActivityResult的第一个入参,约定了一个输入类型和一个结果的返回类型。

public abstract class ActivityResultContract<I, O> {

    /** Create an intent that can be used for {@link Activity#startActivityForResult} */
    public abstract @NonNull Intent createIntent(@NonNull Context context,
            @SuppressLint("UnknownNullness") I input);

    /** Convert result obtained from {@link Activity#onActivityResult} to O */
    @SuppressLint("UnknownNullness")
    public abstract O parseResult(int resultCode, @Nullable Intent intent);

    /**
     * An optional method you can implement that can be used to potentially provide a result in
     * lieu of starting an activity.
     *
     * @return the result wrapped in a {@link SynchronousResult} or {@code null} if the call
     * should proceed to start an activity.
     */
    public @Nullable SynchronousResult<O> getSynchronousResult(
            @NonNull Context context,
            @SuppressLint("UnknownNullness") I input) {
        return null;
    }

    /**
     * The wrapper for a result provided in {@link #getSynchronousResult}
     *
     * @param <T> type of the result
     */
    public static final class SynchronousResult<T> {
        private final @SuppressLint("UnknownNullness") T mValue;

        /**
         * Create a new result wrapper
         *
         * @param value the result value
         */
        public SynchronousResult(@SuppressLint("UnknownNullness") T value) {
            this.mValue = value;
        }

        /**
         * @return the result value
         */
        public @SuppressLint("UnknownNullness") T getValue() {
            return mValue;
        }
    }
}

ActivityResultContract内主要就两个方法,createIntent()方法创建一个Intent用于startActivityForResult,parseResult()方法对onActivityResult的结果进行转换。

ActivityResultContracts里提供了常用的ActivityResultContract,可以直接拿来使用。


image.png

比如我们最常用的跳转新页面回传数据:ActivityResultContracts.StartActivityForResult()

    public static final class StartActivityForResult
            extends ActivityResultContract<Intent, ActivityResult> {

        /**
         * Key for the extra containing a {@link android.os.Bundle} generated from
         * {@link androidx.core.app.ActivityOptionsCompat#toBundle()} or
         * {@link android.app.ActivityOptions#toBundle()}.
         *
         * This will override any {@link ActivityOptionsCompat} passed to
         * {@link androidx.activity.result.ActivityResultLauncher#launch(Object,
         ActivityOptionsCompat)}
         */
        public static final String EXTRA_ACTIVITY_OPTIONS_BUNDLE = "androidx.activity.result"
                + ".contract.extra.ACTIVITY_OPTIONS_BUNDLE";

        @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,约定输入类型为Intent,结果返回类型为ActivityResult。在createIntent方法中因为输入类型就是Intent,所以没做处理,直接返回。parseResult方法中根据指定的resultCode和intent,创建了一个ActivityResult实例返回。

再看一个ActivityResultContracts.TakePicturePreview()

    public static class TakePicturePreview extends ActivityResultContract<Void, Bitmap> {

        @CallSuper
        @NonNull
        @Override
        public Intent createIntent(@NonNull Context context, @Nullable Void input) {
            return new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        }

        @Nullable
        @Override
        public final SynchronousResult<Bitmap> getSynchronousResult(@NonNull Context context,
                @Nullable Void input) {
            return null;
        }

        @Nullable
        @Override
        public final Bitmap parseResult(int resultCode, @Nullable Intent intent) {
            if (intent == null || resultCode != Activity.RESULT_OK) return null;
            return intent.getParcelableExtra("data");
        }
    }

输入类型为Void,因为在createIntent中自己创建了一个MediaStore.ACTION_IMAGE_CAPTURE的Intent实例。parseResult中根据指定的intent中获取到Bitmap实例返回。
如果ActivityResultContracts里常用的这些无法满足需求,自然也可以自定义一波,实现相应的createIntent方法和parseResult方法即可。

ActivityResultCallback

顾名思义,就是结果回调。

public interface ActivityResultCallback<O> {

    /**
     * Called when result is available
     */
    void onActivityResult(@SuppressLint("UnknownNullness") O result);
}

registerForActivityResult在Activity中的实现

在Activity、Fragment中可以直接使用registerForActivityResult(),是因为ComponentActivity和Fragment都实现了ActivityResultCaller接口。

    @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);
    }

第一个参数使用"activity_rq#" + mNextLocalRequestCode.getAndIncrement()构造了一个key,mNextLocalRequestCode是一个AtomicInteger值,使用这种方式就不需要额外定义REQUEST_CODE来进行区分了。
继续ActivityResultRegistry的register方法:

    @NonNull
    public final <I, O> ActivityResultLauncher<I> register(
            @NonNull final String key,
            @NonNull final LifecycleOwner lifecycleOwner,
            @NonNull final ActivityResultContract<I, O> contract,
            @NonNull final ActivityResultCallback<O> callback) {

        //获取到当前生命周期组件的lifecycle
        Lifecycle lifecycle = lifecycleOwner.getLifecycle();
        //register要在当前生命周期组件处于STARTED状态之前调用
        if (lifecycle.getCurrentState().isAtLeast(Lifecycle.State.STARTED)) {
            throw new IllegalStateException("LifecycleOwner " + lifecycleOwner + " is "
                    + "attempting to register while current state is "
                    + lifecycle.getCurrentState() + ". LifecycleOwners must call register before "
                    + "they are STARTED.");
        }
        //通过传入的key生成requestCode
        final int requestCode = registerKey(key);
        //通过key在集合中获取LifecycleContainer实例,没有则生成一个
        LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key);
        if (lifecycleContainer == null) {
            lifecycleContainer = new LifecycleContainer(lifecycle);
        }
        //生成观察者,当状态为ON_START时执行回调,为ON_STOP时移除与回调的关联,为ON_DESTROY时取消注册
        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));
                    if (mParsedPendingResults.containsKey(key)) {
                        @SuppressWarnings("unchecked")
                        final O parsedPendingResult = (O) mParsedPendingResults.get(key);
                        mParsedPendingResults.remove(key);
                        callback.onActivityResult(parsedPendingResult);
                    }
                    final ActivityResult pendingResult = mPendingResults.getParcelable(key);
                    if (pendingResult != null) {
                        mPendingResults.remove(key);
                        callback.onActivityResult(contract.parseResult(
                                pendingResult.getResultCode(),
                                pendingResult.getData()));
                    }
                } else if (Lifecycle.Event.ON_STOP.equals(event)) {
                    mKeyToCallback.remove(key);
                } else if (Lifecycle.Event.ON_DESTROY.equals(event)) {
                    unregister(key);
                }
            }
        };
        //为LifecycleContainer实例添加观察者
        lifecycleContainer.addObserver(observer);
        mKeyToLifecycleContainers.put(key, lifecycleContainer);
        //返回了一个ActivityResultLauncher实例
        return new ActivityResultLauncher<I>() {
            @Override
            public void launch(I input, @Nullable ActivityOptionsCompat options) {
                mLaunchedKeys.add(key);
                Integer innerCode = mKeyToRc.get(key);
                onLaunch((innerCode != null) ? innerCode : requestCode, contract, input, options);
            }

            @Override
            public void unregister() {
                ActivityResultRegistry.this.unregister(key);
            }

            @NonNull
            @Override
            public ActivityResultContract<I, ?> getContract() {
                return contract;
            }
        };
    }

在register方法中,首先获取到当前生命周期组件的lifecycle。然后register要在当前生命周期组件处于STARTED状态之前调用。通过传入的key生成requestCode。通过key在集合中获取LifecycleContainer实例,没有则生成一个。生成观察者,当状态为ON_START时执行回调,为ON_STOP时移除与回调的关联,为ON_DESTROY时取消注册。为LifecycleContainer实例添加观察者。最终返回了一个ActivityResultLauncher实例。

onLaunch在Activity中的实现

在registerForActivityResult中最终返回了ActivityResultLauncher实例,而ActivityResultLauncher的launch方法里调用了ActivityResultRegistry.onLaunch方法,该方法是一个抽象方法,其实现在ComponentActivity中。

        this.mActivityResultRegistry = new ActivityResultRegistry() {
            public <I, O> void onLaunch(final int requestCode, @NonNull ActivityResultContract<I, O> contract, I input, @Nullable ActivityOptionsCompat options) {
                ComponentActivity activity = ComponentActivity.this;
                final SynchronousResult<O> synchronousResult = contract.getSynchronousResult(activity, input);
                if (synchronousResult != null) {
                    //不需要启动Activity就能知道结果的场景处理
                    (new Handler(Looper.getMainLooper())).post(new Runnable() {
                        public void run() {
                            dispatchResult(requestCode, synchronousResult.getValue());
                        }
                    });
                } else {
                    //需要启动Activity才能知道结果的场景处理
                    //通过ActivityResultContract.createIntent初始化Intent实例
                    Intent intent = contract.createIntent(activity, input);
                    //初始化Bundle
                    Bundle optionsBundle = null;
                    if (intent.getExtras() != null && intent.getExtras().getClassLoader() == null) {
                        intent.setExtrasClassLoader(activity.getClassLoader());
                    }

                    if (intent.hasExtra("androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE")) {
                        optionsBundle = intent.getBundleExtra("androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE");
                        intent.removeExtra("androidx.activity.result.contract.extra.ACTIVITY_OPTIONS_BUNDLE");
                    } else if (options != null) {
                        optionsBundle = options.toBundle();
                    }
                    //如果是权限申请,请求权限
                    if ("androidx.activity.result.contract.action.REQUEST_PERMISSIONS".equals(intent.getAction())) {
                        String[] permissions = intent.getStringArrayExtra("androidx.activity.result.contract.extra.PERMISSIONS");
                        if (permissions == null) {
                            permissions = new String[0];
                        }

                        ActivityCompat.requestPermissions(activity, permissions, requestCode);
                    } else if ("androidx.activity.result.contract.action.INTENT_SENDER_REQUEST".equals(intent.getAction())) {
                        IntentSenderRequest request = (IntentSenderRequest)intent.getParcelableExtra("androidx.activity.result.contract.extra.INTENT_SENDER_REQUEST");

                        try {
                            ActivityCompat.startIntentSenderForResult(activity, request.getIntentSender(), requestCode, request.getFillInIntent(), request.getFlagsMask(), request.getFlagsValues(), 0, optionsBundle);
                        } catch (final SendIntentException var11) {
                            (new Handler(Looper.getMainLooper())).post(new Runnable() {
                                public void run() {
                                    dispatchResult(requestCode, 0, (new Intent()).setAction("androidx.activity.result.contract.action.INTENT_SENDER_REQUEST").putExtra("androidx.activity.result.contract.extra.SEND_INTENT_EXCEPTION", var11));
                                }
                            });
                        }
                    } else {
                        ActivityCompat.startActivityForResult(activity, intent, requestCode, optionsBundle);
                    }
                }
            }
        };

首先区分是否需要启动Activity,需要启动Activity的情况下通过ActivityResultContract.createIntent初始化Intent实例,初始化Bundle,最终也是通过ActivityCompat.startActivityForResult跳转新页面。

总结

后记

当一个页面上需要一下子通过同一个ActivityResultLauncher打开多个页面时,发现在不同Android版本上表现不一样。
每一个registerForActivityResult内部会生成一个RequestCode作为key,ActivityResultLauncher有一个观察者队列,ON_START会添加观察者,ON_STOP会移除观察者。
当onActivityResult回调时,执行dispatchResult方法,从观察者队列中取出观察者进行回传,进行doDispatch方法。
关键在doDispatch方法中,有观察者进行观察者的onActivityResult回调,没有观察者,使用key将数据存储在bundle信息中。

    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (!mActivityResultRegistry.dispatchResult(requestCode, resultCode, data)) {
            super.onActivityResult(requestCode, resultCode, data);
        }
    }

    @MainThread
    public final boolean dispatchResult(int requestCode, int resultCode, @Nullable Intent data) {
        String key = mRcToKey.get(requestCode);
        if (key == null) {
            return false;
        }
        doDispatch(key, resultCode, data, mKeyToCallback.get(key));
        return true;
    }

    private <O> void doDispatch(String key, int resultCode, @Nullable Intent data,
            @Nullable CallbackAndContract<O> callbackAndContract) {
        if (callbackAndContract != null && callbackAndContract.mCallback != null
                && mLaunchedKeys.contains(key)) {
            ActivityResultCallback<O> callback = callbackAndContract.mCallback;
            ActivityResultContract<?, O> contract = callbackAndContract.mContract;
            callback.onActivityResult(contract.parseResult(resultCode, data));
            mLaunchedKeys.remove(key);
        } else {
            // Remove any parsed pending result
            mParsedPendingResults.remove(key);
            // And add these pending results in their place
            mPendingResults.putParcelable(key, new ActivityResult(resultCode, data));
        }
    }

在Android13系统上,回到页面时先ON_START会添加观察者,再onActivityResult回调,没有问题。
而在Android10系统上,回到页面时先onActivityResult回调,由于观察者还未添加回队列,所以使用key存储bundle信息的,因此多次使用同一个registerForActivityResult时会丢失数据,使用key存储bundle信息会覆盖,只留下最后一次返回的bundle信息。

上一篇下一篇

猜你喜欢

热点阅读