您好,登錄后才能下訂單哦!
今天小編給大家分享一下Android startActivityForResult怎么調用與封裝的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
startActivityForResult 可以說是我們常用的一種操作了,用于啟動新頁面并拿到這個頁面返回的數據,是兩個 Activity 交互的基本操作。
雖然可以通過接口,消息總線,單例池,ViewModel 等多種方法來間接的實現這樣一個功能,但是 startActivityForResult 還是使用最方便的。
目前有哪些方式實現 startActivityForResult 的功能呢?
有新老兩種方式,過時的方法是原生Activity/Fragment的 startActivityForResult 方法。另一種方法是 Activity Result API 通過 registerForActivityResult 來注冊回調。
不管是Activity還是Fragment,我們都可以使用 startActivityForResult
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == 120 && resultCode == -1) { toast("接收到返回的數據:" + data?.getStringExtra("text")) } }
可以看到雖然標記過時了,但是 startActivityForResult 這種方法是可以用的,我們一直這么用的,老項目中有很多頁面都是這么定義的。也并沒有什么問題。
不過既然谷歌推薦我們使用 Result Api 我們在以后使用 startActivityForResult 的時候還是推薦使用新的方式。
在之前我們使用 startActivityForResult 這種方式的時候,為了更加方便的私有,有一種很流行的方式 Ghost 。
它使用一種 GhostFragment 的空視圖當做一次中轉,這種思路在現在看來已經不稀奇了,很多框架如Glide,權限申請等都是用的這種方案。
它的大致實現流程為:
Activty/Fragment -> add GhostFragment -> onAttach 中 startActivityForResult -> GhostFragment onActivityResult接收結果 -> callback回調給Activty/Fragment
總體需要兩個類就可以完成這個邏輯,一個是中轉Fragment,一個是管理類:
/** * 封裝Activity Result的API * 使用空Fragemnt的形式調用startActivityForResult并返回回調 * * Activty/Fragment——>add GhostFragment——>onAttach中startActivityForResult * ——>GhostFragment onActivityResult接收結果——>callback回調給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 * 啟動的時候添加Fragment 返回的時移除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的擴展方法來對它進行進一步的封裝
//真正執行AcytivityForResult的方法,使用Ghost的方式執行 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) { //調用自己的擴展方法-數組轉Bundle putExtras(bundle.toBundle()!!) } } Ghost.launchActivityForResult(this, intent, callback) }
使用起來就超級簡單了:
gotoActivityForResult<Demo10Activity> { val text = it?.getStringExtra("text") toast("拿到返回數據:$text") } gotoActivityForResult<Demo10Activity>(bundle = arrayOf("id" to "123", "name" to "zhangsan")) { val text = it?.getStringExtra("text") toast("拿到返回數據:$text") }
其實看Ghost的原來就看得出,他本質上還是對 startActivityForResult 的調用與封裝,還是過期的方法,那么如何使用新的方式,谷歌推薦我們怎么用?
Activity Result API :
它是 Jetpack 的一個組件,這是官方用于替代
startActivityForResult() 和 onActivityResult() 的工具,我們以Activity 1.2.4版本為例:
implementation "androidx.activity:activity-ktx:1.2.4"
那么如何基礎的使用它呢:
private val safLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == RESULT_OK) { val data = result.data?.getStringExtra("text") toast("拿到返回數據:$data") } } //在方法中使用 safLauncher?.launch(Intent(mActivity, Demo10Activity::class.java))
看起來實現很簡單,但是有幾點要注意,Launcher 的創建需要在onStart生命周期之前,并且回調是在 Launcher 中處理的。并且 這些 Launcher 并不是只能返回Activity的Result的,還有其他的啟動方式:
StartActivityForResult()
StartIntentSenderForResult()
RequestMultiplePermissions()
RequestPermission()
TakePicturePreview()
TakePicture()
TakeVideo()
PickContact()
GetContent()
GetMultipleContents()
OpenDocument()
OpenMultipleDocuments()
OpenDocumentTree()
CreateDocument()
可以看到這些方式其實對我們來說很多沒必要,在真正的開發中只有 StartActivityForResult 這一種方式是我們的剛需。
為什么?畢竟現在誰還用這種方式申請權限,操作多媒體文件。相信大家也都是使用框架來處理了,所以我們這里只對 StartActivityForResult 這一種方式做處理。畢竟這才是我們使用場景最多的,也是我們比較需要的。
經過分析,對Result Api的封裝,我們就剩下的兩個重點問題:
我們把 Launcher 的回調能在啟動的方法中觸發。
實現 Launcher 在 Activity/Fragment 中的自動注冊。
下面我們就來實現吧。
我們需要做的是:
第一步我們把回調封裝到launch方法中,并簡化創建的對象方式
第二步我們嘗試自動注冊的功能
首先第一步,我們對 Launcher 對象做一個封裝, 把 ActivityResultCallback 回調方法在 launch 方法中調用。
/** * 對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); } }
上門是對Result的基本封裝,由于我們只想要 StartActivityForResult 這一種方式,所以我們定義一個特定的 GetSAFLauncher
/** * 一般我們用這一個-StartActivityForResult 的 Launcher */ class GetSAFLauncher(caller: ActivityResultCaller) : BaseResultLauncher<Intent, ActivityResult>(caller, ActivityResultContracts.StartActivityForResult()) { //封裝另一種Intent的啟動方式 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) { //調用自己的擴展方法-數組轉Bundle putExtras(bundle.toBundle()!!) } } launch(intent, null, callback) } }
注意這里調用的是 ActivityResultContracts.StartActivityForResult() 并且泛型的兩個參數是 Intent 和 ActivityResult。
如果大家想獲取文件,可以使用 GetContent() 泛型的參數就要變成 String 和 Uri 。由于我們通常不使用這種方式,所以這里不做演示。
封裝第一步之后我們就能這么使用了。
var safLauncher: GetSAFLauncher? = null //其實就是 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("拿到返回數據:$data") } }
//或者使用我們自定義的簡潔方式
fun resultTest() { safLauncher?.launch<Demo10Activity> { result -> val data = result.data?.getStringExtra("text") toast("拿到返回數據:$data") } safLauncher?.launch<Demo10Activity>(arrayOf("id" to "123", "name" to "zhangsan")) { result -> val data = result.data?.getStringExtra("text") toast("拿到返回數據:$data") } }
使用下來是不是簡單了很多了,我們只需要創建一個對象就可以了,拿到這個對象調用launch即可實現 startActivityForResult 的功能呢!
可以看到相比原始的用法,雖然我們現在的用法就簡單了很多,但是我們還是要在oncreate生命周期中創建 Launcher 對象,不然會報錯:
LifecycleOwners must call register before they are STARTED.
那我們有哪些方法處理這個問題?
1)基類定義
我們都已經封裝成對象使用了,我們把創建的邏輯定義到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) ... } }
這樣不就行了嗎?可以正常使用的。那有人可能說,你這個對象可能用不到,又不是每一個Activity都會用到 Launcher 對象,你這么無腦創建出來消耗內存。
有辦法,按需加載!
2).懶加載
懶加載可以吧,我需要的時候就創建。
abstract class AbsActivity() : AppCompatActivity(){ val safLauncher by lazy { GetSAFLauncher(this) } ... }
額,等等,這樣的懶加載貌似是不行的,這在用的時候才初始化,一樣會報錯:
LifecycleOwners must call register before they are STARTED.
我們只能在頁面創建的時候就要明確,這個頁面是否需要這個 Launcher 對象,如果要就要在onCreate中創建對象,如果確定不要 Launcher 對象,那么就不必創建對象。
那我們就這么做:
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 }
我們使用一個flag判斷不就行了嗎?這個頁面如果需要 Launcher 對象,重寫方法返回true就行了。默認是不創建這個對象的。
3).Kotlin委托
我們可以使用Kotlin的委托方式,把初始化的代碼和 Launcher 的對象獲取用接口封裝,然后提供對應的實現類,不就可以完成按需添加 Launcher 的效果了嗎?
我們定義一個接口,由于邏輯都封裝在了別處,這里就盡量不改動之前的代碼,只是定義初始化和提供對象兩種方法。
/** * 定義是否需要SAFLauncher */ interface ISAFLauncher { fun <T : ActivityResultCaller> T.initLauncher() fun getLauncher(): GetSAFLauncher? }
接著定義這個實現類
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中直接初始化對象 override fun init() { initLauncher() } //獲取到對象直接用即可,還是之前的幾個方法,沒有變。 fun resultTest() { getLauncher()?.launch<Demo10Activity> { result -> val data = result.data?.getStringExtra("text") toast("拿到返回數據:$data") } } }
效果都是一樣的:
這樣通過委托的方式,我們就能自己管理初始化,自己隨時獲取到對象調用launch方法。
如果你當前的Activity不需要 startActivityForResult 這種功能,那么你不實現這個接口即可,如果想要 startActivityForResult 的功能,就實現接口委托實現,從而實現按需加載的邏輯。
我們再回顧一下 Result Api 需要封裝的兩個痛點與優化步驟:
第一步我們把回調封裝到launch方法中,并簡化創建的對象方式
第二步我們嘗試自動注冊的功能
同時我們還對一些步驟做了更多的可能性分析,對主動注冊的方式我們有三種方式,(當然其實還有更多別的方式來實現,我只寫了我認為比較簡單方便的幾種方式)。
到此對 Result Api的封裝就此結束。
以上就是“Android startActivityForResult怎么調用與封裝”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。