110/02 - 只有 StartActivityForResult 可以用吗?

前一天讲到合约(Contracts)和启动器(Launcher)取代StartActivityForResult,官方也帮我们建立了14种常见的合约模板,以下是官方的14种合约

ActivityResultContracts.CreateDocument()
ActivityResultContracts.GetContent()
ActivityResultContracts.GetMultipleContents()
ActivityResultContracts.OpenDocument()
ActivityResultContracts.OpenDocumentTree()
ActivityResultContracts.OpenMultipleDocuments()
ActivityResultContracts.PickContact()
ActivityResultContracts.RequestMultiplePermissions()
ActivityResultContracts.RequestPermission()
ActivityResultContracts.StartActivityForResult()
ActivityResultContracts.StartIntentSenderForResult()
ActivityResultContracts.TakePicture()
ActivityResultContracts.TakePicturePreview()
ActivityResultContracts.TakeVideo()

这篇先介绍

ActivityResultContracts.CreateDocument()

An ActivityResultContract to prompt the user to select a path for creating a new document, returning the content: Uri of the item that was created.
The input is the suggested name for the new file.

This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().

CreateDocument()可以用在拍照前的建立空白档案。

以下范例是建立空白的saberEat.jpg档案,然後回传档案的content://uri,需要注意使用者可以更改档案名称。

createDocumentResultLauncher.launch("saberEat.jpg")
private val createDocumentResultLauncher =
    registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri ->
        Log.d("maho", "回传: $uri")
    }

实际执行程序後的Log

D/maho: 回传: content://com.android.providers.downloads.documents/document/1692

ActivityResultContracts.GetContent()

An ActivityResultContract to prompt the user to pick a piece of content, receiving a content:// Uri for that content that allows you to use android.content.ContentResolver.openInputStream(Uri) to access the raw data. By default, this adds Intent.CATEGORY_OPENABLE to only return content that can be represented as a stream.

The input is the mime type to filter by, e.g. image/*.
This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().

以下范例是输入一个MIME type指定类型,然後选择一个档案,回传档案的content://uri,不可以输入null

getContentResultLauncher.launch("image/*")
private val getContentResultLauncher =
    registerForActivityResult(ActivityResultContracts.GetContent()) { uri ->
        Log.d("maho", "回传: $uri")
    }

实际执行程序後的Log

D/maho: 回传: content://com.android.providers.downloads.documents/document/1688

ActivityResultContracts.GetMultipleContents()

An ActivityResultContract to prompt the user to pick one or more a pieces of content, receiving a content:// Uri for each piece of content that allows you to use android.content.ContentResolver.openInputStream(Uri) to access the raw data. By default, this adds Intent.CATEGORY_OPENABLE to only return content that can be represented as a stream.

The input is the mime type to filter by, e.g. image/*.

This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().

以下范例是输入一个MIME type指定类型,然後长按选择多个档案,用阵列的形式回传档案的content://uri,不可以输入null

getMultipleContentsResultLauncher.launch("image/*")
private val getMultipleContentsResultLauncher =
    registerForActivityResult(ActivityResultContracts.GetMultipleContents()) { uri ->
        Log.d("maho", "回传: $uri")
    }

实际执行程序後的Log

D/maho: 回传: [
content://com.android.providers.downloads.documents/document/1688,
content://com.android.providers.downloads.documents/document/1677
]

ActivityResultContracts.OpenDocument()

An ActivityResultContract to prompt the user to open a document, receiving its contents as a file:/http:/content: Uri.

The input is the mime types to filter by, e.g. image/*.

This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().

See Also: DocumentsContract

以下范例是输入多个MIME type指定类型,然後选择一个档案,回传档案的content://uri,可以输入null表示不指定类型。

openDocumentResultLauncher.launch(arrayOf("image/jpeg", "video/mp4"))
private val openDocumentResultLauncher =
    registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri ->
        Log.d("maho", "回传: $uri")
    }

实际执行程序後的Log

D/maho: 回传: content://com.android.providers.downloads.documents/document/1677

ActivityResultContracts.OpenDocumentTree()

An ActivityResultContract to prompt the user to select a directory, returning the user selection as a Uri. Apps can fully manage documents within the returned directory.

The input is an optional Uri of the initial starting location.

This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().

See Also:
Intent.ACTION_OPEN_DOCUMENT_TREE, DocumentsContract.buildDocumentUriUsingTree, DocumentsContract.buildChildDocumentsUriUsingTree

以下范例是选择资料夹,然後回传资料夹的content://uri,可以输入null表示不指定路径。

openDocumentTreeResultLauncher.launch(null)
private val openDocumentTreeResultLauncher = 
    registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
        Log.d("maho", "回传: $uri")
    }

选择Download/Duo资料夹,实际执行程序後的Log

D/maho: 回传: content://com.android.externalstorage.documents/tree/primary%3ADownload%2FDuo

ActivityResultContracts.OpenMultipleDocuments()

An ActivityResultContract to prompt the user to open (possibly multiple) documents, receiving their contents as file:/http:/content: Uris.

The input is the mime types to filter by, e.g. image/*.

This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().

See Also:
DocumentsContract

以下范例是输入多个MIME type指定类型,然後选择多个档案,用阵列的形式回传档案的content://uri,不可以输入null

openMultipleDocumentsResultLauncher.launch(arrayOf("image/jpeg", "video/mp4"))
private val openMultipleDocumentsResultLauncher =
    registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { list ->
        Log.d("maho", "回传: $list")
    }

实际执行程序後的Log

D/maho: 回传: [
content://com.android.providers.media.documents/document/image%3A5789, 
content://com.android.providers.media.documents/document/image%3A5791
]

ActivityResultContracts.PickContact()

An ActivityResultContract to request the user to pick a contact from the contacts app.

The result is a content: Uri.

See Also:
ContactsContract

选择单个连络人,回传content://uri

pickContactResultLauncher.launch(null)
private val pickContactResultLauncher =
    registerForActivityResult(ActivityResultContracts.PickContact()) { uri ->
        Log.d("maho", "回传: $uri")
    }

实际执行程序後的Log

D/maho: 回传: content://com.android.contacts/contacts/lookup/1519iaad44c18aa14d9a/10

ActivityResultContracts.RequestMultiplePermissions()

An Intent action for making a permission request via a regular Activity.startActivityForResult API. Caller must provide a String[] extra EXTRA_PERMISSIONS Result will be delivered via Activity.onActivityResult(int, int, Intent) with String[] EXTRA_PERMISSIONS and int[] EXTRA_PERMISSION_GRANT_RESULTS, similar to Activity.onRequestPermissionsResult(int, String[], int[])

See Also:
Activity.requestPermissions(String[], int), Activity.onRequestPermissionsResult(int, String[], int[])

Key for the extra containing all the requested permissions.
See Also:
ACTION_REQUEST_PERMISSIONS

Key for the extra containing whether permissions were granted.
See Also:
ACTION_REQUEST_PERMISSIONS

以下范例是输入多个系统权限,用map的形式回传每个权限的truefalse,可以输入null,不过没意义。

requestMultiplePermissionsResultLauncher.launch(
    arrayOf(
        Manifest.permission.CAMERA,
        Manifest.permission.WRITE_EXTERNAL_STORAGE
    )
)
private val requestMultiplePermissionsResultLauncher =
    registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { map ->
        Log.d("maho", "回传: $map")
    }

实际执行程序後的Log

D/maho: 回传: {
android.permission.CAMERA=false, 
android.permission.WRITE_EXTERNAL_STORAGE=false
}

ActivityResultContracts.RequestPermission()

An ActivityResultContract to request a permission

以下范例是输入一个系统权限,回传权限的truefalse,可以输入null,不过没意义。

requestPermissionResultLauncher.launch(Manifest.permission.CAMERA)
private val requestPermissionResultLauncher =
    registerForActivityResult(ActivityResultContracts.RequestPermission()) { boolean ->
        Log.d("maho", "回传: $boolean")
    }

实际执行程序後的Log

D/maho: 回传: false

ActivityResultContracts.StartIntentSenderForResult()

An ActivityResultContract that calls Activity.startIntentSender(IntentSender, Intent, int, int, int). This ActivityResultContract takes an IntentSenderRequest, which must be constructed using an IntentSenderRequest.Builder. If the call to Activity.startIntentSenderForResult(IntentSender, int, Intent, int, int, int) throws an IntentSender.SendIntentException the androidx.activity.result.ActivityResultCallback will receive an ActivityResult with an Activity.RESULT_CANCELED resultCode and whose intent has the action of ACTION_INTENT_SENDER_REQUEST and an extra EXTRA_SEND_INTENT_EXCEPTION that contains the thrown exception.

我不知道这个是做什麽用的,Google後才知道是Phone Selector Api,可以用来做简讯验证之类的功能。

//implementation 'com.google.android.gms:play-services-auth:19.2.0'

val hintRequest = HintRequest
    .Builder()
    .setPhoneNumberIdentifierSupported(true)
    .build()
    
val credentialsOptions = CredentialsOptions
    .Builder()
    .forceEnableSaveDialog()
    .build()
    
val credentials = Credentials
    .getClient(this, credentialsOptions)
    .getHintPickerIntent(hintRequest)
    
startIntentSenderForResultResultLauncher.launch(
    IntentSenderRequest
        .Builder(credentials)
        .build()
)
private val startIntentSenderForResultResultLauncher =
    registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
        if (RESULT_OK == result.resultCode) {
            val credential: Credential? = result
                .data
                ?.getParcelableExtra(Credential.EXTRA_KEY)
            Log.d(
                "maho",
                "id: ${credential?.id} " +
                        "\naccountType: ${credential?.accountType} " +
                        "\nfamilyName: ${credential?.familyName} " +
                        "ngivenName: ${credential?.givenName} " +
                        "\nidTokens: ${credential?.idTokens} " +
                        "\nname: ${credential?.name} " +
                        "\npassword: ${credential?.password} " +
                        "\nprofilePictureUri: ${credential?.profilePictureUri}"
            )
        }
    }

实际执行程序後的Log

D/maho: id: +886910123456 
    accountType: null 
    familyName: null 
    givenName: null 
    idTokens: [] 
    name: null 
    password: null 
    profilePictureUri: null

ActivityResultContracts.TakePicture()

An ActivityResultContract to take a picture saving it into the provided content-Uri.

Returns true if the image was saved into the given Uri.

This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().

以下范例是建立空白档案後再开启相机拍摄照片,回传true表示储存成功,false表示储存失败。

第一种写法:用ActivityResultContracts.CreateDocument()建立档案,取得档案的uri後再使用TakePicture()拍照

takePictureCreateDocumentResultLauncher.launch("002.jpg")
private val takePictureCreateDocumentResultLauncher =
    registerForActivityResult(ActivityResultContracts.CreateDocument()) { uri ->
        takePictureResultLauncher.launch(uri)
    }
    
private val takePictureResultLauncher =
    registerForActivityResult(ActivityResultContracts.TakePicture()) { boolean ->
        Log.d("maho", "回传: $boolean")
    }    

实际执行程序後的Log

//ActivityResultContracts.CreateDocument()的Log
D/maho: 回传: content://com.android.providers.downloads.documents/document/1701

//ActivityResultContracts.TakePicture()的Log
D/maho: 回传: true

第二种写法:使用File()建立档案,再使用getUriForFile()取得档案的uri後再使用TakePicture()拍照

val picturePath = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "003.jpg")
val uri = getUriForFile(this, "$packageName.fileprovider", picturePath)

takePictureResultLauncher.launch(uri)
private val takePictureResultLauncher =
    registerForActivityResult(ActivityResultContracts.TakePicture()) { boolean ->
        Log.d("maho", "回传: $boolean")
    }    

如果要用第二种写法还要前置作业

  1. res 资料夹底下新增 xml 资料夹
  2. 建立 tool_provider_paths.xml
<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="lens_picture"
        path="." />
</paths>
  1. AndroidManifest 新增
<?xml version="1.0" encoding="utf-8"?>
<manifest>
    <application>
    
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/tool_provider_paths" />
        </provider>
        
    </application>
</manifest>

实际执行程序後的Log

D/maho: 回传: true

ActivityResultContracts.TakePicturePreview()

An ActivityResultContract to take small a picture preview, returning it as a Bitmap.

This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().

以下范例是开启相机,拍摄照片後回传相片缩图的bitmap,因为是缩图,所以图片会非常小,官方文件表示 take small a picture preview,所以我也不太懂这个可以用来做什麽功能。

takePicturePreviewResultLauncher.launch(null)
private val takePicturePreviewResultLauncher =
    registerForActivityResult(ActivityResultContracts.TakePicturePreview()) { bitmap ->    
        amIvTakePicturePreview.setImageBitmap(bitmap)
        
        //把缩图存起来的程序码
        val picturePath = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "004.jpg")
        val fileOutputStream = FileOutputStream(picturePath)
        val bufferedOutputStream = BufferedOutputStream(fileOutputStream)
        
        bitmap.compress(Bitmap.CompressFormat.JPEG, 100, bufferedOutputStream)
        bufferedOutputStream.flush()
        bufferedOutputStream.close()
    }

实际执行程序後的Log

D/maho: 回传: android.graphics.Bitmap@93f6ede

ActivityResultContracts.TakeVideo()

An ActivityResultContract to take a video saving it into the provided content-Uri.
Returns a thumbnail.

This can be extended to override createIntent if you wish to pass additional extras to the Intent created by super.createIntent().

以下范例是建立空白档案後再开启相机录制影片,录制完後回传影片缩图的bitmap

//路径为空,会储存到 DCIM 资料夹
takeVideoResultLauncher.launch(null)

//指定储存路径
val picturePath = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "005.mp4")
val uri = getUriForFile(this, "$packageName.fileprovider", picturePath)
takeVideoResultLauncher.launch(uri)
private val takeVideoResultLauncher =
    registerForActivityResult(ActivityResultContracts.TakeVideo()) { bitmap ->
        amIvTakeVideo.setImageBitmap(bitmap)
    }

实际执行程序後的Log,影片有储存成功,理论上要回传影片缩图的bitmap,但我在Android 11一直试不出来,StackOverflow也有一样的问题,也还没解决,只能说是官方的坑

D/maho: 回传: null

总结

我自己觉得官方合约的命名方式满混乱的,开启档案就有GetOpen,然後档案本身也有DocumentContent,所以来分类一下

建立档案

  • CreateDocument()

取得档案

  • GetContent()
  • OpenDocument()
  • GetMultipleContents()
  • OpenMultipleDocuments()
输入单个档案类型 输入多个档案类型
回传单个档案uri GetContent() OpenDocument()
回传阵列档案uri GetMultipleContents() OpenMultipleDocuments()

取得资料夹

  • OpenDocumentTree()

取得连络人

  • PickContact()

取得电话验证

  • StartIntentSenderForResult()

权限验证

  • RequestMultiplePermissions()
  • RequestPermission()
输入单个权限 输入多个权限
回传单个权限验证结果 RequestPermission() X
回传阵列权限验证结果 X RequestMultiplePermissions()

页面跳转

  • StartActivityForResult()

镜头控制

  • TakePicture()
  • TakePicturePreview()
  • TakeVideo()
相片 相片缩图 影片
回传布林值 TakePicture() X X
回传相片缩图bitmap X TakePicturePreview() X
回传影片缩图bitmap X X TakeVideo()

程序码放在feature/resultTemplate分支
https://github.com/AndyAWD/AndroidSystem/tree/feature/resultTemplate


<<:  Day-03 认识Android模拟器

>>:  [Day 04] Sass - 简介

[Day05] JavaScript - 资料型别

资料型别   JavaScript 的型别主要可以分成基本型别 (Primitives)与物件型别 ...

:nth-child() 为什麽是从1开始不是从0开始

之前上课时jQuery讲师说到: :nth-child(b) b要从1开始,不知道为什麽 比如$('...

鬼故事 - 我不晓得这东西为甚麽会动

鬼故事 - 我不晓得这东西为甚麽会动 source: starecat.com 故事开始 故事主角:...

Day 13 : 优化 Zettelkasten 卡片盒笔记法,试试Evergreen Note (长青笔记)

前言 在 上一篇文章 中讲完了 Zettelkasten 笔记法,这篇文章来讲它的优化版本 - An...

Day 22 - 谈谈我对Android的兴趣

Day 22 - 谈谈我对Android的兴趣 认识Android 小时候我拿到我第一台手机,那时我...