Android 超詳細(xì)深刨Activity Result API的使用
如果你將項(xiàng)目中的appcompat庫升級(jí)到1.3.0或更高的版本,你會(huì)發(fā)現(xiàn)startActivityForResult()方法已經(jīng)被廢棄了。
這個(gè)方法相信所有做過Android的開發(fā)者都用過,它主要是用于在兩個(gè)Activity之間交換數(shù)據(jù)的。
那么為什么這個(gè)如此常用的方法會(huì)被廢棄呢?官方給出的說法是,現(xiàn)在更加建議使用Activity Result API來實(shí)現(xiàn)在兩個(gè)Activity之間交換數(shù)據(jù)的功能。
我個(gè)人的觀點(diǎn)是,startActivityForResult()方法并沒有什么致命的問題,只是Activity Result API在易用性和接口統(tǒng)一性方面都做得更好。既然有更好的API,那么就不再建議去使用過去老舊的API,所以才把startActivityForResult()方法標(biāo)為了廢棄。
其實(shí)除了startActivityForResult()方法之外,還有像requestPermissions()方法也被標(biāo)為了廢棄??雌饋硭鼈儍烧咧g好像并沒有什么關(guān)聯(lián),但是到了Activity Result API中,它們就被歸屬到了統(tǒng)一的API模板當(dāng)中。因此,我們可以使用非常類似的代碼去實(shí)現(xiàn)在兩個(gè)Activity之間交換數(shù)據(jù),以及請(qǐng)求運(yùn)行時(shí)權(quán)限的功能。
另外,Activity Result API的用法非常簡(jiǎn)單,一學(xué)就會(huì)。相信你看完本篇文章之后,就可以將自己項(xiàng)目中所有相關(guān)的代碼都升級(jí)成Activity Result API的用法。
那么我們開始吧。
在兩個(gè)Activity之間交換數(shù)據(jù)
如果想要在兩個(gè)Activity之間交換數(shù)據(jù),我們先回顧一下傳統(tǒng)的寫法:
class FirstActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_first) val firstButton = findViewById<Button>(R.id.first_button) firstButton.setOnClickListener { val intent = Intent(this, SecondActivity::class.java) startActivityForResult(intent, 1) } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { 1 -> { if (resultCode == RESULT_OK) { val data = data?.getStringExtra("data") // Handle data from SecondActivity } } } } }
這里調(diào)用了startActivityForResult()方法去向SecondActivity請(qǐng)求數(shù)據(jù),然后在onActivityResult()方法中去解析SecondActivity返回的結(jié)果。
那么SecondActivity中的代碼是什么樣的呢?這里我們就簡(jiǎn)單模擬一下,隨便返回一個(gè)數(shù)據(jù)即可:
class SecondActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_second) val secondButton = findViewById<Button>(R.id.second_button) secondButton.setOnClickListener { val intent = Intent() intent.putExtra("data", "data from SecondActivity") setResult(RESULT_OK, intent) finish() } } }
如此一來,F(xiàn)irstActivity向SecondActivity請(qǐng)求數(shù)據(jù)的功能就通了,是不是感覺也挺簡(jiǎn)單的?所以我剛才說了,startActivityForResult()方法并沒有什么致命的問題。
那么接下來我們學(xué)習(xí)一下如何使用Activity Result API來實(shí)現(xiàn)同樣的功能。
首先,SecondActivity中的代碼是不需要修改的。這部分代碼并沒有被廢棄,Activity Result API也與它無關(guān)。
FirstActivity中的代碼,我們需要使用Activity Result API來替代startActivityForResult()的寫法,如下所示:
class FirstActivity : AppCompatActivity() { private val requestDataLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == RESULT_OK) { val data = result.data?.getStringExtra("data") // Handle data from SecondActivity } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_first) val firstButton = findViewById<Button>(R.id.first_button) firstButton.setOnClickListener { val intent = Intent(this, SecondActivity::class.java) requestDataLauncher.launch(intent) } } }
注意這里的代碼變更。我們完全移除了對(duì)onActivityResult()方法的重寫,而是調(diào)用registerForActivityResult()方法來注冊(cè)一個(gè)對(duì)Activity結(jié)果的監(jiān)聽。
registerForActivityResult()方法接收兩個(gè)參數(shù),第一個(gè)參數(shù)是一種Contract類型,由于我們是希望從另外一個(gè)Activity中請(qǐng)求數(shù)據(jù),因此這里使用了StartActivityForResult這種Contract。第二個(gè)參數(shù)是一個(gè)Lambda表達(dá)式,當(dāng)有結(jié)果返回時(shí)則會(huì)回調(diào)到這里,然后我們?cè)谶@里獲取并處理數(shù)據(jù)即可。
registerForActivityResult()方法的返回值是一個(gè)ActivityResultLauncher對(duì)象,這個(gè)對(duì)象當(dāng)中有一個(gè)launch()方法可以用于去啟用Intent。這樣我們就不需要再調(diào)用startActivityForResult()方法了,而是直接調(diào)用launch()方法,并把Intent傳入即可。
這兩種寫法到底孰優(yōu)孰劣呢?我個(gè)人感覺還是Activity Result API的寫法更簡(jiǎn)單一點(diǎn),不過總體優(yōu)勢(shì)并沒有那么大。Activity Result API真正的優(yōu)勢(shì)在于我們接下來要講的內(nèi)容。
請(qǐng)求運(yùn)行時(shí)權(quán)限
除了startActivityForResult()方法之外,requestPermissions()方法也被廢棄了。至于理由都是一樣的,推薦使用Activity Result API。
那么要如何使用Activity Result API來請(qǐng)求運(yùn)行時(shí)權(quán)限呢?不要驚訝,它將會(huì)出奇得簡(jiǎn)單:
class FirstActivity : AppCompatActivity() { private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted -> if (granted) { // User allow the permission. } else { // User deny the permission. } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_first) val firstButton = findViewById<Button>(R.id.first_button) firstButton.setOnClickListener { requestPermissionLauncher.launch(Manifest.permission.READ_EXTERNAL_STORAGE) } } }
我們只需關(guān)注代碼變更的部分。
由于這次是請(qǐng)求運(yùn)行時(shí)權(quán)限,因此不能再使用剛才的StartActivityForResult來作為Contract了,而是要使用RequestPermission這種Contract。
另外由于指定了不同的Contract類似,Lambda表達(dá)式的參數(shù)也會(huì)發(fā)生變化?,F(xiàn)在Lambda表達(dá)式會(huì)傳入一個(gè)布爾型的參數(shù),用于告訴我們用戶是否允許了我們請(qǐng)求的權(quán)限。
最后,launch()方法的參數(shù)也發(fā)生了變化,現(xiàn)在只需傳入要請(qǐng)求的權(quán)限名即可。
有沒有發(fā)現(xiàn),這兩段代碼的模板出奇得一致。我們使用了兩段差不多的代碼,實(shí)現(xiàn)了之前幾乎并沒有太大聯(lián)系的兩個(gè)功能。這就是Activity Result API的好處,它將一些API的接口統(tǒng)一化,使得我們?cè)趯?shí)現(xiàn)特定功能的時(shí)候能夠變得非常簡(jiǎn)單。
內(nèi)置Contract
剛才我們體驗(yàn)了StartActivityForResult和RequestPermission這兩種Contract,分別用于在兩個(gè)Activity之間交換數(shù)據(jù),以及請(qǐng)求運(yùn)行時(shí)權(quán)限。它們都是Activity Result API中內(nèi)置的Contract。
那么除此之外,我們還有哪些內(nèi)置Contract可以使用呢?
下面是我列出的appcompat 1.3.0版本所支持的所有內(nèi)置Contract,以后還可能會(huì)繼續(xù)增加新的Contract:
StartActivityForResult()
StartIntentSenderForResult()
RequestMultiplePermissions()
RequestPermission()
TakePicturePreview()
TakePicture()
TakeVideo()
PickContact()
GetContent()
GetMultipleContents()
OpenDocument()
OpenMultipleDocuments()
OpenDocumentTree()
CreateDocument()
每個(gè)Contract的命名已經(jīng)明確表示它們的作用是什么了,也就是說,當(dāng)我們要實(shí)現(xiàn)以上Contract所包含的功能時(shí),都不需要再自己手動(dòng)費(fèi)力去寫了,Activity Result API已經(jīng)幫我們支持好了。
比如,我想要調(diào)用手機(jī)攝像頭去拍攝一張圖片,并且得到這張圖片的Bitmap對(duì)象,那么就可以使用TakePicturePreview這個(gè)Contract。
實(shí)現(xiàn)代碼如下:
class FirstActivity : AppCompatActivity() { private val takePictureLauncher = registerForActivityResult(ActivityResultContracts.TakePicturePreview()) { bitmap -> // bitmap from camera } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_first) val firstButton = findViewById<Button>(R.id.first_button) firstButton.setOnClickListener { takePictureLauncher.launch(null) } } }
代碼非常簡(jiǎn)單,就是換了一下Contract類型,然后Lambda表達(dá)式的參數(shù)會(huì)變成bitmap對(duì)象。另外由于TakePicturePreview這個(gè)Contract不需要輸入?yún)?shù),所以我們調(diào)用launch()方法的時(shí)候直接傳入null就可以了。
看到這里,可能有些讀者朋友會(huì)比較好奇。我怎么知道每種Contract要求什么輸入?yún)?shù),以及Lambda表達(dá)式中返回的參數(shù)是什么呢?
這個(gè)很簡(jiǎn)單,只需要看一下這個(gè)Contract的源碼即可。比如TakePicturePreview的源碼如下:
/** * An {@link ActivityResultContract} to * {@link MediaStore#ACTION_IMAGE_CAPTURE take small a picture} preview, returning it as a * {@link Bitmap}. * <p> * This can be extended to override {@link #createIntent} if you wish to pass additional * extras to the Intent created by {@code super.createIntent()}. */ public static class TakePicturePreview extends ActivityResultContract<Void, Bitmap> { ... }
我們暫時(shí)不用關(guān)心TakePicturePreview內(nèi)部的具體實(shí)現(xiàn),只要看一下它在繼承父類時(shí)指定的泛型類型即可。其中第一個(gè)參數(shù)就是要求的輸入?yún)?shù),而第二個(gè)參數(shù)就是Lambda表達(dá)式返回的輸出參數(shù)。
只要掌握這個(gè)小技巧,每種Contract你就都能輕松運(yùn)用自如了。那么我就不再多做演示,剩下這些Contract的用法等待你自己去探索。
自定義Contract
除了以上內(nèi)置Contract之外,我們確實(shí)也可以定義自己的Contract類型。
雖然我覺得這個(gè)必要性并不是很強(qiáng),因?yàn)閮?nèi)置Contract已經(jīng)可以幫助我們應(yīng)對(duì)絕大多數(shù)場(chǎng)景了。
不過,自定義Contract并不是一件復(fù)雜的事情。相反,它非常簡(jiǎn)單,所以這里還是簡(jiǎn)略提一下吧。
剛才我們大概看到了TakePicturePreview的源碼實(shí)現(xiàn),它必須繼承自ActivityResultContract類,并通過泛型來指定當(dāng)前Conract類型的輸入?yún)?shù)和輸出參數(shù)。
ActivityResultContract是一個(gè)抽象類,它的內(nèi)部定義了兩個(gè)抽象方法,如下所示:
public abstract class ActivityResultContract<I, O> { public abstract @NonNull Intent createIntent(@NonNull Context context, I input); public abstract O parseResult(int resultCode, @Nullable Intent intent); ... }
也就是說,任何一個(gè)繼承自ActivityResultContract的Contract,都需要重寫createIntent()和parseResult()這兩個(gè)方法。
而這兩個(gè)方法的作用也非常明顯。createIntent()就是用于創(chuàng)建一個(gè)Intent,后續(xù)會(huì)使用這個(gè)Intent來發(fā)起動(dòng)作,比如啟動(dòng)另外一個(gè)Activity去獲取數(shù)據(jù),或者打開相機(jī)去拍照等等。而parseResult()則是用于解析響應(yīng)的結(jié)果,并把解析出來的結(jié)果作為輸出參數(shù)返回到Lambda表達(dá)式當(dāng)中。
每一個(gè)內(nèi)置的Contract都是使用的這種規(guī)則來封裝的自己的邏輯。
那么我們要自定義一個(gè)什么樣的Contract來進(jìn)行演示呢?
我想了一下,剛才在編寫兩個(gè)Activity之間交換數(shù)據(jù)的時(shí)候,我們需要顯示地啟動(dòng)SecondActivity,并手動(dòng)將SecondActivity返回的數(shù)據(jù)從Intent中解析出來,這就稍微有些麻煩。而借助自定義Contract就可以對(duì)此處進(jìn)行優(yōu)化。
新建一個(gè)叫做GetDataFromSecondActivity的Contract,代碼如下所示:
class GetDataFromSecondActivity : ActivityResultContract<Void, String?>() { override fun createIntent(context: Context, input: Void?): Intent { return Intent(context, SecondActivity::class.java) } override fun parseResult(resultCode: Int, intent: Intent?): String? { if (resultCode == Activity.RESULT_OK) { if (intent != null) { return intent.getStringExtra("data") } } return null } }
我們通過泛型指定,這個(gè)Contract的輸入?yún)?shù)是Void,輸出參數(shù)是一個(gè)字符串。
然后在createIntent()方法中,我們手動(dòng)創(chuàng)建了一個(gè)Intent,并將它的用途設(shè)置為打開SecondActivity。
最后在parseResult()方法中,我們對(duì)SecondActivity返回的結(jié)果進(jìn)行解析,并將解析出來的字符串作為輸出參數(shù)返回。
這樣一個(gè)自定義的Contract就完成了,而我們使用這個(gè)Contract再去實(shí)現(xiàn)最開始的在兩個(gè)Activity之間交換數(shù)據(jù)的功能,就會(huì)變得更加簡(jiǎn)單:
class FirstActivity : AppCompatActivity() { private val getDataLauncher = registerForActivityResult(GetDataFromSecondActivity()) { data -> // Handle data from SecondActivity } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_first) val firstButton = findViewById<Button>(R.id.first_button) firstButton.setOnClickListener { getDataLauncher.launch(null) } } }
可以看到,借助GetDataFromSecondActivity這個(gè)Contract,我們不需要再顯式地聲明去啟動(dòng)SecondActivity,launch()方法直接傳入null即可。另外,我們也不需要再去手動(dòng)解析SecondActivity返回的數(shù)據(jù),lambda表達(dá)式上的參數(shù)就是解析出來的結(jié)果了。
最后一個(gè)小問題
到這里,我們基本就將Activity Result API的所有內(nèi)容都學(xué)完了。
在本篇文章的最后,我想再回答一個(gè)小問題。因?yàn)槲易约寒?dāng)初在使用Activity Result API的時(shí)候產(chǎn)生過這樣的疑惑,所以我猜或許也會(huì)有朋友有同樣的問題,那么在這里就順手解答了。
現(xiàn)在你已經(jīng)知道,Activity Result API是可以完全取代startActivityForResult()方法的。但是我們?cè)谡{(diào)用startActivityForResult()方法時(shí),除了傳入Intent之外,還需要再傳入一個(gè)requestCode,用于在多個(gè)任務(wù)之間進(jìn)行區(qū)分。比如如下代碼:
class FirstActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_first) val firstButton = findViewById<Button>(R.id.first_button) val secondButton = findViewById<Button>(R.id.second_button) firstButton.setOnClickListener { val intent = Intent(Intent.ACTION_VIEW) startActivityForResult(intent, 1) } secondButton.setOnClickListener { val intent = Intent(Intent.ACTION_DIAL) startActivityForResult(intent, 2) } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) when (requestCode) { 1 -> { // Handle result for ACTION_VIEW } 2 -> { // Handle result for ACTION_DIAL } } } }
這里我們分別在兩處調(diào)用了startActivityForResult()方法,它們各自用于處理不同的任務(wù),因此需要給它們?cè)O(shè)置不同的requestCode。
在onActivityResult()方法當(dāng)中,我們?yōu)榱藚^(qū)分這個(gè)結(jié)果是來自之前的哪個(gè)任務(wù)的,所以要在這里再對(duì)requestCode進(jìn)行判斷。
這是以前使用startActivityForResult()方法時(shí)的傳統(tǒng)寫法。
而Activity Result API是沒有地方讓你傳入requestCode的。
我在剛接觸Activity Result API的時(shí)候受思維慣性的影響被這個(gè)問題困擾了一下,沒有地方傳入requestCode該怎么辦呢?
后來思維轉(zhuǎn)過來彎之后發(fā)現(xiàn),原來Activity Result API根本就不需要requestCode這種東西,我們可以使用如下寫法來實(shí)現(xiàn)和剛才完全一樣的功能:
class FirstActivity : AppCompatActivity() { private val actionViewLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> // Handle result for ACTION_VIEW } private val actionDialLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> // Handle result for ACTION_DIAL } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_first) val firstButton = findViewById<Button>(R.id.first_button) val secondButton = findViewById<Button>(R.id.second_button) firstButton.setOnClickListener { val intent = Intent(Intent.ACTION_VIEW) actionViewLauncher.launch(intent) } secondButton.setOnClickListener { val intent = Intent(Intent.ACTION_DIAL) actionDialLauncher.launch(intent) } } }
由此也可以看出,Activity Result API的設(shè)計(jì)更加合理,不需要借助requestCode這種魔術(shù)數(shù)字也能對(duì)多個(gè)任務(wù)進(jìn)行區(qū)分。
一切都更加簡(jiǎn)單和清晰。
到此這篇關(guān)于Android 超詳細(xì)深刨Activity Result API的使用的文章就介紹到這了,更多相關(guān)Android Activity Result 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
android創(chuàng)建數(shù)據(jù)庫(SQLite)保存圖片示例
這篇文章主要介紹了android創(chuàng)建數(shù)據(jù)庫,保存圖片到數(shù)據(jù)庫再從數(shù)據(jù)庫取圖片的方法,大家參考使用吧2014-01-01Android Studio全局搜索快捷鍵(Ctrl+Shift+F)失效問題及解決
這篇文章主要介紹了Android Studio全局搜索快捷鍵(Ctrl+Shift+F)失效問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01揭秘雙十一手機(jī)淘寶圖標(biāo)如何被動(dòng)態(tài)更換
這篇文章主要介紹了每到雙十一十二的時(shí)候Android手機(jī)動(dòng)態(tài)更換手機(jī)圖標(biāo)的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08單獨(dú)編譯Android 源代碼中的模塊實(shí)現(xiàn)方法
本文主要講解單獨(dú)編譯Android 源代碼中的模塊,這里對(duì)Android源碼單獨(dú)編譯模塊,做出了詳細(xì)的步驟,希望能幫助研究Android 源代碼的朋友2016-08-08Android應(yīng)用閃屏頁延遲跳轉(zhuǎn)的三種寫法
這篇文章主要介紹了 Android應(yīng)用閃屏頁延遲跳轉(zhuǎn)的三種寫法,需要的朋友可以參考下2017-03-03android表格效果之ListView隔行變色實(shí)現(xiàn)代碼
首先繼承SimpleAdapter再使用重載的Adapter來達(dá)到效果,其實(shí)主要是需要重載SimpleAdapter,感興趣的朋友可以研究下,希望本文可以幫助到你2013-02-02flutter?material?widget組件之信息展示組件使用詳解
這篇文章主要為大家詳細(xì)介紹了flutter?material?widget組件之信息展示組件的使用,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08簡(jiǎn)單實(shí)現(xiàn)Android繪圖板
這篇文章主要教大家如何簡(jiǎn)單實(shí)現(xiàn)Android繪圖板,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12