在 Android 中,我们如果想在 Activity 之间双向传递数据,需要使用 startActivityForResult 启动,然后在 onActivityResult 中处理返回,另外申请权限也是类似的步骤。 但是这样的处理方式会让我们的代码变得非常复杂,并且也无法保证在 Activity 发送或接收数据时参数的类型安全。 ActivityResult 是 Jetpack 提供的一个功能,可以简化 Activity 直接的数据传递 (包括权限申请)。它通过提供类型安全的 contract (协定) 来简化处理来自 Activity 的数据。这些协定为一些常见操作 (比如: 拍照或请求权限) 定义了预期的输入和输出类型,除此之外您还能够自定义协定来满足不同场景的需求。 ActivityResult API 提供了一些组件用于注册 Activity 的处理结果、发起请求以及在系统返回结果后立即进行相应处理。您也可以在启动 Activity 的地方使用一个独立的类接收返回结果,这样依然能够保证类型安全。
一、ActivityResult 使用
使用 ActivityResult 先添加依赖:
dependencies {
// 在 https://developer.android.google.cn/jetpack/androidx/releases/activity 获得最新版本号
def activity_version = "1.2.0"
// 在 https://developer.android.google.cn/jetpack/androidx/releases/fragment 获得最新版本号
def fragment_version = "1.3.0"
implementation "androidx.activity:activity:$activity_version"
implementation "androidx.fragment:fragment:$fragment_version”
}
然后先看看最简单的使用方式,比如打开系统文件管理器选择一个图片,代码如下:
val getContent = registerForActivityResult(GetContent()) { uri: Uri? ->
}
getContent.launch("image/*")
这里涉及几个重要的类和函数:
(1) registerForActivityResult: 是 ComponentActivity 的一个函数,注意这里的 ComponentActivity 是 androidx.activity.ComponentActivity 而不是 androidx.core.app.ComponentActivity,androidx.core 中的对应类 (截止 1.3.0) 还不支持这项功能。
public final <I, O> ActivityResultLauncher<I> registerForActivityResult(
@NonNull ActivityResultContract<I, O> contract,
@NonNull ActivityResultCallback<O> callback)
可以看到这个函数接收两个参数,分别是 ActivityResultContract 和回调 ActivityResultCallback,ActivityResultContract 是封装启动所需要的各项参数 (组成 Intent,后面会细说)。函数返回 ActivityResultLauncher,可以看到后面通过他的 launch 函数就可以启动 activity。
(2) GetContent: ActivityResultContracts.GetContent 类是一个继承 ActivityResultContract 的具体实现类,封装了调用系统文件管理器的功能。Jetpack 提供了一些常用的 ActivityResultContract,比如选取图片,拍照等等,如果我们需要拉起自己的 Activity,就需要自定义一个 ActivityResultContract。
(3) launch: ActivityResultLauncher 的函数,启动 activity,代替了之前的 startActivity。
二、在 Jetpack 提供的已封装好的 ActivityResultContract 有 (都是 ActivityResultContracts 的子类):
(1) StartActivityForResult:
最简单的,相当于传统方式的 startActivityForResult,只不过将 onActivityResult 的几个参数封装成一个 ActivityResult。
(2) StartIntentSenderForResult:
相当于 Activity.startIntentSender(IntentSender, Intent, int, int, int),与 PendingIntent 配合使用。
(3) RequestMultiplePermissions:
用于批量申请权限。以 Map 形式返回每个权限的情况。
(4) RequestPermission
申请单个权限,通过这两个来申请权限就可以很方便的进行后续处理。
(5) TakePicturePreview
拉起拍照预览,直接返回 bitmap 数据。(跟传统方式一样,这个 bitmap 只是一个图片预览,因为 intent 中不能传输过大的数据) 。 注意虽然输入是 Void,但是执行 ActivityResultLauncher 的 lanch 函数是还需要传入一个 null 才行。
(6) TakePicture
拉起拍照,输入图片要保存的位置 uri
(7) TakeVideo
录制视频,输入视频要保存的位置 uri,返回视频的缩略图
(8) PickContact
选取联系人
(9) GetContent
获取单个文件,输入过滤类型,返回文件 uri
(10) GetMultipleContents
文件多选,同上
(11) OpenDocument
打开单个文档 (拉起的是系统文档管理器) 对应 Intent.ACTION_OPEN_DOCUMENT,输入的是类型过滤 (如 image/*),输出 uri
(12) OpenMultipleDocuments
打开多个文档,与上面类似
(13) OpenDocumentTree
打开文档 tree,对应 Intent.ACTION_OPEN_DOCUMENT_TREE
(14) CreateDocument
新建一个文档,对应 Intent.ACTION_CREATE_DOCUMENT
可以看到 Android 已经将常用的功能都封装了,基本可以满足我们的开发使用。
三、使用TakePicture调起拍照实例
代码:MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var mImageView: ImageView
private lateinit var mImageView1: ImageView
private var mUri: Uri? = null
private var mTextView: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mImageView = findViewById(R.id.image)
mImageView1 = findViewById(R.id.image1)
mTextView = findViewById(R.id.tx)
Glide.with(this).load("/storage/emulated/0/Android/data/com.example.myapplication/cache.jpg").into(mImageView1)
mUri = FileProvider.getUriForFile(
this, applicationContext.packageName + ".fileprovider",
File(externalCacheDir!!.absolutePath + ".jpg")
)
findViewById<Button>(R.id.button).setOnClickListener {
mTakePicture.launch(mUri)
}
}
private val mTakePicture = registerForActivityResult(ActivityResultContracts.TakePicture()) {
if (it) {
try {
val bmp = MediaStore.Images.Media.getBitmap(
contentResolver,
Uri.fromFile(File(mUri?.path.toString().substring(5)))
)
mImageView.setImageBitmap(bmp)
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
代码说明: 1、代码中有一个mImageView和一个mImageView1,在mTakePicture代码块中,如果直接使用Glide.with(this).load方式加载图片至mImageView,没图片时会成功,有图片后,会出现图片不更新,不是刚所拍照片,是因为直接使用uri会有缓存的原因。故使用Bitmap方式去加载图片。 2、之所以mUri?.path需要substring(5),因为mUri?.path的路径为(/root/storage/emulated/0/Android/data/com.example.myapplication/cache.jpg),多了一个/root,导致读取不到照片,所以需要将其处理一下。 3、实现此实例还需要申请读写权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
4、需要在AndroidManifest.xml中加入:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.myapplication.fileprovider"
android:exported="false"
android:grantUriPermissions="true"
>
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
|