Android Activity Result API

1.Activity Results API

在appcompat库1.3.0或更高的版本中,startActivityForResult()方法被废弃了。这个方法主要是用于在两个Activity之间交换数据的。Google官方建议使用Activity Result API来实现在两个Activity或Fragment之间交换数据、获取返回结果的功能。

Activity Result API主要用到两个组件:

①ActivityResultContract: 协议,它定义了如何传递数据和如何处理返回的数据。

ActivityResultContract是一个抽象类,需要继承它来创建自己的协议,每个 ActivityResultContract都需要定义输出和输入类,如果不需要任何输出,可应用void(在Kotlin中应用Void?或Unit)作为输出类型。

②ActivityResultLauncher: 启动器,调用它的launch方法来启动页面跳转,作用相当于原来的startActivity()。

 

2.使用方法

①定义协议

首先自定义一个Contract用于两个activity互传数据处理。自定义Contract需要继承自ActivityResultContract,其中I是输入的类型,O是输出的类型,并实现2个方法:createIntent和parseResult,输入类型I作为createIntent方法的参数,输出类型O作为parseResult方法的返回值。

class MyActivityResultContract : ActivityResultContract() { 

    override fun createIntent(context: Context, input: String?): Intent { 

        return Intent(context, SecondActivity::class.java).apply { 

            putExtra("input", input) 

        } 

    } 

    override fun parseResult(resultCode: Int, intent: Intent?): String? { 

        val data = intent?.getStringExtra("result") 

        return if (resultCode == Activity.RESULT_OK && !TextUtils.isEmpty(data)) data else null 

    } 

在createIntent方法中创建Intent,并携带了参数input,在parseResult方法中获取返回的数据result。

②注册协议,获取启动器ActivityResultLauncher

调用registerForActivityResult方法注册刚刚定义的contract协议,该方法由ComponentActivity或Fragment提供,有2个参数,第一个参数是自定义的Contract协定,第二个参数是一个回调ActivityResultCallback,其中O就是Contract的输出类型;返回一个activityResultLauncher对象。 

val activityResultLauncher = registerForActivityResult(MyActivityResultContract()) { result ->

    Toast.makeText(this, "result value is :$result", Toast.LENGTH_LONG).show() 

}

注册了MyActivityResultContract,registerForActivityResult方法的返回值是ActivityResultLauncher。回调方法中result就是从上一个界面传回的值,这里简略的用Toast显示。

③调用启动器的launch办法开启界面跳转

用返回的launcher对象启动另一个activity界面

btn_start_second.setOnClickListener { 

    activityResultLauncher.launch("second activity start") 

④新页面通过setResult(int resultCode, Intent data)回传数据。

SecondActivity中的代码不需要修改,这部分代码并没有被废弃,Activity Result API与它无关。

class SecondActivity: AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_second)

        val input = intent.getStringExtra("input")

        textview.text = "接到的数据为: $input"

        btn_second.setOnClickListener {

            val intent = Intent().apply {

                putExtra("result", "我是从second activity回传回来的数据")

            }

            setResult(RESULT_OK, intent)

            finish()

        }

    }

}

这样就完成了用Activity Result API实现Activity之间的数据传递,并获取到Activity返回的结果。

使用Activity Result API完全移除了对onActivityResult()方法的重写,而是通过调用registerForActivityResult()方法注册了一个对Activity结果的监听。

 

3.内置的Contract

使用Activity Result API每次都得定义Contract,因此Google内置了很多Contract,它们都定义在类ActivityResultContracts中:

①StartActivityForResult:通用的Contract,不做任何转换,Intent作为输入,ActivityResult作为输出,这也是最常用的一个协定。

②RequestMultiplePermissions:用于申请一组权限

③RequestPermission:用于申请单个权限

④TakePicturePreview: 调用MediaStore.ACTION_IMAGE_CAPTURE拍照,返回值为Bitmap图片

⑤TakePicture: 调用MediaStore.ACTION_IMAGE_CAPTURE拍照,并将图片保存到给定的Uri地址,返回true表示保存成功。

⑥TakeVideo: 调用MediaStore.ACTION_VIDEO_CAPTURE 拍摄视频,保留到给定的Uri地址,返回一张缩略图。

⑦PickContact: 从通讯录APP获取联系人

⑧GetContent: 提醒用户选择一条内容,返回一个通过ContentResolver#openInputStream(Uri)访问原生数据的Uri地址(content://模式)。默认状况下,它减少了 Intent#CATEGORY_OPENABLE,返回能够表示流的内容。

⑨CreateDocument: 提醒用户选择一个文档,返回一个(file:/http:/content:)结尾的Uri。

⑩OpenMultipleDocuments: 提醒用户选择文档(能够选择多个),分别返回它们的Uri,以List的模式。

⑩OpenDocumentTree: 提醒用户选择一个目录,并返回用户选择的作为一个Uri返回。

这些内置的Contract中,除了StartActivityForResult和RequestMultiplePermissions外,根本都是解决的与其它APP交互、返回数据的场景,比如拍照、选择图片、选择联系人、打开文档等。应用最多的就是StartActivityForResult和RequestMultiplePermissions了。

有了这些内置的Contract,Activity之间传递数据就简单多了。

1)比如上面的例子,能够简化成这样:

①注册协定,获取ActivityResultLauncher:

 private val myActivityLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()){ activityResult ->  

    if(activityResult.resultCode == Activity.RESULT_OK){

        val result = activityResult.data?.getStringExtra("result")

        Toast.makeText(applicationContext, result, Toast.LENGTH_SHORT).show()

        textView.text = "回传数据:$result"

    }

}

②启动页面跳转

 button.setOnClickListener {

    val intent = Intent(this, SecondActivity::class.java).apply {

         putExtra("input","Hello,技术最TOP")

    }

    myActivityLauncher.launch(intent)

}

2)再比如,权限申请:

request_permission.setOnClickListener {

    requestPermission.launch( permission.BLUETOOTH)

}

request_multiple_permission.setOnClickListener {

    requestMultiplePermissions.launch(

        arrayOf(

            permission.BLUETOOTH,

            permission.NFC,

            permission.ACCESS_FINE_LOCATION

        )

    )

}

// 申请单个权限

private val requestPermission =

    registerForActivityResult( ActivityResultContracts.RequestPermission()) { isGranted ->

        if (isGranted) toast("Permission is granted")

        else toast("Permission is denied")

    }

// 申请一组权限

private val requestMultiplePermissions =

    registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions()) { permissions : Map ->

        permissions.entries.forEach {

            // Do checking here

        }                                                                     

}

由于是请求运行时权限,因此不能再使用刚才的StartActivityForResult来作为Contract了,而是要使用RequestPermission这种Contract。

由于指定了不同的Contract,Lambda表达式的参数也会发生变化。现在Lambda表达式会传入一个布尔型的参数,用于告诉用户是否允许了请求的权限。

最后,launch()方法的参数也发生了变化,现在只需传入要请求的权限名即可。

有了这个就能够摒弃所有的第三方权限申请框架,只需要将这两个Contract放到BaseActivity中,或者抽取到一个独自的类中,就能随时随地申请权限。

这两段代码的模板很相似,使用两段差不多的代码就实现了之前几乎并没有太大联系的两个功能。这就是Activity Result API的好处,它将一些API的接口统一化,使得在实现特定功能的时候能够变得非常简单。

 

4.Activity Result API原理

1)Activity Result API由Launcher、Contract、Callback三个要素组成。

@Override

public final ActivityResultLauncher registerForActivityResult(ActivityResultContract contract, ActivityResultCallback callback) {

    return registerForActivityResult(contract, mActivityResultRegistry, callback);

}

①ActivityResultLauncher

public abstract class ActivityResultLauncher {

    public void launch(I input) {

        launch(input, null);

    }

    public abstract void launch(I input, ActivityOptionsCompat options);

    @MainThread

    public abstract void unregister();

    public abstract ActivityResultContract getContract();

}

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

②ActivityResultContract

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

public abstract class ActivityResultContract {

    public abstract Intent createIntent(Context context, I input);

    public abstract O parseResult(int resultCode, Intent intent);

    public SynchronousResult getSynchronousResult(Context context, I input){

        return null;

    }

    public static final class SynchronousResult {

        private final T mValue;

        public SynchronousResult(T value) {

            this.mValue = value;

        }

        public T getValue() {

            return mValue;

        }

    }

}

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

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

比如最常用的跳转新页面回传数据StartActivityForResult:

public static final class StartActivityForResult extends ActivityResultContract {

    public static final String EXTRA_ACTIVITY_OPTIONS_BUNDLE = "androidx.activity.result" + ".contract.extra.ACTIVITY_OPTIONS_BUNDLE";

    @Override

    public Intent createIntent(Context context, Intent input) {

        return input;

    }

    @Override

    public ActivityResult parseResult(int resultCode, Intent intent) {

        return new ActivityResult(resultCode, intent);

    }

}

继承ActivityResultContract,约定输入类型为Intent,结果返回类型为ActivityResult。在createIntent方法中因为输入类型就是Intent,所以没做处理,直接返回。parseResult方法中根据指定的resultCode和intent,创建了一个ActivityResult实例返回。

再看一个ActivityResultContracts.TakePicturePreview:

public static class TakePicturePreview extends ActivityResultContract {

    @Override

    public Intent createIntent(Context context, Void input) {

        return new Intent( MediaStore.ACTION_IMAGE_CAPTURE);

    }

    @Override

    public final SynchronousResult getSynchronousResult(Context context,Void input) {

        return null;

    }

    @Override

    public final Bitmap parseResult(int resultCode, 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 {

    void onActivityResult(O result);

}

2)registerForActivityResult在Activity中的实现

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

@Override

public final ActivityResultLauncher registerForActivityResult(final ActivityResultContract contract, final ActivityResultRegistry registry, final ActivityResultCallback callback) {

    return registry.register("activity_rq#" + mNextLocalRequestCode.getAndIncrement(), this, contract, callback);

}

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

继续ActivityResultRegistry的register方法:

public final ActivityResultLauncher register(final String key, final LifecycleOwner lifecycleOwner, final ActivityResultContract contract, final ActivityResultCallback 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( LifecycleOwner lifecycleOwner, Lifecycle.Event event) {

            if (Lifecycle.Event.ON_START.equals( event)) {

                mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));

                if (mParsedPendingResults.contai nsKey(key)) {

                    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.equ als(event)) {

                mKeyToCallback.remove(key);

            } else if (Lifecycle.Event.ON_DESTRO Y.equals(event)) {

                unregister(key);

            }

        }

    };

    //为LifecycleContainer实例添加观察者

    lifecycleContainer.addObserver(observer);

    mKeyToLifecycleContainers.put(key, lifecycleContainer);

    //返回了一个ActivityResultLauncher实例

    return new ActivityResultLauncher() {

        @Override

        public void launch(I input, 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);

        }

        @Override

        public ActivityResultContract getContract() {

            return contract;

        }

    };

}

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

3)onLaunch在Activity中的实现

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

this.mActivityResultRegistry = new ActivityResultRegistry() {

    public void onLaunch(final int requestCode, ActivityResultContract contract, I input, ActivityOptionsCompat options) {

        ComponentActivity activity = ComponentActivity.this;

        final SynchronousResult 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.res ult.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.ac tion.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.contr act.action.INTENT_SENDER_REQUEST".equals(intent.getAction())) {

                IntentSenderRequest request = (IntentSenderRequest)intent.getParcelableExtra("androidx.activity.result.contract.extra.INTENT_SENDER_REQUEST");

                try {

                    ActivityCompat.startIntentSende rForResult(activity, request.getIntentSender(), requestCode, request.getFillInIntent(), request.getFlagsMask(), request.getFlagsValues(), 0, optionsBundle);

                } catch (final SendIntentException var11) {

                    (new Handler(Looper.getMainLoo per())).post(new Runnable() {

                        public void run() {

                            dispatchResult( requestCode, 0, (new Intent()).setAction("androidx.act ivity.result.contract.action.INTENT_SENDER_REQUEST").putExtra("androidx.activity.result.contract.extra.SEND_INTENT_EXCEPTION", var11));

                        }

                    });

                }

            } else {

                ActivityCompat.startActivityForRes ult(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, Intent data) {

    if (!mActivityResultRegistry.dispatchResult( requestCode, resultCode, data)) {

        super.onActivityResult(requestCode, resultCode, data);

    }

}

@MainThread

public final boolean dispatchResult(int requestCode, int resultCode, Intent data) {

    String key = mRcToKey.get(requestCode);

    if (key == null) {

        return false;

    }

    doDispatch(key, resultCode, data, mKeyToCallback.get(key));

    return true;

}

private void doDispatch(String key, int resultCode, Intent data, CallbackAndContract callbackAndContract) {

    if (callbackAndContract != null && callbackAndContract.mCallback != null && mLaunchedKeys.contains(key)) {

        ActivityResultCallback callback = callbackAndContract.mCallback;

        ActivityResultContract contract = callbackAndContract.mContract;

        callback.onActivityResult( contract.parseResult(resultCode, data));

        mLaunchedKeys.remove(key);

    } else {

        mParsedPendingResults.remove(key);

        mPendingResults.putParcelable(key, new ActivityResult(resultCode, data));

    }

}

在Android13系统上,回到页面时先ON_START会添加观察者,再onActivityResult回调,没有问题。

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

你可能感兴趣的:(android)