Jetpack之優雅的Activity回調

在我們的日常開發中,從一個Activity打開另一個Activity並接收其回調結果是一個很普遍的場景,這其中包括打開其他應用的Activity,通常我們的做法都是通過activity.startActivityForResult()方法來實現。這其中除了我們應用內業務相關頁面的跳轉之外,還有很多像打開相冊、調起相機等和系統Activity交互的場景,這些場景每次都要很麻煩的維護很多代碼,即使是原生頁面跳轉也要維護requestCode和bundle數據的key值等常量,代碼臃腫難以維護。Jetpack出現後,現在在處理這種非單向操作的場景時有了一種更優雅的方式,我們來看看它是怎麼幫我們封裝的。

首先,我們需要添加兩個依賴(androidx.*中的)

api 'androidx.fragment:fragment:1.3.0-alpha06'
api 'androidx.activity:activity:1.2.0-alpha06'

然後我們順着Activity的繼承關係找下去會看到ComponentActivity的繼承關係:

其中的ActivityResultCaller是註冊回調的關鍵接口,ActivityResultRegistryOwner是用來獲取Activity的ActivityResultRegistry對象的,通常用於Fragment或者其他非Activity類使用。

ActivityResultCaller接口中有兩個方法:

@NonNull
<I, O> ActivityResultLauncher<I> registerForActivityResult(
        @NonNull ActivityResultContract<I, O> contract,
        @NonNull ActivityResultCallback<O> callback);
@NonNull
<I, O> ActivityResultLauncher<I> registerForActivityResult(
        @NonNull ActivityResultContract<I, O> contract,
        @NonNull ActivityResultRegistry registry,
        @NonNull ActivityResultCallback<O> callback);

這兩個方法的差別就是三參數的那個多了個ActivityResultRegistry類型的參數,如果在Activity中調用則不需要這個參數,因爲ComponentActivity中幫我們維護了一個ActivityResultRegistry對象了,如果在自定義組件或者其它類中可以使用三參數方法並調用activity的getActivityResultRegistry()方法傳入該參數。

現在我們知道了每一個ComponentActivity的子類都可以使用這兩個方法註冊ActivityResult回調,我們從註冊開始順着看看是怎麼實現的。

img

我們這裏在Activity中註冊,所以使用兩個參數的註冊方法。第一個參數就是Contract契約,是一個ActivityResultContract類型的對象,裏面有兩個關鍵方法,createIntent()和parseResult(),createIntent()就是來創建用於請求打開新Activity的intent的;其中的第二個參數是泛型,可以自定義,parseResult()用於最後根據resultCode和data創建ActivityResult對象傳給ActivityResultCallback的onActivityResult()方法內使用,後面會看到。

第二個參數就是ActivityResultCallback對象了,重寫onActivityResult方法就可以處理回調了,這就相當於之前我們在Activity中重寫的onActivityResult方法。

image-20200703141056335

進到registerForActivityResult方法裏面可以看到兩個參數的registerForActivityResult也是調用了三參數的重載方法,ActivityResultRegistry對象用的是ComponentActivity內部定義的mActivityResultRegistry,三參數的方法最終返回的是mActivityResultRegistry的register方法的調用,這個方法有四個參數,後面兩個就是ActivityResultContract對象和ActivityResultCallback對象,第一個參數是key值,稍後會提到,第二個參數是LifecycleOwner對象,這裏就是Activity本身(因爲ComponentActivity實現了它),現在我們進到ActivityResultRegistry類的register裏面看看:

image-20200703141309938

我們會看到首先通過registerKey方法獲取requestCode:

image-20200703141558444

這裏可以看到requestCode是通過當前類裏面的一個AtomicInteger對象產生的,所以每次產生的都會是最新且唯一的(這裏注意getAndIncrement()方法返回的是增加前的值),這就不需要我們自己去維護requestCode了,然後把它保存在map裏,這裏有兩個map,一個是mKeyToRc,它保存的是key->requestCode,另一個是mRcToKey,它保存的是requestCode-key,後面會看到他們的用武之地。

接着有一個mKeyToCallback的map以傳進來的key值爲鍵,保存了一個組合了callback和contract的CallbackAndContract對象,接下來根據lifecycleOwner獲取到Activity的生命週期對象。

接下來的mPendingResults部分的代碼主要是爲了處理Activity非主動銷燬時的恢復,後面會提到,值得注意的是,這裏會使用正在進行註冊的新callback來處理之前未正常執行完成的result。這裏判斷如果存在尚未正常執行完的回調且Activity此時生命週期已是STARTED之後(onStart()方法執行完),則先執行未完成的回調;若是生命週期在STARTED之前則給其生命週期添加監聽,STARTED之後自動執行回調。

然後統一添加銷燬時的註銷工作的監聽,最後會返回一個ActivityResultLauncher對象,我們就用這個對象來啓動指定的Activity。

launcher.launch("image/*")

因爲我們之前使用的contract是ActivityResultContracts.GetContent(),所以這裏我們傳入一個String作爲intent的type。

image-20200703142447801

我們看到ActivityResultLauncher類裏有兩個launch方法,只有一個是需要重寫的,因爲ActivityOptionsCompat參數是可選的,ActivityOptionsCompat就是一個封裝了Activity之間轉換動畫的類(還有一點其他設置),這裏沒傳就是null。

我們上面說到這個launcher實際上就是那個register方法返回的匿名對象,所以這裏的launch方法就是調用的那個匿名對象的launch方法,回過頭去看,匿名對象的launch方法實際上是調用的ActivityResultRegistry類的onLaunch方法,並且把生成的requestCode、contract、傳入的“iamge/*”、還有一個ActivityOptionsCompat對象帶過去,所以到了這裏實際上調用的就是ComponentActivity裏的mActivityResultRegistry的onLaunch方法:

image-20200703143800799

onLaunch()方法是ActivityResultRegistry類裏唯一的abstract方法。我們看看它都做了些什麼:

首先通過contract的getSynchronousResult()方法拿到一個ActivityResultContract.SynchronousResult(這個類中只有一個泛型對象)對象,如果這個對象不爲空則直接在主線程調用dispatchResult()方法結束launch,ActivityResultContract的getSynchronousResult()方法有默認實現(默認返回null),是一個可選方法,RequestPermission和RequestMultiplePermissions裏會用得到,主要是用它處理一些不需要啓動Activity就能知道預期結果的場景。dipatchResult方法很簡單,不做說明了,看下圖:

image-20200703150421901

接下來就是正常情況下最後啓動Activity的流程了,可以看到出了兩個特殊情況需要特別處理之外,其他的啓動方式最後都是調用了我們熟悉的ActivityCompat.startActivityForResult(activity, intent, requestCode, optionsBundle)方法,ActivityResultContracts裏面已經給我們封裝好了常用的啓動系統Activity的場景,如果我們需要啓動自己的Activity,我們也可以使用ActivityResultContracts中的StartActivityForResult來實現。

到現在爲止,我們可以很明顯地感受到這種方式使我們啓動Activity變得簡潔優雅,但還有最後一個問題,最終的回調是如何觸發的呢?前面我們看到了Immediate result path的回調,現在我們來看一下啓動Activity之後返回的回調是怎麼觸發的。

官方文檔中並沒有回調觸發的新方法,所以我們猜測應該還是finish()這個老朋友的任務:

image-20200703153317825

通過註釋我們知道最終會回調到調用者的onActivityResult()方法:

image-20200703153556965

註釋告訴我們已經不提倡在Activity裏重寫這個方法來接收回調了,提倡使用我們新介紹的這種方式。可以看到在這個統一的回調方法裏,會首先調用mActivityResultRegistry的dispatchResult方法,如果返回false則繼續走傳統的回調方式:

image-20200703153928351

這裏通過requestCode拿到一開始註冊的key,再通過key拿到CallbackAndContract對象,最終執行doDispatch()方法:

image-20200703154224412

這裏的resultCode和data都是上一個Activity調用setResult()方法設置的。可以看到,在這裏調用了contract的parseResult方法拿到ActivityResult對象,回調到前面註冊時callback的onActivityResult方法中,else分支的意義在於如果出現異常沒取到回調對象(有可能是註冊的時候傳入的callback參數是null,雖然registerForActivityResult的callback參數加了註解明確表示不能爲空,但是因爲Java不像kotlin那樣傳入null會報錯,所以這裏還是要特殊容錯處理)則會放到表示未處理的mPendingResults中,等到有新的註冊行爲時會通過新註冊的callback對象來處理,前面已經提過。

至此,一個完整的新式啓動回調流程就完成了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章