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>,其中I是输入的类型,O是输出的类型,并实现2个方法:createIntent和parseResult,输入类型I作为createIntent方法的参数,输出类型O作为parseResult方法的返回值。

class MyActivityResultContract : ActivityResultContract<String, String?>() { 

    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>,其中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") 

launch方法的参数就是要传递给下一个页面的数据。

④新页面通过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了。

有了这些内置的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<String, Boolean> ->

        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 <I, O> ActivityResultLauncher<I> registerForActivityResult(ActivityResultContract<I, O> contract, ActivityResultCallback<O> callback) {

    return registerForActivityResult(contract, mActivityResultRegistry, callback);

}

①ActivityResultLauncher

public abstract class ActivityResultLauncher<I> {

    public void launch(I input) {

        launch(input, null);

    }

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

    @MainThread

    public abstract void unregister();

    public abstract ActivityResultContract<I, ?> getContract();

}

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

②ActivityResultContract

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

public abstract class ActivityResultContract<I, O> {

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

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

    public SynchronousResult<O> getSynchronousResult(Context context, I input){

        return null;

    }

    public static final class SynchronousResult<T> {

        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<Intent, ActivityResult> {

    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<Void, Bitmap> {

    @Override

    public Intent createIntent(Context context, Void input) {

        return new Intent( MediaStore.ACTION_IMAGE_CAPTURE);

    }

    @Override

    public final SynchronousResult<Bitmap> 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<O> {

    void onActivityResult(O result);

}

2)registerForActivityResult在Activity中的实现

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

@Override

public final <I, O> ActivityResultLauncher<I> registerForActivityResult(final ActivityResultContract<I, O> contract, final ActivityResultRegistry registry, 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方法:

public final <I, O> ActivityResultLauncher<I> register(final String key, final LifecycleOwner lifecycleOwner, final ActivityResultContract<I, O> contract, 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( 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<I>() {

        @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<I, ?> 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 <I, O> void onLaunch(final int requestCode, ActivityResultContract<I, O> contract, I input, 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.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 <O> void doDispatch(String key, int resultCode, Intent data, 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 {

        mParsedPendingResults.remove(key);

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

    }

}

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

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

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android Image Cropper 是一个开源的图片裁剪库,可以快速地实现图片的裁剪功能。它支持手势缩放和移动,可以裁剪任意比例的图片,同时也支持圆形裁剪。 使用 Android Image Cropper 很简单,只需要添加以下依赖: ```groovy implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.+' ``` 然后在代码中使用 CropImageActivity 来启动图片裁剪界面: ```java CropImage.activity(imageUri) .setGuidelines(CropImageView.Guidelines.ON) .setAspectRatio(1, 1) .setCropShape(CropImageView.CropShape.RECTANGLE) .start(this); ``` 其中,imageUri 是要裁剪的图片的 Uri,setGuidelines 方法用于设置裁剪界面上的辅助线,setAspectRatio 方法用于设置裁剪的宽高比,setCropShape 方法用于设置裁剪的形状,可以选择矩形或圆形。 在 onActivityResult 方法中处理裁剪后的图片: ```java @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) { CropImage.ActivityResult result = CropImage.getActivityResult(data); if (resultCode == RESULT_OK) { Uri croppedUri = result.getUri(); // 处理裁剪后的图片 } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) { Exception error = result.getError(); // 处理裁剪错误 } } } ``` 在这个示例代码中,我们使用 CropImage.activity 方法启动图片裁剪界面,并在 onActivityResult 方法中获取裁剪后的图片的 Uri。如果裁剪成功,可以从 Uri 中获取裁剪后的图片,并进行后续处理。如果裁剪失败,可以从 CropImage.ActivityResult 中获取错误信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值