日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

前言

最近兩年,MVVM的呼聲越來(lái)越高,說(shuō)實(shí)話,在經(jīng)歷了MVP的臃腫,MVP的繁瑣,我有點(diǎn)怕了。但是這次google官方帶來(lái)的一系列為MVVM架構(gòu)設(shè)計(jì)的武器—Jetpack,真的讓我驚喜到了。

也許你還沒(méi)有使用這個(gè)新的武器,那么我真的建議你去使用一下,感受下這個(gè)新武器的快準(zhǔn)狠,感受下這個(gè)新架構(gòu)的精妙解耦。

介紹

2018年谷歌I/O,Jetpack橫空出世,官方介紹如下:

Jetpack 是一套庫(kù)、工具和指南,可幫助開(kāi)發(fā)者更輕松地編寫優(yōu)質(zhì)應(yīng)用。這些組件可幫助您遵循最佳做法、讓您擺脫編寫樣板代碼的工作并簡(jiǎn)化復(fù)雜任務(wù),以便您將精力集中放在所需的代碼上。

一直以來(lái),Android開(kāi)發(fā)都充斥了大量的不規(guī)范的操作和重復(fù)代碼,比如生命周期的管理,開(kāi)發(fā)過(guò)程的重復(fù),項(xiàng)目架構(gòu)的選擇等等。所以Google為了規(guī)范開(kāi)發(fā)行為,就推出這套指南,旨在讓開(kāi)發(fā)者們能夠更好,更快,更規(guī)范地開(kāi)發(fā)出優(yōu)質(zhì)應(yīng)用。

當(dāng)然,這兩年的實(shí)踐也確實(shí)證明了Jetpack做到了它介紹的那樣,便捷,快速,優(yōu)質(zhì)。所以我們作為開(kāi)發(fā)者還是應(yīng)該早點(diǎn)應(yīng)用到這些工具,提高自己的開(kāi)發(fā)效率,也規(guī)范我們自己的開(kāi)發(fā)行為。

今天給大家?guī)?lái)的是Jetpack中的架構(gòu)組件,這個(gè)模塊的組件可以說(shuō)就是為MVVM框架服務(wù)的,每個(gè)庫(kù)也都是可以單獨(dú)使用的。

Jetpack-架構(gòu)組件

先簡(jiǎn)單說(shuō)下MVVM,Model—View—ViewModel。

  • Model層主要指數(shù)據(jù),比如服務(wù)器數(shù)據(jù),本地?cái)?shù)據(jù)庫(kù)數(shù)據(jù),所以網(wǎng)絡(luò)操作和數(shù)據(jù)庫(kù)讀取就是這一層,只保存數(shù)據(jù)。
  • View層主要指UI相關(guān),比如xml布局文件,Activity界面顯示
  • ViewModel層是MVVM的核心,連接view和model,需要將model的數(shù)據(jù)展示到view上,以及view上的操作數(shù)據(jù)反映轉(zhuǎn)化到model層,所以就相當(dāng)于一個(gè)雙向綁定。

所以就需要,databinding進(jìn)行數(shù)據(jù)的綁定,單向或者雙向。viewmodel進(jìn)行數(shù)據(jù)管理,綁定view和數(shù)據(jù)。lifecycle進(jìn)行生命周期管理。LiveData進(jìn)行數(shù)據(jù)的及時(shí)反饋。 迫不及待了吧,跟隨我一起看看每個(gè)庫(kù)的神奇之處。

數(shù)據(jù)綁定

數(shù)據(jù)綁定庫(kù)是一種支持庫(kù),借助該庫(kù),您可以使用聲明性格式(而非程序化地)將布局中的界面組件綁定到應(yīng)用中的數(shù)據(jù)源。

主要指的就是數(shù)據(jù)綁定庫(kù)DataBinding,下面從六個(gè)方面具體介紹下

配置應(yīng)用使用數(shù)據(jù)綁定:

   android {
        ...
        dataBinding {
            enabled = true
        }
    }  

1)布局和綁定表達(dá)式通過(guò)數(shù)據(jù)綁定,我們可以讓xml布局文件中的view與數(shù)據(jù)對(duì)象進(jìn)行綁定和賦值,并且可以借助表達(dá)式語(yǔ)言編寫表達(dá)式來(lái)處理視圖分派的事件。舉個(gè):

    //布局 activity_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
       <data>
           <variable name="user" type="com.example.User"/>
       </data>
       <TextView android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@{user.name}"/>
    </layout>
    
    //實(shí)體類User
    data class User(val name: String)
    
    
    //Activity賦值
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding: ActivityMainBinding = DataBindingUtil.setContentView(
                this, R.layout.activity_main)
        binding.user = User("Bob")
    }
    

通過(guò)@{}符號(hào),可以在布局中使用數(shù)據(jù)對(duì)象,并且可以通過(guò)DataBindingUtil獲取賦值對(duì)象。并且@{}里面的表達(dá)式語(yǔ)言支持多種運(yùn)算符,包括算術(shù)運(yùn)算符,邏輯運(yùn)算符等等。

2)可觀察的數(shù)據(jù)對(duì)象可觀察性是指一個(gè)對(duì)象將其數(shù)據(jù)變化告知其他對(duì)象的能力。通過(guò)數(shù)據(jù)綁定庫(kù),您可以讓對(duì)象、字段或集合變?yōu)榭捎^察。

比如上文剛說(shuō)到的User類,我們將name屬性改成可觀察對(duì)象,

   data class User(val name: ObservableField<String>)
   
   val userName = ObservableField<String>()
   userName.set("Bob")

   val binding: ActivityMainBinding = DataBindingUtil.setContentView(
                this, R.layout.activity_main)
   binding.user = User(userName)   

然后綁定到布局中,這時(shí)候這個(gè)User的name屬性就是被觀察對(duì)象了,如果userName改變,布局里面的TextView顯示數(shù)據(jù)也會(huì)跟著改變,這就是可觀察數(shù)據(jù)對(duì)象。

3)生成的綁定類

剛才我們獲取綁定布局是通過(guò)DataBindingUtil.setContentView方法生成ActivityMainBinding對(duì)象并綁定布局。那么ActivityMainBinding類是怎么生成的呢?只要你的布局用layout屬性包圍,編譯后就會(huì)自動(dòng)生成綁定類,類名稱基于布局文件的名稱,它會(huì)轉(zhuǎn)換為 Pascal 大小寫形式并在末尾添加 Binding 后綴。

正常創(chuàng)建綁定對(duì)象是通過(guò)如下寫法:

    //Activity
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: MyLayoutBinding = MyLayoutBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
    
    
    //Fragment
    @Nullable
    fun onCreateView( inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        mDataBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_layout, container, false)
        return mDataBinding.getRoot()
    }

4)綁定適配器

適配器這里指的是布局中的屬性設(shè)置,android:text="@{user.name}"表達(dá)式為例,庫(kù)會(huì)查找接受user.getName()所返回類型的setText(arg) 方法。 重要的是,我們可以自定義這個(gè)適配器了,也就是布局里面的屬性我們可以隨便定義它的名字和作用。來(lái)個(gè)

    @BindingAdapter("imageUrl")
    fun loadImage(view: ImageView, url: String) {
        Picasso.get().load(url).into(view)
    }
    
    <ImageView App:imageUrl="@{venue.imageUrl}" />

在類中定義一個(gè)外部可以訪問(wèn)的方法loadImage,注釋@BindingAdapter里面的屬性為你需要定義的屬性名稱,這里設(shè)置的是imageUrl。所以在布局中就可以使用app:imageUrl,并傳值為String類型,系統(tǒng)就會(huì)找到這個(gè)適配器方法并執(zhí)行。

5)將布局視圖綁定到架構(gòu)組件這一塊就是實(shí)際應(yīng)用了,和jetpack其他組件相結(jié)合使用,形成完整的MVVM分層架構(gòu)。

        // Obtain the ViewModel component.
        val userModel: UserViewModel by viewModels()

        // Inflate view and obtain an instance of the binding class.
        val binding: ActivityDatabindingMvvmBinding =
            DataBindingUtil.setContentView(this, R.layout.activity_databinding_mvvm)

        // Assign the component to a property in the binding class.
        binding.viewmodel = userModel
        
    <data>
        <variable
            name="viewmodel"
            type="com.panda.jetpackdemo.dataBinding.UserViewModel" />
    </data>
    
    class UserViewModel : ViewModel() {
    val currentName: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

    init {
        currentName.value="zzz"
    }
}

6)雙向數(shù)據(jù)綁定

剛才我們介紹的都是單向綁定,也就是布局中view綁定了數(shù)據(jù)對(duì)象,那么如何讓數(shù)據(jù)對(duì)象也對(duì)view產(chǎn)生綁定呢?也就是view改變的時(shí)候數(shù)據(jù)對(duì)象也能接收到訊息,形成雙向綁定。

很簡(jiǎn)單,比如一個(gè)EditText,需求是EditText改變的時(shí)候,user對(duì)象name數(shù)據(jù)也會(huì)跟著改變,只需要把之前的"@{}"改成"@={}"

    //布局 activity_main.xml
    <?xml version="1.0" encoding="utf-8"?>
    <layout xmlns:android="http://schemas.android.com/apk/res/android">
       <data>
           <variable name="user" type="com.example.User"/>
       </data>
       <EditText android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:text="@={user.name}"/>
    </layout>

很簡(jiǎn)單吧,同樣,這個(gè)雙向綁定功能也是支持自定義的。來(lái)個(gè)

object SwipeRefreshLayoutBinding {

    //方法1,數(shù)據(jù)綁定到view
    @JvmStatic
    @BindingAdapter("app:bind_refreshing")
    fun setSwipeRefreshLayoutRefreshing(swipeRefreshLayout: SwipeRefreshLayout,newValue: Boolean) {
        if (swipeRefreshLayout.isRefreshing != newValue)
            swipeRefreshLayout.isRefreshing = newValue
    }

    //方法1,view改變會(huì)通知bind_refreshingChanged,并且從該方法獲取view的數(shù)據(jù)
    @JvmStatic
    @InverseBindingAdapter(attribute = "app:bind_refreshing",event = "app:bind_refreshingChanged")
    fun isSwipeRefreshLayoutRefreshing(swipeRefreshLayout: SwipeRefreshLayout): Boolean =swipeRefreshLayout.isRefreshing
            
    //方法3,view如何改變來(lái)影響數(shù)據(jù)內(nèi)容  
    @JvmStatic
    @BindingAdapter("app:bind_refreshingChanged",requireAll = false)
    fun setOnRefreshListener(swipeRefreshLayout: SwipeRefreshLayout,bindingListener: InverseBindingListener?) {
        if (bindingListener != null)
            swipeRefreshLayout.setOnRefreshListener {
                bindingListener.onChange()
            }
    }
}


<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:bind_refreshing="@={viewModel.refreshing }">
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

簡(jiǎn)單說(shuō)明下,首先通過(guò)bind_refreshing屬性,將數(shù)據(jù)viewModel.refreshing綁定到view上,這樣數(shù)據(jù)變化,view也會(huì)跟著變化。然后view變化的時(shí)候,通過(guò)InverseBindingAdapter注釋,會(huì)調(diào)用bind_refreshingChanged事件,而bind_refreshingChanged事件告訴了我們view什么時(shí)候會(huì)進(jìn)行數(shù)據(jù)的修改,在這個(gè)案例中也就是swipeRefreshLayout下滑的時(shí)候會(huì)導(dǎo)致數(shù)據(jù)進(jìn)行改變,于是數(shù)據(jù)對(duì)象會(huì)從isSwipeRefreshLayoutRefreshing方法獲取到最新的數(shù)值,也就是從view更新過(guò)來(lái)的數(shù)據(jù)。

這里要注意的一個(gè)點(diǎn)是,雙向綁定要考慮到死循環(huán)問(wèn)題,當(dāng)View被改變,數(shù)據(jù)對(duì)象對(duì)應(yīng)發(fā)生更新,同時(shí),這個(gè)更新又回通知View層去刷新UI,然后view被改變又會(huì)導(dǎo)致數(shù)據(jù)對(duì)象更新,無(wú)限循環(huán)下去了。所以防止死循環(huán)的做法就是判斷view的數(shù)據(jù)狀態(tài),當(dāng)發(fā)生改變的時(shí)候才去更新view。

Lifecycles

生命周期感知型組件可執(zhí)行操作來(lái)響應(yīng)另一個(gè)組件(如 Activity 和 Fragment)的生命周期狀態(tài)的變化。這些組件有助于您寫出更有條理且往往更精簡(jiǎn)的代碼,這樣的代碼更易于維護(hù)。

Lifecycles,稱為生命周期感知型組件,可以感知和響應(yīng)另一個(gè)組件(如 Activity 和 Fragment)的生命周期狀態(tài)的變化。

可能有人會(huì)疑惑了,生命周期就那幾個(gè),我為啥還要導(dǎo)入一個(gè)庫(kù)呢?有了庫(kù)難道就不用寫生命周期了嗎,有什么好處呢? 舉個(gè),讓你感受下。

首先導(dǎo)入庫(kù),可以根據(jù)實(shí)際項(xiàng)目情況導(dǎo)入

        // ViewModel
        implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
        // LiveData
        implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
        // Lifecycles only (without ViewModel or LiveData)
        implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
        //.......

現(xiàn)在有一個(gè)定位監(jiān)聽(tīng)器,需要在Activity啟動(dòng)的時(shí)候開(kāi)啟,銷毀的時(shí)候關(guān)閉。正常代碼如下:

class BindingActivity : AppCompatActivity() {

    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        myLocationListener = MyLocationListener(this) { location ->
            // update UI
        }
    }
    public override fun onStart() {
        super.onStart()
        myLocationListener.start()       
    }
    public override fun onStop() {
        super.onStop()
        myLocationListener.stop()
    }

    internal class MyLocationListener(
            private val context: Context,
            private val callback: (Location) -> Unit
    ) {
        fun start() {
            // connect to system location service
        }
        fun stop() {
            // disconnect from system location service
        }
    }
    
}

乍一看也沒(méi)什么問(wèn)題是吧,但是如果需要管理生命周期的類一多,是不是就不好管理了。所有的類都要在Activity里面管理,還容易漏掉。 所以解決辦法就是實(shí)現(xiàn)解耦,讓需要管理生命周期的類自己管理,這樣Activity也不會(huì)遺漏和臃腫了。上代碼:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        myLocationListener = MyLocationListener(this) { location ->
            // update UI
        }
       lifecycle.addObserver(myLocationListener)
    }



    internal class MyLocationListener (
            private val context: Context,
            private val callback: (Location) -> Unit
    ): LifecycleObserver {

        @OnLifecycleEvent(Lifecycle.Event.ON_START)
        fun start() {

        }

        @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
        fun stop() {
            // disconnect if connected
        }
    }

很簡(jiǎn)單吧,只要實(shí)現(xiàn)LifecycleObserver接口,就可以用注釋的方式執(zhí)行每個(gè)生命周期要執(zhí)行的方法。然后在Activity里面addObserver綁定即可。

同樣的,Lifecycle也支持自定義生命周期,只要繼承LifecycleOwner即可,然后通過(guò)markState方法設(shè)定自己類的生命周期,舉個(gè)

class BindingActivity : AppCompatActivity(), LifecycleOwner {

    private lateinit var lifecycleRegistry: LifecycleRegistry

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleRegistry = LifecycleRegistry(this)
        lifecycleRegistry.markState(Lifecycle.State.CREATED)
    }

    public override fun onStart() {
        super.onStart()
        lifecycleRegistry.markState(Lifecycle.State.STARTED)
    }
}    

LiveData

LiveData 是一種可觀察的數(shù)據(jù)存儲(chǔ)器類。與常規(guī)的可觀察類不同,LiveData 具有生命周期感知能力,意指它遵循其他應(yīng)用組件(如 Activity、Fragment 或 Service)的生命周期。這種感知能力可確保 LiveData 僅更新處于活躍生命周期狀態(tài)的應(yīng)用組件觀察者。

LiveData 是一種可觀察的數(shù)據(jù)存儲(chǔ)器類。 等等,這個(gè)介紹好像似曾相識(shí)?對(duì),前面說(shuō)數(shù)據(jù)綁定的時(shí)候就有一個(gè)可觀察的數(shù)據(jù)對(duì)象ObservableField。那兩者有什么區(qū)別呢?

1)LiveData 具有生命周期感知能力,可以感知到Activity等的生命周期。這樣有什么好處呢?很常見(jiàn)的一點(diǎn)就是可以減少內(nèi)存泄漏和崩潰情況了呀,想想以前你的項(xiàng)目中針對(duì)網(wǎng)絡(luò)接口返回?cái)?shù)據(jù)的時(shí)候都要判斷當(dāng)前界面是否銷毀,現(xiàn)在LiveData就幫你解決了這個(gè)問(wèn)題。

具體為什么能解決崩潰和泄漏問(wèn)題呢?

  • 不會(huì)發(fā)生內(nèi)存泄漏 觀察者會(huì)綁定到 Lifecycle 對(duì)象,并在其關(guān)聯(lián)的生命周期遭到銷毀后進(jìn)行自我清理。
  • 不會(huì)因 Activity 停止而導(dǎo)致崩潰 如果觀察者的生命周期處于非活躍狀態(tài)(如返回棧中的 Activity),則它不會(huì)接收任何 LiveData 事件。
  • 自動(dòng)判斷生命周期并回調(diào)方法 如果觀察者的生命周期處于 STARTED 或 RESUMED狀態(tài),則 LiveData 會(huì)認(rèn)為該觀察者處于活躍狀態(tài),就會(huì)調(diào)用onActive方法,否則,如果 LiveData 對(duì)象沒(méi)有任何活躍觀察者時(shí),會(huì)調(diào)用 onInactive()方法。

2) LiveData更新數(shù)據(jù)更靈活,不一定是改變數(shù)據(jù),而是調(diào)用方法(postValue或者setValue)的方式進(jìn)行UI更新或者其他操作。

好了。還是舉個(gè)更直觀的看看吧:

    //導(dǎo)入庫(kù):
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"

    class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
        private val stockManager = StockManager(symbol)

        private val listener = { price: BigDecimal ->
            value = price
        }

        override fun onActive() {
            stockManager.requestPriceUpdates(listener)
        }

        override fun onInactive() {
            stockManager.removeUpdates(listener)
        }
    }
    
    public class MyFragment : Fragment() {
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            val myPriceListener: LiveData<BigDecimal> = StockLiveData("")
            myPriceListener.observe(this, Observer<BigDecimal> { price: BigDecimal? ->
                // 監(jiān)聽(tīng)livedata的數(shù)據(jù)變化,如果調(diào)用了setValue或者postValue會(huì)調(diào)用該onChanged方法
                //更新UI數(shù)據(jù)或者其他處理
            })
        }
    }
        

這是一個(gè)股票數(shù)據(jù)對(duì)象,StockManager為股票管理器,如果該對(duì)象有活躍觀察者時(shí),就去監(jiān)聽(tīng)股票市場(chǎng)的情況,如果沒(méi)有活躍觀察者時(shí),就可以斷開(kāi)監(jiān)聽(tīng)。 當(dāng)監(jiān)聽(tīng)到股票信息變化,該股票數(shù)據(jù)對(duì)象就會(huì)通過(guò)setValue方法進(jìn)行數(shù)據(jù)更新,反應(yīng)到觀察者的onChanged方法。這里要注意的是setValue方法只能在主線程調(diào)用,而postValue則是在其他線程調(diào)用。 當(dāng)Fragment這個(gè)觀察者生命周期發(fā)生變化時(shí),LiveData就會(huì)移除這個(gè)觀察者,不再發(fā)送消息,所以也就避免崩潰問(wèn)題。

Navigation

導(dǎo)航 Navigation 組件旨在用于具有一個(gè)主 Activity 和多個(gè) Fragment 目的地的應(yīng)用。主 Activity 與導(dǎo)航圖相關(guān)聯(lián),且包含一個(gè)負(fù)責(zé)根據(jù)需要交換目的地的 NavHostFragment。在具有多個(gè) Activity 目的地的應(yīng)用中,每個(gè) Activity 均擁有其自己的導(dǎo)航圖。

所以說(shuō)白了,Navigation就是一個(gè)Fragment的管理框架。 怎么實(shí)現(xiàn)?創(chuàng)建Activity,F(xiàn)ragment,進(jìn)行連接。

1)導(dǎo)入庫(kù)

  def nav_version = "2.3.0"
  implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
  implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

2)創(chuàng)建3個(gè)Fragment和一個(gè)Activity

3)創(chuàng)建res/navigation/my_nav.xml 文件

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    app:startDestination="@id/myFragment1"
    tools:ignore="UnusedNavigation">

    <fragment
        android:id="@+id/myFragment1"
        android:name="com.example.studynote.blog.jetpack.navigation.MyFragment1"
        android:label="fragment_blank"
        tools:layout="@layout/fragmetn_my_1" >
        <action
            android:id="@+id/action_blankFragment_to_blankFragment2"
            app:destination="@id/myFragment2" />
    </fragment>

    <fragment
        android:id="@+id/myFragment2"
        android:name="com.example.studynote.blog.jetpack.navigation.MyFragment1"
        android:label="fragment_blank"
        tools:layout="@layout/fragmetn_my_1" >
        <action
            android:id="@+id/action_blankFragment_to_blankFragment2"
            app:destination="@id/myFragment3" />
    </fragment>

    <fragment
        android:id="@+id/myFragment3"
        android:name="com.example.studynote.blog.jetpack.navigation.MyFragment1"
        android:label="fragment_blank"
        tools:layout="@layout/fragmetn_my_1" >
    </fragment>
</navigation>

在res文件夾下新建navigation目錄,并新建my_nav.xml 文件。配置好每個(gè)Fragment,其中:

  • app:startDestination 屬性代表一開(kāi)始顯示的fragment
  • android:name 屬性代表對(duì)應(yīng)的Fragment路徑
  • action 代表該Fragment存在的跳轉(zhuǎn)事件,比如myFragment1可以跳轉(zhuǎn)myFragment2。
  1. 修改Activity的布局文件:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">

<fragment
    android:id="@+id/nav_host_fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="0dp"
    android:layout_height="0dp"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:defaultNavHost="true"
    app:navGraph="@navigation/my_nav" />

</androidx.constraintlayout.widget.ConstraintLayout>

可以看到,Activity的布局文件就是一個(gè)fragment控件,name為NavHostFragment,navGraph為剛才新建的mynavigation文件。

5)配置完了之后,就可以設(shè)置具體的跳轉(zhuǎn)邏輯了。

    override fun onClick(v: View) {
    //不帶參數(shù)
 v.findNavController().navigate(R.id.action_blankFragment_to_blankFragment2)
   //帶參數(shù)
    var bundle = bundleOf("amount" to amount)
    v.findNavController().navigate(R.id.confirmationAction, bundle)
 
    }
    
    //接收數(shù)據(jù)
    tv.text = arguments?.getString("amount")
    

需要注意的是,跳轉(zhuǎn)這塊官方建議用Safe Args 的Gradle 插件,該插件可以生成簡(jiǎn)單的 object 和 builder類,以便以類型安全的方式瀏覽和訪問(wèn)任何關(guān)聯(lián)的參數(shù)。這里就不細(xì)說(shuō)了,感興趣的可以去官網(wǎng)看看

Room

Room 持久性庫(kù)在 SQLite 的基礎(chǔ)上提供了一個(gè)抽象層,讓用戶能夠在充分利用 SQLite 的強(qiáng)大功能的同時(shí),獲享更強(qiáng)健的數(shù)據(jù)庫(kù)訪問(wèn)機(jī)制。

所以Room就是一個(gè)數(shù)據(jù)庫(kù)框架。問(wèn)題來(lái)了,市面上那么多數(shù)據(jù)庫(kù)組件,比如ormLite,greendao等等,為什么google還要出一個(gè)room,有什么優(yōu)勢(shì)呢?

  • 性能優(yōu)勢(shì),一次數(shù)據(jù)庫(kù)操作主要包括:構(gòu)造sql語(yǔ)句—編譯語(yǔ)句—傳入?yún)?shù)—執(zhí)行操作。ORMLite主要在獲取參數(shù)屬性值的時(shí)候,是通過(guò)反射獲取的,所以速度較慢。GreenDao在構(gòu)造sql語(yǔ)句的時(shí)候是通過(guò)代碼拼接,所以較慢。Room是通過(guò)接口方法的注解生成sql語(yǔ)句,也就是編譯成字節(jié)碼的時(shí)候就生成了sql語(yǔ)句,所以運(yùn)行起來(lái)較快。
  • 支持jetpack其他組件(比如LiveData,Paging)以及RxJAVA,這就好比借助了當(dāng)前所在的優(yōu)勢(shì)環(huán)境,就能給你帶來(lái)一些得天獨(dú)厚的優(yōu)勢(shì)。當(dāng)然實(shí)際使用起來(lái)也確實(shí)要方便很多,比如liveData結(jié)合,就能在數(shù)據(jù)查詢后進(jìn)行自動(dòng)UI更新。

既然Room這么優(yōu)秀,那就用起來(lái)吧。 Room的接入主要有三大點(diǎn):DataBase、Entity、Dao。分別對(duì)應(yīng)數(shù)據(jù)庫(kù),表和數(shù)據(jù)訪問(wèn)。

1)首先導(dǎo)入庫(kù):

    apply plugin: 'kotlin-kapt'

    dependencies {
      def room_version = "2.2.5"

      implementation "androidx.room:room-runtime:$room_version"
      kapt "androidx.room:room-compiler:$room_version" // For Kotlin use kapt instead of annotationProcessor

      // optional - Kotlin Extensions and Coroutines support for Room
      implementation "androidx.room:room-ktx:$room_version"

      // optional - RxJava support for Room
      implementation "androidx.room:room-rxjava2:$room_version"
    }
    

2)建立數(shù)據(jù)庫(kù)類,聲明數(shù)據(jù)庫(kù)表成員,數(shù)據(jù)庫(kù)名稱,數(shù)據(jù)庫(kù)版本,單例等等

@Database(entities = arrayOf(User::class), version = 1)
abstract class UserDb : RoomDatabase() {

    abstract fun userDao(): UserDao

    companion object {
        private var instance: UserDb? = null

        @Synchronized
        fun get(context: Context): UserDb {
            if (instance == null) {
                instance = Room.databaseBuilder(context.applicationContext,
                    UserDb::class.java, "StudentDatabase").build()
            }
            return instance!!
        }
    }
}

3)建表,可以設(shè)置主鍵,外鍵,索引,自增等等

@Entity
data class User(@PrimaryKey(autoGenerate = true) val id: Int,
                val name: String)

4)Dao,數(shù)據(jù)操作

@Dao
interface UserDao {

    @Query("SELECT * FROM User")
    fun getAllUser(): DataSource.Factory<Int, User>

    @Query("SELECT * FROM User")
    fun getAllUser2(): LiveData<List<User>>

    @Query("SELECT * from user")
    fun getAllUser3(): Flowable<List<User>>

    @Insert
    fun insert(users: List<User>)
}

然后就可以進(jìn)行數(shù)據(jù)庫(kù)操作了,很簡(jiǎn)單吧。

Paging

分頁(yè)庫(kù)可幫助您一次加載和顯示一小塊數(shù)據(jù)。按需載入部分?jǐn)?shù)據(jù)會(huì)減少網(wǎng)絡(luò)帶寬和系統(tǒng)資源的使用量。

所以Paging就是一個(gè)分頁(yè)庫(kù),主要用于Recycleview列表展示。下面我就結(jié)合Room說(shuō)說(shuō)Paging的用法。 使用Paging主要注意兩個(gè)類:PagedList和PagedListAdapter。1)PagedList用于加載應(yīng)用數(shù)據(jù)塊,綁定數(shù)據(jù)列表,設(shè)置數(shù)據(jù)頁(yè)等。結(jié)合上述Room的Demo我繼續(xù)寫了一個(gè)UserModel進(jìn)行數(shù)據(jù)管理:

class UserModel(app: Application) : AndroidViewModel(app) {
    val dao = UserDb.get(app).userDao()
    var idNum = 1

    companion object {
        private const val PAGE_SIZE = 10
    }

    //初始化PagedList
    val users = LivePagedListBuilder(
        dao.getAllUser(), PagedList.Config.Builder()
            .setPageSize(PAGE_SIZE)
            .setEnablePlaceholders(true)
            .build()
    ).build()

    //插入用戶
    fun insert() = ioThread {
        dao.insert(newTenUser())
    }

    //獲取新的10個(gè)用戶
    fun newTenUser(): ArrayList<User> {
        var newUsers = ArrayList<User>()
        for (index in 1..10) {
            newUsers.add(User(0, "bob${++idNum}"))
        }
        return newUsers
    }

}

2)PagedListAdapter使用Recycleview必要要用到adatper,所以這里需要綁定一個(gè)繼承自PagedListAdapter的adapter:

class UserAdapter : PagedListAdapter<User, UserAdapter.UserViewHolder>(diffCallback) {
    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        holder.bindTo(getItem(position))
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder =
        UserViewHolder(parent)

    companion object {

        private val diffCallback = object : DiffUtil.ItemCallback<User>() {
            override fun areItemsTheSame(oldItem: User, newItem: User): Boolean =
                oldItem.id == newItem.id

            override fun areContentsTheSame(oldItem: User, newItem: User): Boolean =
                oldItem == newItem
        }
    }

    class UserViewHolder(parent: ViewGroup) : RecyclerView.ViewHolder(
        LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)) {

        private val tv1 = itemView.findViewById<TextView>(R.id.name)
        var user: User? = null

        fun bindTo(user: User?) {
            this.user = user
            tv1.text = user?.name
        }
    }
}

這里還用到了DiffUtil.ItemCallback 類,用于比較數(shù)據(jù),進(jìn)行數(shù)據(jù)更新用。

ok,數(shù)據(jù)源,adapter都設(shè)置好了,接下來(lái)就是監(jiān)聽(tīng)數(shù)據(jù),刷新數(shù)據(jù)就可以了

        // 監(jiān)聽(tīng)users數(shù)據(jù),數(shù)據(jù)改變調(diào)用submitList方法
        viewModel.users.observe(this, Observer(adapter::submitList))

對(duì),就是這么一句,監(jiān)聽(tīng)PagedList,并且在它改變的時(shí)候調(diào)用PagedListAdapter的submitList方法。 這分層夠爽吧,其實(shí)這也就是paging或者說(shuō)jetpack給我們項(xiàng)目帶來(lái)的優(yōu)勢(shì),層層解耦,adapter都不用維護(hù)list數(shù)據(jù)源了。

ViewModel

ViewModel 類旨在以注重生命周期的方式存儲(chǔ)和管理界面相關(guān)的數(shù)據(jù)。ViewModel 類讓數(shù)據(jù)可在發(fā)生屏幕旋轉(zhuǎn)等配置更改后繼續(xù)留存。

終于說(shuō)到ViewModel了,其實(shí)之前的demo都用了好多遍了,ViewModel主要是從界面控制器邏輯中分離出視圖數(shù)據(jù),為什么要這么做呢?主要為了解決兩大問(wèn)題:

  • 以前Activity中如果被系統(tǒng)銷毀或者需要重新創(chuàng)建的時(shí)候,頁(yè)面臨時(shí)性數(shù)據(jù)都會(huì)丟失,需要通過(guò)onSaveInstanceState() 方法保存,onCreate方法中讀取。而且數(shù)據(jù)量一大就更加不方便了。
  • 在Activity中,難免有些異步調(diào)用,所以就會(huì)容易導(dǎo)致界面銷毀時(shí)候,這些調(diào)用還存在。那就會(huì)發(fā)生內(nèi)存泄漏或者直接崩潰。

所以ViewModel誕生了,還是解耦,我把數(shù)據(jù)單獨(dú)拿出來(lái)管理,還加上生命周期,那不就可以解決這些問(wèn)題了嗎。而且當(dāng)所有者 Activity 完全銷毀之后,ViewModel會(huì)調(diào)用其onCleared()方法,以便清理資源。

接下來(lái)舉個(gè),看看ViewModel具體是怎么使用的:

def lifecycle_version = "2.2.0"
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"


class SharedViewModel : ViewModel() {
    var userData = MutableLiveData<User>()

    fun select(item: User) {
        userData.value = item
    }

    override fun onCleared() {
        super.onCleared()
    }
}

class MyFragment1 : Fragment() {
    private lateinit var btn: Button

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val model=activity?.let { ViewModelProvider(it).get(SharedViewModel::class.java) }
        btn.setOnClickListener{
            model?.select(User(0,"bob"))
        }
    }
}

class MyFragment2 : Fragment() {
    private lateinit var btn: Button

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        val model=activity?.let { ViewModelProvider(it).get(SharedViewModel::class.java) }
        model?.userData?.observe(viewLifecycleOwner, Observer<User> { item ->
            // Update the UI
        })
    }
}
    

Fragment中,獲取到viewmodel的實(shí)例,然后進(jìn)行數(shù)據(jù)監(jiān)聽(tīng)等操作。等等,你能發(fā)現(xiàn)什么不? 對(duì)了,數(shù)據(jù)通信。不同的 Fragment 可以使用其父Activity共享ViewModel 來(lái)進(jìn)行數(shù)據(jù)的通信,厲害吧。還有很多其他的用法,去項(xiàng)目中慢慢發(fā)現(xiàn)吧!

WorkManager

使用 WorkManager API 可以輕松地調(diào)度即使在應(yīng)用退出或設(shè)備重啟時(shí)仍應(yīng)運(yùn)行的可延遲異步任務(wù)。

聽(tīng)聽(tīng)這個(gè)介紹就很神奇了,應(yīng)用退出和設(shè)備重啟都能自動(dòng)運(yùn)行?通過(guò)廣播?那數(shù)據(jù)又是怎么保存的呢?聽(tīng)說(shuō)還可以執(zhí)行周期性異步任務(wù),順序鏈?zhǔn)秸{(diào)用哦!接下來(lái)一一解密

一般這個(gè)API應(yīng)用到什么場(chǎng)景呢?想想,可靠運(yùn)行,還可以周期異步。 對(duì)了,發(fā)送日志。可以通過(guò)WorkManager設(shè)定周期任務(wù),每天執(zhí)行一次發(fā)送日志的任務(wù)。而且能夠保證你的任務(wù)可靠運(yùn)行,一定可以上傳到,當(dāng)然也是支持監(jiān)聽(tīng)任務(wù)結(jié)果等。:

1)導(dǎo)入庫(kù)

    dependencies {
      def work_version = "2.3.4"
        // Kotlin + coroutines
        implementation "androidx.work:work-runtime-ktx:$work_version"

        // optional - RxJava2 support
        implementation "androidx.work:work-rxjava2:$work_version"

        // optional - GCMNetworkManager support
        implementation "androidx.work:work-gcm:$work_version"
      }
    

2) 新建任務(wù)類,繼承Worker,重寫doWork方法,返回任務(wù)結(jié)果。

class UploadLogcatWork(appContext: Context, workerParams: WorkerParameters) :
    Worker(appContext, workerParams) {

    override fun doWork(): Result {

        if (isUploadLogcatSuc()) {
            return Result.success()
        } else if (isNeedRetry()){
            return Result.retry()
        }

        return Result.failure()
    }

    fun isUploadLogcatSuc(): Boolean {
        var isSuc: Boolean = false
        return isSuc
    }

    fun isNeedRetry(): Boolean {
        var isSuc: Boolean = false
        return isSuc
    }
}

3)最后就是設(shè)定約束(是否需要網(wǎng)絡(luò),是否支持低電量,是否支持充電執(zhí)行,延遲等等),執(zhí)行任務(wù)(單次任務(wù)或者循環(huán)周期任務(wù))

        //設(shè)定約束
        val constraints =
            Constraints.Builder()
                //網(wǎng)絡(luò)鏈接的時(shí)候使用
                .setRequiredNetworkType(NetworkType.CONNECTED)
                //是否在設(shè)備空閑的時(shí)候執(zhí)行
                .setRequiresDeviceIdle(false)
                //是否在低電量的時(shí)候執(zhí)行
                .setRequiresBatteryNotLow(true)
                //是否在內(nèi)存不足的時(shí)候執(zhí)行
                .setRequiresStorageNotLow(true)
                //是否時(shí)充電的時(shí)候執(zhí)行
                .setRequiresCharging(true)
                //延遲執(zhí)行
                .setTriggerContentMaxDelay(1000 * 1, TimeUnit.MILLISECONDS)
                .build()

        //設(shè)定循環(huán)任務(wù)
        val uploadRequest =
            PeriodicWorkRequestBuilder<UploadLogcatWork>(1, TimeUnit.HOURS)
                .setConstraints(constraints)
                .addTag("uploadTag")
                .build()

        //執(zhí)行
        WorkManager.getInstance(applicationContext).enqueue(uploadRequest)


        //監(jiān)聽(tīng)執(zhí)行結(jié)果
        WorkManager.getInstance(this)
//            .getWorkInfosByTagLiveData("uploadTag") //通過(guò)tag拿到work
            .getWorkInfoByIdLiveData(uploadRequest.id) //通過(guò)id拿到work
            .observe(this, Observer {
                it?.apply {
                    when (this.state) {
                        WorkInfo.State.BLOCKED -> println("BLOCKED")
                        WorkInfo.State.CANCELLED -> println("CANCELLED")
                        WorkInfo.State.RUNNING -> println("RUNNING")
                        WorkInfo.State.ENQUEUED -> println("ENQUEUED")
                        WorkInfo.State.FAILED -> println("FAILED")
                        WorkInfo.State.SUCCEEDED -> println("SUCCEEDED")
                        else -> println("else status ${this.state}")
                    }
                }

            })

4)另外還支持任務(wù)取消,任務(wù)鏈?zhǔn)巾樞蛘{(diào)用等

    //取消
    fun cancelWork(){
  WorkManager.getInstance(applicationContext).cancelAllWorkByTag("uploadTag")
    }

    fun startLineWork(){
        //圖片濾鏡1
        val filter1 = OneTimeWorkRequestBuilder<UploadLogcatWork>()
            .build()
        //圖片濾鏡2
        val filter2 = OneTimeWorkRequestBuilder<UploadLogcatWork>()
            .build()
        //圖片壓縮
        val compress = OneTimeWorkRequestBuilder<UploadLogcatWork>()
            .build()
        //圖片上傳
        val upload = OneTimeWorkRequestBuilder<UploadLogcatWork>()
            .build()

        WorkManager.getInstance(applicationContext)
            .beginWith(listOf(filter1, filter2))
            .then(compress)
            .then(upload)
            .enqueue()
    }

總結(jié)

Jetpack-架構(gòu)組件講完了,大家吃應(yīng)該吃飽了吧哈哈。希望這篇文章能讓不怎么熟悉Jetpack的同學(xué)熟悉熟悉。

當(dāng)然,這還遠(yuǎn)遠(yuǎn)不夠,在我看來(lái),本文更像是一個(gè)科普文,只是告訴了大家Jetpack-架構(gòu)組件有哪些成員,有什么用處。實(shí)際項(xiàng)目中,我們還需要建立MVVM的思想,深刻了解每個(gè)組件的設(shè)計(jì)意義,靈活運(yùn)用組件。最后希望大家都能通過(guò)jetpack構(gòu)建高質(zhì)量,簡(jiǎn)易并優(yōu)質(zhì)的項(xiàng)目架構(gòu),從而解放生產(chǎn)力,成為效率達(dá)人。

當(dāng)然了,本文所列出的知識(shí)點(diǎn)還不完全,要比較系統(tǒng)的學(xué)習(xí),我這里可以分享我一份Jetpack的資料。

更重要的是,還有大佬收錄整理的Android學(xué)習(xí)PDF+架構(gòu)視頻+面試文檔+源碼筆記,高級(jí)架構(gòu)技術(shù)進(jìn)階腦圖、Android開(kāi)發(fā)面試專題資料,高級(jí)進(jìn)階架構(gòu)資料

這些都是我現(xiàn)在閑暇還會(huì)反復(fù)翻閱的精品資料。里面對(duì)近幾年的大廠面試高頻知識(shí)點(diǎn)都有詳細(xì)的講解。相信可以有效的幫助大家掌握知識(shí)、理解原理。

當(dāng)然你也可以拿去查漏補(bǔ)缺,提升自身的競(jìng)爭(zhēng)力。

如果你有需要,可以私信或者評(píng)論獲取

喜歡本文的話,不妨順手給我點(diǎn)個(gè)贊、評(píng)論區(qū)留言或者轉(zhuǎn)發(fā)支持一下唄~

是時(shí)候更新手里的武器了—Jetpack架構(gòu)組件簡(jiǎn)析

 


是時(shí)候更新手里的武器了—Jetpack架構(gòu)組件簡(jiǎn)析

 


是時(shí)候更新手里的武器了—Jetpack架構(gòu)組件簡(jiǎn)析

 

分享到:
標(biāo)簽:架構(gòu)
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定