文章目錄
- 一、如何解決 startActivityForResult 被棄用?
- 二、ActivityResultContract 該如何使用?
- 三、但是......我就想簡單的使用startActivityForResult怎么辦?
- 四、總結
轉載請注明出處:https://blog.csdn.net/hx7013/article/details/120916287
Activity Result API已經出來有一段時間了,但是還是有很多朋友對這個API感到使用不便或疑惑,今天盡量用一篇簡短的文章簡述下registerForActivityResult的使用方法,
一、如何解決 startActivityForResult 被棄用?

可以明顯的看到,在androidx.activity1.2.0-alpha04時開始,Android中這位你呼叫過無數次的startActivityForResult和onActivityResult,已經被官方標記為棄用了,繼而推出了名為Activity Result API的組件,
棄用原因也許是onActivityResult里需要處理的各種判斷、嵌套,也許是既要處理requestCode也要處理resultCode這種高耦合難以維護的Id判斷模式,但其原因已不重要了,因為既然Android里已提供了更好的方案并把startActivityForResult標記為了棄用,那么我們就應該開始了解一下位于 ComponentActivity 或 Fragment 中的registerForActivityResult了,
這里先做一個簡單的對比,來了解下registerForActivityResult的簡單及清爽
// startActivityForResult
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
startActivityForResult(Intent(this, SecondActivity::class.java), REQUEST_CODE)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
REQUEST_CODE -> {
val code = resultCode
val data = data
}
}
}
companion object {
private const val REQUEST_CODE = 1024
}
// registerForActivityResult
private lateinit var resultLauncher: ActivityResultLauncher<Intent>
private val launcherCallback = ActivityResultCallback<ActivityResult> { result ->
val code = result.resultCode
val data = result.data
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
resultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult(),
launcherCallback)
resultLauncher.launch(Intent(this,SecondActivity::class.java))
}
代碼一看完,是不是第一感覺不對啊,怎么感覺比之前還復雜了,其實這里是為了讓你更直觀的了解到這個registerForActivityResult到底是什么東西,所以對載體、定義協定、回呼3個類分別定義寫出來,其實大部分情況我們像以下代碼其實這么寫就可以了
private val launcherActivity = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()) {
val code = it.resultCode
val data = it.data
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
launcherActivity.launch(Intent(this, SecondActivity::class.java))
}
是不是瞬間清爽了許多,但是…你還是覺得比使用startActivityForResult更復雜?其實不然,因為上面代碼的需求是一個單一的回呼,所以看著似乎startActivityForResult更便于維護和使用,但倘若撰寫一個稍復雜的頁面,需要同時請求相冊、需要在其它Activity選擇資料并回呼、需要判斷權限等等時,繼續使用startActivityForResult,會導致onActivityResult里摻雜各種嵌套及判斷,導致代碼難以維護,而使用registerForActivityResult()可以多次呼叫以注冊多個 ActivityResultLauncher 實體,用來處理不同的Activity結果,讓代碼更便于維護,
優勢了解到了,但既然需要使用新的功能,那么我們就必須要先了解以下,剛說到的ActivityResultLauncher、ActivityResultContract、ActivityResultCallback到底是些什么東西
- ActivityResultLauncher 從字面意思其實就能很好理解,可以理解它就是一個Activity的啟動器,它的作用就是承載啟動物件與回傳物件,通過
registerForActivityResult回傳該物件,這時并不會立即啟動另一個Activity, - ActivityResultContract 是用來協定所需的輸入型別以及結果的輸出型別,Android默認提供了一些常用的定義,例如上面所使用到到
ActivityResultContracts.StartActivityForResult(),當然這里你也可以通過繼承ActivityResultContract實作自己的定義, - ActivityResultCallback 通過名字就可以了解到這是啟動Activity并回傳到當前Activity時的結果回呼,
對于這3個類,其實只需重點了解ActivityResultContract,就能很輕松的理解并使用好Activity Result API了,
同時,參考一個官方檔案的警告 ↓
注意:雖然在 fragment 或 activity 創建完畢之前可安全地呼叫
registerForActivityResult(),但在 fragment 或 activity 的Lifecycle變為CREATED狀態之前,您無法啟動ActivityResultLauncher,
二、ActivityResultContract 該如何使用?
剛才的例子中,其實已經簡單的使用到Android提供的一個默認協定ActivityResultContracts.StartActivityForResult()來啟動了一個Activity并獲得想要的回傳值,除了StartActivityForResult(),Android還提供了以下的默認協定以便于開發者的使用
| ActivityResultContracts.* | 說明 | 引數 | 回呼 |
|---|---|---|---|
| StartActivityForResult | 可以理解為startActivityForResult | Intent | ActivityResult(code,data) |
| TakePicture | 通過MediaStore.ACTION_IMAGE_CAPTURE拍照并保存 | 保存檔案的Uri | 是否保存成功 |
| TakePicturePreview | 通過MediaStore.ACTION_IMAGE_CAPTURE拍照 | null(Void) | 圖片的Bitmap |
| CaptureVideo | 通過MediaStore.ACTION_VIDEO_CAPTURE拍攝視頻并保存(androidx.activity 1.3.0-alpha08后提供,androidx.appcompat好像還沒提供該類) | 保存檔案的Uri | 是否保存成功, |
| RequestPermission | 請求單個權限 | Manifest.permission.* | 用戶是否授予該權限 |
| RequestMultiplePermissions | 請求多個權限 | Array<Manifest.permission.*> | 回呼為map, key為請求的權限,value為用戶是否授予該權限 |
| CreateDocument | 通過Intent.ACTION_CREATE_DOCUMENT創建一個檔案 | 默認檔案名 | 選擇目錄后回傳該檔案的Uri |
| GetContent | 通過Intent.ACTION_GET_CONTENT獲取一個檔案(這個方法可以通過android.content.ContentResolver.openInputStream獲取到檔案的原始資料) | MIME型別 | 檔案Uri |
| GetMultipleContents | 通過Intent.ACTION_GET_CONTENT及Intent.EXTRA_ALLOW_MULTIPLE獲取一個或多個檔案(這個方法可以通過android.content.ContentResolver.openInputStream獲取到檔案的原始資料) | MIME型別 | 檔案List |
| OpenDocument | 通過Intent.ACTION_OPEN_DOCUMENT選擇檔案 | MIME型別 | 檔案Uri |
| OpenDocumentTree | 通過Intent.ACTION_OPEN_DOCUMENT_TREE選擇一個目錄,回傳一個Uri并得到該目錄下全部檔案的管理權 | 目錄初始位置Uri | 選擇目錄Uri |
| OpenMultipleDocuments | 通過Intent.ACTION_OPEN_DOCUMENT及Intent.EXTRA_ALLOW_MULTIPLE獲取一個或多個檔案 | MIME型別 | 檔案List |
| PickContact | 通過Intent.ACTION_PICK從系統通訊錄中獲取聯系人 | null(Void) | 聯系人Uri |
| StartIntentSenderForResult | 構建IntentSender或PendingIntent | 使用IntentSenderRequest.Builder構建 | ActivityResult(code,data) |
通過MediaStore.ACTION_VIDEO_CAPTURE拍攝視頻并保存(棄用了,官方解釋是縮略圖的Bitmap的回傳不穩定,替換為上面的CaptureVideo即可) | 保存檔案的Uri | 視頻縮略圖Bitmap |
以上全部ActivityResultContracts可在GitHub查看完整示例原始碼
OK,到此是不是慢慢開始感覺到Activity Result API的便捷了,
雖然Android提供的默認協定ActivityResultContracts已經很豐富了,但是為了自己應用內Activity型別安全的傳遞或是解耦,有時我們需要自己創建一個ActivityResultContract,其實查看ActivityResultContracts任意一個類的原始碼,發現自己實作ActivityResultContract并不復雜,只需繼承ActivityResultContract,即可實作型別安全的Activity啟動與資料回傳,
class ContractActivity : AppCompatActivity() {
......
/**
* 繼承[ActivityResultContract]
*
* 泛型第一個引數為傳入到 [ContractActivity] 的引數
* 第二個引數為 [ContractActivity] 回傳給啟動Activity的回傳值
*/
class Contract : ActivityResultContract<String, String>() {
/**
* 創建啟動Intent
* @param context [Context]
* @param input 當前類的第一個泛型參, 這里自己實作傳遞程序
*/
override fun createIntent(context: Context, input: String): Intent =
Intent(context, ContractActivity::class.java).apply {
putExtra(EXTRA_NAME, "$input - ${System.currentTimeMillis()}")
}
/**
* 決議結果
* @param resultCode [Activity.setResult] 的 resultCode
* @param intent [Activity.setResult] 的 intent
* 其實這里類似 [Activity.onActivityResult] 處理程序,只是由于回傳是明確的,所以少了requestCode
*/
override fun parseResult(resultCode: Int, intent: Intent?): String {
return if (resultCode == Activity.RESULT_OK && intent != null) {
"${intent.getStringExtra(EXTRA_RESULT)} - ${System.currentTimeMillis()}"
} else {
"error"
}
}
companion object {
/** EXTRA */
const val EXTRA_NAME = "extra_name"
const val EXTRA_RESULT = "extra_result"
}
}
}
定義好一個Contract后,在使用Activity Result API啟動ContractActivity時,只需呼叫
val launcherContractActivity = registerForActivityResult(ContractActivity.Contract()) { result: String -> }
launcherContractActivity.launch("hi, Activity Result API!")
怎么樣,是不是瞬間覺得特別方便?而且這種方式讓啟動Activity解耦得很徹底,啟動方能明確的知道該傳什么值給被啟動的Activity,也能明確的知道被啟動Activity會回傳什么資料,
以上全部ActivityResultContracts可在GitHub查看完整示例原始碼
三、但是…我就想簡單的使用startActivityForResult怎么辦?
雖然Activity Result API非常強大與便捷,但在國內各廠商深度定制系統的情況下,權限申請操作一般我們還是會使用到第三方框架,拍照、視頻錄制大部分情況使用系統界面操作肯定也不適用,所以Activity Result API里,我的剛需似乎只是一個startActivityForResult那么簡單, 那有更便捷的方法嗎?
首先,嘗試一個直接在按鈕點擊時創建一個ActivityResultLauncher并啟動
viewBinding.btnStartActivityForResult.setOnClickListener {
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result->
val code = result.resultCode
val data = result.data
}.launch(Intent(this@ActivityResultContractsActivity, SecondActivity::class.java))
}
看著似乎很方便,又不需要提前注冊,即用即回呼,但是很不幸,這樣使用你會識訓一個IllegalStateException,例外里也說得很明白LifecycleOwner xxx is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.,必須在STARTED前呼叫registerForActivityResult,
看來我們必須每次都必須通過registerForActivityResult預定一個ActivityResultLauncher,在需要的時候再去launch,這…似憾訓是比較麻煩,那么來看看一個ManageStartActivity小框架吧,
👉 ManageStartActivity - Github 一個基于Activity Result API搭建的純粹啟動Activity的小框架,
何為純粹?就是只負責啟動Acitivity,權限、拍照、錄像等等雖然Activity Result API都支持,但是該框架都不去實作這些功能,原因一,剛也描述過,這些預定的Contract可能在國內大環境下兼容不一定好,或者UI及功能上過于簡單不能滿足實際功能需求,原因二,是若想自己呼叫,使用其實也很簡單,只需呼叫Android提供的這些默認協定即可,所以ManageStartActivity只是純粹的為了減輕使用Activity Result API啟動Activity時過于繁瑣的那些步驟,
只需兩步,輕松實作startActivityForResult
ComponentActivity、Fragment實作IMangeStartActivity介面,并委托給MangeStartActivity處理onCreate時呼叫initManageStartActivity()即可
abstract class BaseActivity : AppCompatActivity(), IMsa by msa() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initManageStartActivity()
}
}
推薦在BaseActivity、BaseFragment里實作 IMangeStartActivity介面, 這樣就能愉快的在任意繼承Base的頁面中使用startActivityForResult了
// 注意實作 `IMsa by msa()`
class SampleActivity : AppCompatActivity(), IMsa by msa() {
override fun onCreate(savedInstanceState: Bundle?) {
// 推薦在super.onCreate 前初始化 `initMangeStartActivity`
// 這樣就能防止還未初始化`ActivityResultLauncher`就呼叫`launch`導致`UninitializedPropertyAccessException`例外
initManageStartActivity()
super.onCreate(savedInstanceState)
}
/**
* 直接啟動Activity
* 3 種方式
*/
fun startActivity() {
// Android習慣模式,傳入KClass即可
startActivity(MainActivity::class) {
putExtra("key", "value")
}
// KClass擴展方法模式
MainActivity::class.start {
putExtra("key", "value")
}
// Intent擴展方法模式
Intent(this, MainActivity::class.java).apply {
putExtra("key", "value")
}.start()
}
/**
* 啟動Activity并需要回呼結果
* 4 種方法
*/
fun startActivityForResult() {
// Android習慣模式,傳入KClass即可
startActivityForResult(MainActivity::class, {
putExtra("key", "value")
}) { code: Int, data: Intent? ->
// code = resultCode
}
// KClass擴展方法模式
MainActivity::class.startForResult({
putExtra("key", "value")
}) {code: Int, data: Intent? ->
// code = resultCode
}
// Android習慣模式,傳入Intent即可
startActivityForResult(Intent(this, MainActivity::class.java).apply {
putExtra("key", "value")
}){ code: Int, data: Intent? ->
// code = resultCode
}
// Intent擴展方法模式
Intent(this, MainActivity::class.java).apply {
putExtra("key", "value")
}.startForResult { code: Int, data: Intent? ->
// code = resultCode
}
}
/**
* Kotlin協程掛起函式
* 4 種方式
*/
fun startActivityForResultCoroutine() {
lifecycleScope.launch {
// Android習慣模式,傳入KClass即可
val (code1: Int, data1: Intent?) = startActivityForResultSync(MainActivity::class) {
putExtra("key", "value")
}
// KClass擴展方法模式
val (code2: Int, data2: Intent?) = MainActivity::class.startForResultSync {
putExtra("key", "value")
}
// Android習慣模式,傳入Intent即可
val (code3: Int, data3: Intent?) = startActivityForResultSync(Intent(this@SampleActivity, MainActivity::class.java).apply {
putExtra("key", "value")
})
// Intent擴展方法模式
val (code4: Int, data4: Intent?) = Intent(this@SampleActivity, MainActivity::class.java).apply {
putExtra("key", "value")
}.startForResultSync()
}
}
}
對的,要的就是這種純粹的startActivityForResult的感覺,
四、總結
Activity Result API和更優雅的使用startActivityForResult現在想必你都已經會使用了,是不是比你想象的更簡單?
如果有需要,我會再寫一篇使用較少的在單獨的類中接收 Activity 結果和Activity Result API的原始碼決議,技術有限,若文中有錯誤遺漏之處,盡情諒解,也歡迎指正共同進步,
轉載請注明出處:https://blog.csdn.net/hx7013/article/details/120916287
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/335193.html
標籤:其他
