五月份的Google I/O大會(huì),Google推出了Android Jetpack架構(gòu)組件延欠,以幫助開(kāi)發(fā)者構(gòu)建更加優(yōu)質(zhì)的應(yīng)用隅津,Jetpack包含了以下組件
- Navigation
用于管理APP頁(yè)面跳轉(zhuǎn)導(dǎo)航辩撑,切換fragment更加直觀,可視化界面展示fragment的切換流程圖
- WorkManager
WorkManager API可以輕松指定可延遲的異步任務(wù)以及何時(shí)運(yùn)行它們方面。 這些API允許您創(chuàng)建任務(wù)并將其交給WorkManager立即運(yùn)行或在適當(dāng)?shù)臅r(shí)間運(yùn)行。 例如色徘,應(yīng)用可能需要不時(shí)從網(wǎng)絡(luò)下載新資源恭金。 使用這些類(lèi),您可以設(shè)置任務(wù)褂策,為其運(yùn)行選擇適當(dāng)?shù)沫h(huán)境(例如“僅在設(shè)備正在充電和在線”時(shí))横腿,并將其交給WorkManager以在滿(mǎn)足條件時(shí)運(yùn)行。 即使您的應(yīng)用程序強(qiáng)制退出或設(shè)備重新啟動(dòng)斤寂,該任務(wù)仍可保證運(yùn)行耿焊。
- LifeCycle
每個(gè) Android 開(kāi)發(fā)者都應(yīng)該面對(duì)過(guò)生命周期問(wèn)題,即操作系統(tǒng)啟動(dòng)扬蕊、停止和銷(xiāo)毀 Activity搀别。這意味著開(kāi)發(fā)者需要根據(jù)生命周期的不同階段丹擎,有針對(duì)性地管理組件狀態(tài)尾抑,比如用于更新用戶(hù)界面的可觀察對(duì)象。生命周期管理(Lifecycles)幫助開(kāi)發(fā)者創(chuàng)建 “可感知生命周期的” 組件蒂培,讓其自己管理自己的生命周期再愈,從而減少內(nèi)存泄露和崩潰的可能性。生命周期庫(kù)是其他架構(gòu)組件(如 LiveData)的基礎(chǔ)护戳。
- Paging
簡(jiǎn)單來(lái)說(shuō)翎冲,其實(shí)就是一個(gè)官方的分頁(yè)加載數(shù)據(jù)框架
- Room
Room persistence庫(kù)為SQLite提供了一個(gè)抽象層,以便在利用SQLite的全部功能的同時(shí)實(shí)現(xiàn)更強(qiáng)大的數(shù)據(jù)庫(kù)訪問(wèn)媳荒。
navigation
本篇文章主要闡述Navigation如何管理TestNavigationActivity中的FragmentA抗悍、FragmentB、FragmentC三個(gè)fragment钳枕,以及fragment之間數(shù)據(jù)傳遞
注意:目前jetpack架構(gòu)組件需要運(yùn)行在Android Studio 3.2以上
navigation導(dǎo)航文件
在APP的res目錄下缴渊,創(chuàng)建一個(gè)navigation文件夾,在此文件夾下新增一個(gè)navigation
的xml文件鱼炒,此處新增一個(gè) test_navi.xml衔沼,內(nèi)容如下:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/test_navi"
app:startDestination="@id/fragmentA">
<fragment
android:id="@+id/fragmentA"
android:name="ywq.ares.jetpacklearning.ui.main.navigation.FragmentA"
android:label="fragmentA"
tools:layout="@layout/fragmentA">
<action
android:id="@+id/action_fragmentA_to_fragmentB"
app:destination="@id/fragmentB"
app:exitAnim="@android:anim/slide_out_right" />
</fragment>
<fragment
android:id="@+id/fragmentB"
android:name="ywq.ares.jetpacklearning.ui.main.navigation.FragmentB"
android:label="fragmentB"
tools:layout="@layout/fragmentB">
<argument android:name="title" app:type="string" android:defaultValue="test"/>
<argument android:name="num" app:type="integer" android:defaultValue="100"/>
<action
android:id="@+id/action_fragmentB_to_fragmentC"
app:destination="@id/fragmentC"
app:exitAnim="@android:anim/slide_out_right" />
</fragment>
<fragment
android:id="@+id/fragmentC"
android:name="ywq.ares.jetpacklearning.ui.main.navigation.FragmentC"
android:label="fragmentC"
tools:layout="@layout/fragmentC">
<action
android:id="@+id/action_fragmentC_to_fragmentA"
app:destination="@id/fragmentA"
app:exitAnim="@android:anim/slide_out_right" />
</fragment>
</navigation>
很明顯,這是一個(gè)用于管理fragment跳轉(zhuǎn)意圖的文件
- navigation根節(jié)點(diǎn) startDestination 表示第一個(gè)顯示的fragment
app:startDestination="@id/fragmentA"
- fragment 節(jié)點(diǎn)
- name屬性表示所屬的fragment類(lèi)
- action 節(jié)點(diǎn) destination屬性用于指定下一個(gè)目標(biāo)fragment昔瞧、進(jìn)入退出動(dòng)畫(huà)等
- argument 用于傳遞數(shù)據(jù)(下面會(huì)講)
在AS中指蚁,在navigation界面點(diǎn)擊Design標(biāo)簽,可以看到非常直觀的fragment切換流程
創(chuàng)建TestNavigationActivity
提示:必須繼承AppCompatActivity 或 FragmentActivity 否則會(huì)報(bào)以下異常
java.lang.RuntimeException: Unable to start activity ComponentInfo{ywq.ares.jetpacklearning/ywq.ares.jetpacklearning.ui.main.navigation.TestNavigationActivity}: android.view.InflateException: Binary XML file line #9: Binary XML file line #9: Error inflating class fragment
....
Caused by: android.app.Fragment$InstantiationException: Trying to instantiate a class androidx.navigation.fragment.NavHostFragment that is not a Fragment
activity布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/fragment_first2"
app:navGraph="@navigation/test_navi"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
其中fragment必須設(shè)置id,否則會(huì)報(bào)錯(cuò)
Caused by: java.lang.IllegalArgumentException: Binary XML file line #9: Must specify unique android:id, android:tag, or have a parent with an id for androidx.navigation.fragment.NavHostFragment
navGraph 用來(lái)指定上面的導(dǎo)航意圖文件 test_navi.xml
app:navGraph="@navigation/test_navi"
name 必須指定為以下值自晰,表示這是一個(gè)可以切換fragment的控件
android:name="androidx.navigation.fragment.NavHostFragment"
假如你指定了其他fragment凝化,會(huì)報(bào)以下錯(cuò)誤
Caused by: java.lang.IllegalStateException: Fragment FirstFragment{b0bdbe5 #0 id=0x7f08004e} does not have a NavController set
defaultNavHost 表示是否攔截返回鍵,默認(rèn)為false, 假如頁(yè)面處于第二個(gè)fragment酬荞,這是按下返回鍵搓劫,會(huì)退出當(dāng)前activity而不是回到上一個(gè)fragment劣光。true則反之
app:defaultNavHost="true"
切換fragment
從FragmentA切換到FragmentB,以前的方式
getSupportFragmentManager().beginTransaction().show(fragmentB).hide(fragmentA).commit();
通過(guò)Navigation的方式
//在fragmentA中調(diào)用
NavHostFragment.findNavController(fragmentA).navigate(R.id.action_fragmentA_to_fragmentB)
//假如是通過(guò)點(diǎn)擊某個(gè)按鈕進(jìn)行切換的話(huà),也可以通過(guò)以下方式,id為目標(biāo)fragment的id
jumpBtn.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.fragment_third));
數(shù)據(jù)傳遞
那么fragmentA如何傳遞數(shù)據(jù)到fragmentB呢糟把?
- 傳統(tǒng)方式
//fragmentA傳遞
var bundle = Bundle()
bundle.putString("title","xixi")
bundle.putInt("num",200)
NavHostFragment.findNavController(fragmentA).navigate(R.id.action_fragmentA_to_fragmentB,bundle)
//fragmentB接收
val title = arguments?.getString("title")
val num = arguments?.getString("num")
以上方式存在一個(gè)弊端绢涡,就是接收方必須知道對(duì)應(yīng)的key才能正確接收對(duì)應(yīng)的值,這在團(tuán)隊(duì)協(xié)作上是極其不方便的遣疯。
- Navigation組件提供的方式雄可,安裝插件,編譯自動(dòng)生成參數(shù)對(duì)象
項(xiàng)目根目錄的builde.gradle文件
dependencies {
...
classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha02'
...
}
app目錄的build.gradle文件
apply plugin: 'androidx.navigation.safeargs'
在文章開(kāi)頭缠犀,navigation導(dǎo)航文件中可以設(shè)置fragment所接收的參數(shù)
<fragment
android:id="@+id/fragmentB"
android:name="ywq.ares.jetpacklearning.ui.main.navigation.FragmentB"
android:label="fragmentB"
tools:layout="@layout/fragmentB">
<argument android:name="title" app:type="string" android:defaultValue="test"/>
<argument android:name="num" app:type="integer" android:defaultValue="100"/>
</fragment>
rebuild項(xiàng)目数苫,會(huì)自動(dòng)生成對(duì)應(yīng)的參數(shù)類(lèi)(規(guī)則:fragment的類(lèi)名+Args),上面那個(gè)fragment生成的參數(shù)類(lèi)為FragmentBArgs
//fragmentA傳遞
val arg = FragmentBArgs.Builder()
arg.title = "haha"
arg.num = 100
NavHostFragment.findNavController(this).navigate(R.id.action_fragment1_to_fragment2,arg.build().toBundle())
//fragmentB接收
val args = SecondFragmentArgs.fromBundle(arguments)
val title = args.title
val num = args.num
相對(duì)于傳遞方式辨液,這種方式能夠非常清晰明了fragment所有需要接收的參數(shù)類(lèi)型乌妒,不用擔(dān)心字符串拼錯(cuò)而導(dǎo)致傳遞無(wú)法接收的問(wèn)題
遺留問(wèn)題
盡管通過(guò)Navigation方式管理Fragment的代碼簡(jiǎn)潔而且直觀,但是有一個(gè)非常致命的問(wèn)題研乒,就是同一個(gè)Fragment會(huì)被重復(fù)創(chuàng)建什乙,這樣會(huì)導(dǎo)致每次切換fragment的時(shí)候都會(huì)重新去請(qǐng)求數(shù)據(jù),非常浪費(fèi)系統(tǒng)資源燎悍,而且降低了APP的用戶(hù)體驗(yàn)敬惦,目前還找不到解決方案,大概以后官方會(huì)解決這個(gè)問(wèn)題吧~
下一篇谈山,我們來(lái)講一講Jetpack架構(gòu)組件中的WorkManager的用法