Android?startActivityForResult的調(diào)用與封裝詳解
前言
startActivityForResult 可以說(shuō)是我們常用的一種操作了,用于啟動(dòng)新頁(yè)面并拿到這個(gè)頁(yè)面返回的數(shù)據(jù),是兩個(gè) Activity 交互的基本操作。
雖然可以通過(guò)接口,消息總線(xiàn),單例池,ViewModel 等多種方法來(lái)間接的實(shí)現(xiàn)這樣一個(gè)功能,但是 startActivityForResult 還是使用最方便的。
目前有哪些方式實(shí)現(xiàn) startActivityForResult 的功能呢?
有新老兩種方式,過(guò)時(shí)的方法是原生Activity/Fragment的 startActivityForResult 方法。另一種方法是 Activity Result API 通過(guò) registerForActivityResult 來(lái)注冊(cè)回調(diào)。
我們一起看看都是如何使用,使用起來(lái)方便嗎?通常我們又都是如何封裝的呢?
一、原生的使用
不管是Activity還是Fragment,我們都可以使用 startActivityForResult

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 120 && resultCode == -1) {
toast("接收到返回的數(shù)據(jù):" + data?.getStringExtra("text"))
}
}可以看到雖然標(biāo)記過(guò)時(shí)了,但是 startActivityForResult 這種方法是可以用的,我們一直這么用的,老項(xiàng)目中有很多頁(yè)面都是這么定義的。也并沒(méi)有什么問(wèn)題。
不過(guò)既然谷歌推薦我們使用 Result Api 我們?cè)谝院笫褂?startActivityForResult 的時(shí)候還是推薦使用新的方式。
二、對(duì)原生的封裝Ghost
在之前我們使用 startActivityForResult 這種方式的時(shí)候,為了更加方便的私有,有一種很流行的方式 Ghost 。
它使用一種 GhostFragment 的空視圖當(dāng)做一次中轉(zhuǎn),這種思路在現(xiàn)在看來(lái)已經(jīng)不稀奇了,很多框架如Glide,權(quán)限申請(qǐng)等都是用的這種方案。
它的大致實(shí)現(xiàn)流程為:
Activty/Fragment -> add GhostFragment -> onAttach 中 startActivityForResult -> GhostFragment onActivityResult接收結(jié)果 -> callback回調(diào)給Activty/Fragment
總體需要兩個(gè)類(lèi)就可以完成這個(gè)邏輯,一個(gè)是中轉(zhuǎn)Fragment,一個(gè)是管理類(lèi):
/**
* 封裝Activity Result的API
* 使用空Fragemnt的形式調(diào)用startActivityForResult并返回回調(diào)
*
* Activty/Fragment——>add GhostFragment——>onAttach中startActivityForResult
* ——>GhostFragment onActivityResult接收結(jié)果——>callback回調(diào)給Activty/Fragment
*/
class GhostFragment : Fragment() {
private var requestCode = -1
private var intent: Intent? = null
private var callback: ((result: Intent?) -> Unit)? = null
fun init(requestCode: Int, intent: Intent, callback: ((result: Intent?) -> Unit)) {
this.requestCode = requestCode
this.intent = intent
this.callback = callback
}
private var activityStarted = false
override fun onAttach(activity: Activity) {
super.onAttach(activity)
if (!activityStarted) {
activityStarted = true
intent?.let { startActivityForResult(it, requestCode) }
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (!activityStarted) {
activityStarted = true
intent?.let { startActivityForResult(it, requestCode) }
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK && requestCode == this.requestCode) {
callback?.let { it1 -> it1(data) }
}
}
override fun onDetach() {
super.onDetach()
intent = null
callback = null
}
}/**
* 管理GhostFragment用于StartActivityForResult
* 啟動(dòng)的時(shí)候添加Fragment 返回的時(shí)移除Fragment
*/
object Ghost {
var requestCode = 0
set(value) {
field = if (value >= Integer.MAX_VALUE) 1 else value
}
inline fun launchActivityForResult(
starter: FragmentActivity?,
intent: Intent,
crossinline callback: ((result: Intent?) -> Unit)
) {
starter ?: return
val fm = starter.supportFragmentManager
val fragment = GhostFragment()
fragment.init(++requestCode, intent) { result ->
callback(result)
fm.beginTransaction().remove(fragment).commitAllowingStateLoss()
}
fm.beginTransaction().add(fragment, GhostFragment::class.java.simpleName)
.commitAllowingStateLoss()
}
}如此我們就可以使用Kotlin的擴(kuò)展方法來(lái)對(duì)它進(jìn)行進(jìn)一步的封裝
//真正執(zhí)行AcytivityForResult的方法,使用Ghost的方式執(zhí)行
inline fun <reified T> FragmentActivity.gotoActivityForResult(
flag: Int = -1,
bundle: Array<out Pair<String, Any?>>? = null,
crossinline callback: ((result: Intent?) -> Unit)
) {
val intent = Intent(this, T::class.java).apply {
if (flag != -1) {
this.addFlags(flag)
}
if (bundle != null) {
//調(diào)用自己的擴(kuò)展方法-數(shù)組轉(zhuǎn)Bundle
putExtras(bundle.toBundle()!!)
}
}
Ghost.launchActivityForResult(this, intent, callback)
}使用起來(lái)就超級(jí)簡(jiǎn)單了:
gotoActivityForResult<Demo10Activity> {
val text = it?.getStringExtra("text")
toast("拿到返回?cái)?shù)據(jù):$text")
}
gotoActivityForResult<Demo10Activity>(bundle = arrayOf("id" to "123", "name" to "zhangsan")) {
val text = it?.getStringExtra("text")
toast("拿到返回?cái)?shù)據(jù):$text")
}三、Result Api 的使用
其實(shí)看Ghost的原來(lái)就看得出,他本質(zhì)上還是對(duì) startActivityForResult 的調(diào)用與封裝,還是過(guò)期的方法,那么如何使用新的方式,谷歌推薦我們?cè)趺从茫?/p>
Activity Result API :
它是 Jetpack 的一個(gè)組件,這是官方用于替代 startActivityForResult() 和 onActivityResult() 的工具,我們以Activity 1.2.4版本為例:
implementation "androidx.activity:activity-ktx:1.2.4"
那么如何基礎(chǔ)的使用它呢:
private val safLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
val data = result.data?.getStringExtra("text")
toast("拿到返回?cái)?shù)據(jù):$data")
}
}
//在方法中使用
safLauncher?.launch(Intent(mActivity, Demo10Activity::class.java))
看起來(lái)實(shí)現(xiàn)很簡(jiǎn)單,但是有幾點(diǎn)要注意,Launcher 的創(chuàng)建需要在onStart生命周期之前,并且回調(diào)是在 Launcher 中處理的。并且 這些 Launcher 并不是只能返回Activity的Result的,還有其他的啟動(dòng)方式:
StartActivityForResult()
StartIntentSenderForResult()
RequestMultiplePermissions()
RequestPermission()
TakePicturePreview()
TakePicture()
TakeVideo()
PickContact()
GetContent()
GetMultipleContents()
OpenDocument()
OpenMultipleDocuments()
OpenDocumentTree()
CreateDocument()
可以看到這些方式其實(shí)對(duì)我們來(lái)說(shuō)很多沒(méi)必要,在真正的開(kāi)發(fā)中只有 StartActivityForResult 這一種方式是我們的剛需。
為什么?畢竟現(xiàn)在誰(shuí)還用這種方式申請(qǐng)權(quán)限,操作多媒體文件。相信大家也都是使用框架來(lái)處理了,所以我們這里只對(duì) StartActivityForResult 這一種方式做處理。畢竟這才是我們使用場(chǎng)景最多的,也是我們比較需要的。
經(jīng)過(guò)分析,對(duì)Result Api的封裝,我們就剩下的兩個(gè)重點(diǎn)問(wèn)題:
- 我們把 Launcher 的回調(diào)能在啟動(dòng)的方法中觸發(fā)。
- 實(shí)現(xiàn) Launcher 在 Activity/Fragment 中的自動(dòng)注冊(cè)。
下面我們就來(lái)實(shí)現(xiàn)吧。
四、Result Api 的封裝
我們需要做的是:
第一步我們把回調(diào)封裝到launch方法中,并簡(jiǎn)化創(chuàng)建的對(duì)象方式
第二步我們嘗試自動(dòng)注冊(cè)的功能
4.1 封裝簡(jiǎn)化創(chuàng)建方式
首先第一步,我們對(duì) Launcher 對(duì)象做一個(gè)封裝, 把 ActivityResultCallback 回調(diào)方法在 launch 方法中調(diào)用。
/**
* 對(duì)Result-Api的封裝,支持各種輸入與輸出,使用泛型定義
*/
@SuppressWarnings("unused")
public class BaseResultLauncher<I, O> {
private final androidx.activity.result.ActivityResultLauncher<I> launcher;
private final ActivityResultCaller caller;
private ActivityResultCallback<O> callback;
private MutableLiveData<O> unprocessedResult;
public BaseResultLauncher(@NonNull ActivityResultCaller caller, @NonNull ActivityResultContract<I, O> contract) {
this.caller = caller;
launcher = caller.registerForActivityResult(contract, (result) -> {
if (callback != null) {
callback.onActivityResult(result);
callback = null;
}
});
}
public void launch(@SuppressLint("UnknownNullness") I input, @NonNull ActivityResultCallback<O> callback) {
launch(input, null, callback);
}
public void launch(@SuppressLint("UnknownNullness") I input, @Nullable ActivityOptionsCompat options, @NonNull ActivityResultCallback<O> callback) {
this.callback = callback;
launcher.launch(input, options);
}
}上門(mén)是對(duì)Result的基本封裝,由于我們只想要 StartActivityForResult 這一種方式,所以我們定義一個(gè)特定的 GetSAFLauncher
/**
* 一般我們用這一個(gè)-StartActivityForResult 的 Launcher
*/
class GetSAFLauncher(caller: ActivityResultCaller) :
BaseResultLauncher<Intent, ActivityResult>(caller, ActivityResultContracts.StartActivityForResult()) {
//封裝另一種Intent的啟動(dòng)方式
inline fun <reified T> launch(
bundle: Array<out Pair<String, Any?>>? = null,
@NonNull callback: ActivityResultCallback<ActivityResult>
) {
val intent = Intent(commContext(), T::class.java).apply {
if (bundle != null) {
//調(diào)用自己的擴(kuò)展方法-數(shù)組轉(zhuǎn)Bundle
putExtras(bundle.toBundle()!!)
}
}
launch(intent, null, callback)
}
}注意這里調(diào)用的是 ActivityResultContracts.StartActivityForResult() 并且泛型的兩個(gè)參數(shù)是 Intent 和 ActivityResult。
如果大家想獲取文件,可以使用 GetContent() 泛型的參數(shù)就要變成 String 和 Uri 。由于我們通常不使用這種方式,所以這里不做演示。
封裝第一步之后我們就能這么使用了。
var safLauncher: GetSAFLauncher? = null
//其實(shí)就是 onCreate 方法
override fun init() {
safLauncher = GetSAFLauncher(this@Demo16RecordActivity)
}
//AFR
fun resultTest() {
safLauncher?.launch(Intent(mActivity, Demo10Activity::class.java)) { result ->
val data = result.data?.getStringExtra("text")
toast("拿到返回?cái)?shù)據(jù):$data")
}
}//或者使用我們自定義的簡(jiǎn)潔方式
fun resultTest() {
safLauncher?.launch<Demo10Activity> { result ->
val data = result.data?.getStringExtra("text")
toast("拿到返回?cái)?shù)據(jù):$data")
}
safLauncher?.launch<Demo10Activity>(arrayOf("id" to "123", "name" to "zhangsan")) { result ->
val data = result.data?.getStringExtra("text")
toast("拿到返回?cái)?shù)據(jù):$data")
}
}使用下來(lái)是不是簡(jiǎn)單了很多了,我們只需要?jiǎng)?chuàng)建一個(gè)對(duì)象就可以了,拿到這個(gè)對(duì)象調(diào)用launch即可實(shí)現(xiàn) startActivityForResult 的功能呢!
4.2 自動(dòng)注冊(cè)/按需注冊(cè)
可以看到相比原始的用法,雖然我們現(xiàn)在的用法就簡(jiǎn)單了很多,但是我們還是要在oncreate生命周期中創(chuàng)建 Launcher 對(duì)象,不然會(huì)報(bào)錯(cuò):
LifecycleOwners must call register before they are STARTED.
那我們有哪些方法處理這個(gè)問(wèn)題?
1)基類(lèi)定義
我們都已經(jīng)封裝成對(duì)象使用了,我們把創(chuàng)建的邏輯定義到BaseActivity/BaseFragment不就行了嗎?
abstract class AbsActivity() : AppCompatActivity(){
protected var safLauncher: GetSAFLauncher? = null
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView()
//Result-Api
safLauncher = GetSAFLauncher(this)
...
}
}這樣不就行了嗎?可以正常使用的。那有人可能說(shuō),你這個(gè)對(duì)象可能用不到,又不是每一個(gè)Activity都會(huì)用到 Launcher 對(duì)象,你這么無(wú)腦創(chuàng)建出來(lái)消耗內(nèi)存。
有辦法,按需加載!
2).懶加載
懶加載可以吧,我需要的時(shí)候就創(chuàng)建。
abstract class AbsActivity() : AppCompatActivity(){
val safLauncher by lazy { GetSAFLauncher(this) }
...
}額,等等,這樣的懶加載貌似是不行的,這在用的時(shí)候才初始化,一樣會(huì)報(bào)錯(cuò):
LifecycleOwners must call register before they are STARTED.
我們只能在頁(yè)面創(chuàng)建的時(shí)候就要明確,這個(gè)頁(yè)面是否需要這個(gè) Launcher 對(duì)象,如果要就要在onCreate中創(chuàng)建對(duì)象,如果確定不要 Launcher 對(duì)象,那么就不必創(chuàng)建對(duì)象。
那我們就這么做:
abstract class AbsActivity() : AppCompatActivity(){
protected var safLauncher: GetSAFLauncher? = null
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView()
if (needLauncher()) {
//Result-Api
safLauncher = GetSAFLauncher(this)
}
...
}
open protected fun needLauncher(): Boolean = false
}我們使用一個(gè)flag判斷不就行了嗎?這個(gè)頁(yè)面如果需要 Launcher 對(duì)象,重寫(xiě)方法返回true就行了。默認(rèn)是不創(chuàng)建這個(gè)對(duì)象的。
3).Kotlin委托
我們可以使用Kotlin的委托方式,把初始化的代碼和 Launcher 的對(duì)象獲取用接口封裝,然后提供對(duì)應(yīng)的實(shí)現(xiàn)類(lèi),不就可以完成按需添加 Launcher 的效果了嗎?
我們定義一個(gè)接口,由于邏輯都封裝在了別處,這里就盡量不改動(dòng)之前的代碼,只是定義初始化和提供對(duì)象兩種方法。
/**
* 定義是否需要SAFLauncher
*/
interface ISAFLauncher {
fun <T : ActivityResultCaller> T.initLauncher()
fun getLauncher(): GetSAFLauncher?
}接著定義這個(gè)實(shí)現(xiàn)類(lèi)
class SAFLauncher : ISAFLauncher {
private var safLauncher: GetSAFLauncher? = null
override fun <T : ActivityResultCaller> T.initLauncher() {
safLauncher = GetSAFLauncher(this)
}
override fun getLauncher(): GetSAFLauncher? = safLauncher
}然后我們就可以使用了:
class Demo16RecordActivity : BaseActivity, ISAFLauncher by SAFLauncher() {
//onCreate中直接初始化對(duì)象
override fun init() {
initLauncher()
}
//獲取到對(duì)象直接用即可,還是之前的幾個(gè)方法,沒(méi)有變。
fun resultTest() {
getLauncher()?.launch<Demo10Activity> { result ->
val data = result.data?.getStringExtra("text")
toast("拿到返回?cái)?shù)據(jù):$data")
}
}
}
效果都是一樣的:

這樣通過(guò)委托的方式,我們就能自己管理初始化,自己隨時(shí)獲取到對(duì)象調(diào)用launch方法。
如果你當(dāng)前的Activity不需要 startActivityForResult 這種功能,那么你不實(shí)現(xiàn)這個(gè)接口即可,如果想要 startActivityForResult 的功能,就實(shí)現(xiàn)接口委托實(shí)現(xiàn),從而實(shí)現(xiàn)按需加載的邏輯。
我們?cè)倩仡櫼幌?Result Api 需要封裝的兩個(gè)痛點(diǎn)與優(yōu)化步驟:
- 第一步我們把回調(diào)封裝到launch方法中,并簡(jiǎn)化創(chuàng)建的對(duì)象方式
- 第二步我們嘗試自動(dòng)注冊(cè)的功能
同時(shí)我們還對(duì)一些步驟做了更多的可能性分析,對(duì)主動(dòng)注冊(cè)的方式我們有三種方式,(當(dāng)然其實(shí)還有更多別的方式來(lái)實(shí)現(xiàn),我只寫(xiě)了我認(rèn)為比較簡(jiǎn)單方便的幾種方式)。
到此對(duì) Result Api的封裝就此結(jié)束。
總結(jié)
總的來(lái)說(shuō) Result Api 的封裝其實(shí)也不難,使用起來(lái)也是很簡(jiǎn)單了。如果大家是Kotlin項(xiàng)目我推薦使用委托的方式,如果是Java語(yǔ)言開(kāi)發(fā)的也可以用flag的方式實(shí)現(xiàn)按需加載的邏輯。
而不想使用 Result Api 那么使用原始的 startActivityForResult 也能實(shí)現(xiàn),那么我推薦你使用 Ghost 框架,可以更加方便快速的實(shí)現(xiàn)返回的功能。
本文對(duì)于 Result Api 的封裝也只是限于 startActivityForResult 這一個(gè)場(chǎng)景,不過(guò)我們這種方式是很方便擴(kuò)展的,如果大家想使用Result Api的方式來(lái)操作權(quán)限,文件等,都可以在 BaseResultLauncher 基礎(chǔ)上進(jìn)行擴(kuò)展。
到此這篇關(guān)于A(yíng)ndroid startActivityForResult的調(diào)用與封裝詳解的文章就介紹到這了,更多相關(guān)Android startActivityForResult內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺析Android手機(jī)衛(wèi)士手機(jī)定位的原理
手機(jī)定位的三種方式:網(wǎng)絡(luò)定位,基站定位,GPS定位。本文給大家介紹Android手機(jī)衛(wèi)士手機(jī)定位的原理,感興趣的朋友一起學(xué)習(xí)吧2016-04-04
Android自定義View構(gòu)造函數(shù)詳解
這篇文章主要為大家詳細(xì)介紹了Android自定義View構(gòu)造函數(shù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
Android ActionBar制作時(shí)鐘實(shí)例解析
這篇文章主要為大家詳細(xì)介紹了Android ActionBar制作時(shí)鐘的實(shí)現(xiàn)代碼,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05
Android開(kāi)發(fā)自學(xué)筆記(四):APP布局下
這篇文章主要介紹了Android開(kāi)發(fā)自學(xué)筆記(四):APP布局下,本文是上一篇的補(bǔ)充,需要的朋友可以參考下2015-04-04
Android實(shí)現(xiàn)多段顏色進(jìn)度條效果
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)多段顏色進(jìn)度條效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-01-01
Android編程實(shí)現(xiàn)PendingIntent控制多個(gè)鬧鐘的方法
這篇文章主要介紹了Android編程實(shí)現(xiàn)PendingIntent控制多個(gè)鬧鐘的方法,涉及PendingIntent屬性設(shè)置與使用的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-12-12

