<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
如果你將專案中的appcompat庫升級到1.3.0或更高的版本,你會發現startActivityForResult()方法已經被廢棄了。
這個方法相信所有做過Android的開發者都用過,它主要是用於在兩個Activity之間交換資料的。
那麼為什麼這個如此常用的方法會被廢棄呢?官方給出的說法是,現在更加建議使用Activity Result API來實現在兩個Activity之間交換資料的功能。
我個人的觀點是,startActivityForResult()方法並沒有什麼致命的問題,只是Activity Result API在易用性和介面統一性方面都做得更好。既然有更好的API,那麼就不再建議去使用過去老舊的API,所以才把startActivityForResult()方法標為了廢棄。
其實除了startActivityForResult()方法之外,還有像requestPermissions()方法也被標為了廢棄。看起來它們兩者之間好像並沒有什麼關聯,但是到了Activity Result API中,它們就被歸屬到了統一的API模板當中。因此,我們可以使用非常類似的程式碼去實現在兩個Activity之間交換資料,以及請求執行時許可權的功能。
另外,Activity Result API的用法非常簡單,一學就會。相信你看完本篇文章之後,就可以將自己專案中所有相關的程式碼都升級成Activity Result API的用法。
那麼我們開始吧。
如果想要在兩個Activity之間交換資料,我們先回顧一下傳統的寫法:
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 } } } } }
這裡呼叫了startActivityForResult()方法去向SecondActivity請求資料,然後在onActivityResult()方法中去解析SecondActivity返回的結果。
那麼SecondActivity中的程式碼是什麼樣的呢?這裡我們就簡單模擬一下,隨便返回一個資料即可:
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() } } }
如此一來,FirstActivity向SecondActivity請求資料的功能就通了,是不是感覺也挺簡單的?所以我剛才說了,startActivityForResult()方法並沒有什麼致命的問題。
那麼接下來我們學習一下如何使用Activity Result API來實現同樣的功能。
首先,SecondActivity中的程式碼是不需要修改的。這部分程式碼並沒有被廢棄,Activity Result API也與它無關。
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) } } }
注意這裡的程式碼變更。我們完全移除了對onActivityResult()方法的重寫,而是呼叫registerForActivityResult()方法來註冊一個對Activity結果的監聽。
registerForActivityResult()方法接收兩個引數,第一個引數是一種Contract型別,由於我們是希望從另外一個Activity中請求資料,因此這裡使用了StartActivityForResult這種Contract。第二個引數是一個Lambda表示式,當有結果返回時則會回撥到這裡,然後我們在這裡獲取並處理資料即可。
registerForActivityResult()方法的返回值是一個ActivityResultLauncher物件,這個物件當中有一個launch()方法可以用於去啟用Intent。這樣我們就不需要再呼叫startActivityForResult()方法了,而是直接呼叫launch()方法,並把Intent傳入即可。
這兩種寫法到底孰優孰劣呢?我個人感覺還是Activity Result API的寫法更簡單一點,不過總體優勢並沒有那麼大。Activity Result API真正的優勢在於我們接下來要講的內容。
除了startActivityForResult()方法之外,requestPermissions()方法也被廢棄了。至於理由都是一樣的,推薦使用Activity Result API。
那麼要如何使用Activity Result API來請求執行時許可權呢?不要驚訝,它將會出奇得簡單:
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) } } }
我們只需關注程式碼變更的部分。
由於這次是請求執行時許可權,因此不能再使用剛才的StartActivityForResult來作為Contract了,而是要使用RequestPermission這種Contract。
另外由於指定了不同的Contract類似,Lambda表示式的引數也會發生變化。現在Lambda表示式會傳入一個布林型的引數,用於告訴我們使用者是否允許了我們請求的許可權。
最後,launch()方法的引數也發生了變化,現在只需傳入要請求的許可權名即可。
有沒有發現,這兩段程式碼的模板出奇得一致。我們使用了兩段差不多的程式碼,實現了之前幾乎並沒有太大聯絡的兩個功能。這就是Activity Result API的好處,它將一些API的介面統一化,使得我們在實現特定功能的時候能夠變得非常簡單。
剛才我們體驗了StartActivityForResult和RequestPermission這兩種Contract,分別用於在兩個Activity之間交換資料,以及請求執行時許可權。它們都是Activity Result API中內建的Contract。
那麼除此之外,我們還有哪些內建Contract可以使用呢?
下面是我列出的appcompat 1.3.0版本所支援的所有內建Contract,以後還可能會繼續增加新的Contract:
StartActivityForResult()
StartIntentSenderForResult()
RequestMultiplePermissions()
RequestPermission()
TakePicturePreview()
TakePicture()
TakeVideo()
PickContact()
GetContent()
GetMultipleContents()
OpenDocument()
OpenMultipleDocuments()
OpenDocumentTree()
CreateDocument()
每個Contract的命名已經明確表示它們的作用是什麼了,也就是說,當我們要實現以上Contract所包含的功能時,都不需要再自己手動費力去寫了,Activity Result API已經幫我們支援好了。
比如,我想要呼叫手機攝像頭去拍攝一張圖片,並且得到這張圖片的Bitmap物件,那麼就可以使用TakePicturePreview這個Contract。
實現程式碼如下:
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) } } }
程式碼非常簡單,就是換了一下Contract型別,然後Lambda表示式的引數會變成bitmap物件。另外由於TakePicturePreview這個Contract不需要輸入引數,所以我們呼叫launch()方法的時候直接傳入null就可以了。
看到這裡,可能有些讀者朋友會比較好奇。我怎麼知道每種Contract要求什麼輸入引數,以及Lambda表示式中返回的引數是什麼呢?
這個很簡單,只需要看一下這個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> { ... }
我們暫時不用關心TakePicturePreview內部的具體實現,只要看一下它在繼承父類別時指定的泛型型別即可。其中第一個引數就是要求的輸入引數,而第二個引數就是Lambda表示式返回的輸出引數。
只要掌握這個小技巧,每種Contract你就都能輕鬆運用自如了。那麼我就不再多做演示,剩下這些Contract的用法等待你自己去探索。
除了以上內建Contract之外,我們確實也可以定義自己的Contract型別。
雖然我覺得這個必要性並不是很強,因為內建Contract已經可以幫助我們應對絕大多數場景了。
不過,自定義Contract並不是一件複雜的事情。相反,它非常簡單,所以這裡還是簡略提一下吧。
剛才我們大概看到了TakePicturePreview的原始碼實現,它必須繼承自ActivityResultContract類,並通過泛型來指定當前Conract型別的輸入引數和輸出引數。
ActivityResultContract是一個抽象類,它的內部定義了兩個抽象方法,如下所示:
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); ... }
也就是說,任何一個繼承自ActivityResultContract的Contract,都需要重寫createIntent()和parseResult()這兩個方法。
而這兩個方法的作用也非常明顯。createIntent()就是用於建立一個Intent,後續會使用這個Intent來發起動作,比如啟動另外一個Activity去獲取資料,或者開啟相機去拍照等等。而parseResult()則是用於解析響應的結果,並把解析出來的結果作為輸出引數返回到Lambda表示式當中。
每一個內建的Contract都是使用的這種規則來封裝的自己的邏輯。
那麼我們要自定義一個什麼樣的Contract來進行演示呢?
我想了一下,剛才在編寫兩個Activity之間交換資料的時候,我們需要顯示地啟動SecondActivity,並手動將SecondActivity返回的資料從Intent中解析出來,這就稍微有些麻煩。而藉助自定義Contract就可以對此處進行優化。
新建一個叫做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 } }
我們通過泛型指定,這個Contract的輸入引數是Void,輸出引數是一個字串。
然後在createIntent()方法中,我們手動建立了一個Intent,並將它的用途設定為開啟SecondActivity。
最後在parseResult()方法中,我們對SecondActivity返回的結果進行解析,並將解析出來的字串作為輸出引數返回。
這樣一個自定義的Contract就完成了,而我們使用這個Contract再去實現最開始的在兩個Activity之間交換資料的功能,就會變得更加簡單:
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這個Contract,我們不需要再顯式地宣告去啟動SecondActivity,launch()方法直接傳入null即可。另外,我們也不需要再去手動解析SecondActivity返回的資料,lambda表示式上的引數就是解析出來的結果了。
到這裡,我們基本就將Activity Result API的所有內容都學完了。
在本篇文章的最後,我想再回答一個小問題。因為我自己當初在使用Activity Result API的時候產生過這樣的疑惑,所以我猜或許也會有朋友有同樣的問題,那麼在這裡就順手解答了。
現在你已經知道,Activity Result API是可以完全取代startActivityForResult()方法的。但是我們在呼叫startActivityForResult()方法時,除了傳入Intent之外,還需要再傳入一個requestCode,用於在多個任務之間進行區分。比如如下程式碼:
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 } } } }
這裡我們分別在兩處呼叫了startActivityForResult()方法,它們各自用於處理不同的任務,因此需要給它們設定不同的requestCode。
在onActivityResult()方法當中,我們為了區分這個結果是來自之前的哪個任務的,所以要在這裡再對requestCode進行判斷。
這是以前使用startActivityForResult()方法時的傳統寫法。
而Activity Result API是沒有地方讓你傳入requestCode的。
我在剛接觸Activity Result API的時候受思維慣性的影響被這個問題困擾了一下,沒有地方傳入requestCode該怎麼辦呢?
後來思維轉過來彎之後發現,原來Activity Result API根本就不需要requestCode這種東西,我們可以使用如下寫法來實現和剛才完全一樣的功能:
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的設計更加合理,不需要藉助requestCode這種魔術數位也能對多個任務進行區分。
一切都更加簡單和清晰。
到此這篇關於Android 超詳細深刨Activity Result API的使用的文章就介紹到這了,更多相關Android Activity Result 內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45