美文网首页
Android Activity Result API使用

Android Activity Result API使用

作者: 愿天深海 | 来源:发表于2022-07-20 22:50 被阅读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跳转新页面。

    总结

    • ComponentActivity内部初始化了一个ActivityResultRegistry实例,并重写了 onLaunch()。
    • 调用registerForActivityResult() 最终调用ActivityResultRegistry.register(),在此添加了一个观察者,当生命周期状态切换到ON_START时,执行Contract.parseResult()生成输出内容,并把结果作为参数传入回调callback.onActivityResult()中。
    • 调用ActivityResultLauncher.launch() 才会发起跳转,其中回调了onLaunch()方法,在此调用了Contract.createIntent()创建一个和startActivityForResult()搭配使用的Intent实例。
    • 跳转目标Activity后返回此页面,生命周期发生改变,在观察者中就会执行回调的相关代码。

    后记

    当一个页面上需要一下子通过同一个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信息。

    相关文章

      网友评论

          本文标题:Android Activity Result API使用

          本文链接:https://www.haomeiwen.com/subject/tiihirtx.html