<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
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的封裝,我們就剩下的兩個重點問題:
下面我們就來實現吧。
我們需要做的是:
第一步我們把回撥封裝到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 需要封裝的兩個痛點與優化步驟:
同時我們還對一些步驟做了更多的可能性分析,對主動註冊的方式我們有三種方式,(當然其實還有更多別的方式來實現,我只寫了我認為比較簡單方便的幾種方式)。
到此對 Result Api的封裝就此結束。
總的來說 Result Api 的封裝其實也不難,使用起來也是很簡單了。如果大家是Kotlin專案我推薦使用委託的方式,如果是Java語言開發的也可以用flag的方式實現按需載入的邏輯。
而不想使用 Result Api 那麼使用原始的 startActivityForResult 也能實現,那麼我推薦你使用 Ghost 框架,可以更加方便快速的實現返回的功能。
本文對於 Result Api 的封裝也只是限於 startActivityForResult 這一個場景,不過我們這種方式是很方便擴充套件的,如果大家想使用Result Api的方式來操作許可權,檔案等,都可以在 BaseResultLauncher 基礎上進行擴充套件。
到此這篇關於Android startActivityForResult的呼叫與封裝詳解的文章就介紹到這了,更多相關Android startActivityForResult內容請搜尋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