Android開(kāi)發(fā)框架MVC-MVP-MVVM-MVI的演變Demo
Android框架的歷史演變
記得最開(kāi)始入門(mén)Android的時(shí)候,還未流行MVP,都是MVC一把梭,后面工作了就是使用了MVP,當(dāng)時(shí)學(xué)習(xí)的時(shí)候好難理解它的回調(diào)。
到目前主流的MVVM,其實(shí)就是MVP的升級(jí)版,再到最新的MVI使用意圖傳輸,隔離各層級(jí)的直接調(diào)用。我算是經(jīng)歷了Android框架變遷的全過(guò)程。
這里記錄一下各框架的簡(jiǎn)單Demo用例。
一. MVC框架
經(jīng)典MVC分為:
Model 模型層 : 數(shù)據(jù)和網(wǎng)絡(luò)
View 視圖層 : 視圖的展示
Controller 控制層 : 邏輯控制,調(diào)用模型驅(qū)動(dòng)視圖
一般我們是把一個(gè)xml看作一個(gè)View層, Activity看作一個(gè)Control層 , Model層則是由相關(guān)的數(shù)據(jù)操作類(lèi)。
Model層:
class OtherModel : BaseRepository() { /** * 使用擴(kuò)展方法,請(qǐng)求網(wǎng)絡(luò) */ suspend inline fun getIndustry(): OkResult<List<Industry>> { return extRequestHttp { DemoRetrofit.apiService.getIndustry( Constants.NETWORK_CONTENT_TYPE, Constants.NETWORK_ACCEPT_V1 ) } } }
Controller層:
class MVCActivity : AbsActivity() { private val mOtherModel: OtherModel by lazy { OtherModel() } override fun setContentView() { setContentView(R.layout.activity_demo14_1) } override fun init() { val btnGetData = findViewById<Button>(R.id.btn_get_data) btnGetData.click { requestIndustry() } } private fun requestIndustry() { //MVC中Activity就是Controller,直接調(diào)用接口,獲取數(shù)據(jù)之后直接操作xml控件刷新 lifecycleScope.launch { //開(kāi)始Loading LoadingDialogManager.get().showLoading(this@MVCActivity) val result = mOtherModel.getIndustry() result.checkSuccess { //處理成功的信息 toast("list:$it") //doSth... } LoadingDialogManager.get().dismissLoading() } } }
XML就是View層,獲取到信息展示到XML中。
這樣分工其實(shí)也是很明確的,但是一旦邏輯過(guò)多,會(huì)導(dǎo)致Activity太臃腫。Activcity中又是Model又是View,耦合性太強(qiáng),記得那時(shí)候一個(gè)Activity中上千行代碼都是平平常常。
為了解決這個(gè)問(wèn)題,大家開(kāi)始使用MVP架構(gòu)。
二. MVP框架
MVP框架的出現(xiàn),各個(gè)模塊權(quán)責(zé)分明,各干各的活,降低了耦合,減少Activity的臃腫。
Model層:還是MVC那個(gè)Model。
View層:接口定義由Activity實(shí),用于操作相應(yīng)的UI。
Presenter層:用于Model和View的橋梁,負(fù)責(zé)Model與View的交互
那更復(fù)雜的一點(diǎn)的,就是其中加入Contract契約類(lèi),把指定頁(yè)面的Presenter和View等關(guān)聯(lián)起來(lái),方便維護(hù)。
View接口定義:
interface IDemoView { fun showLoading() fun hideLoading() fun getIndustrySuccess(list: List<Industry>?) fun getIndustryFailed(msg: String?) }
Presenter的實(shí)現(xiàn):
class DemoPresenter(private val view: IDemoView) { private val mOtherModel: OtherModel by lazy { OtherModel() } //獲取行業(yè)數(shù)據(jù) fun requestIndustry(lifecycleScope: LifecycleCoroutineScope) { lifecycleScope.launch { //開(kāi)始Loading view.showLoading() val result = mOtherModel.getIndustry() result.checkResult({ //處理成功的信息 toast("list:$it") view.getIndustrySuccess(it) }, { //失敗 view.getIndustryFailed(it) }) view.hideLoading() } } }
Activity的實(shí)現(xiàn):
class MVPActivity : AbsActivity(), IDemoView { private lateinit var mPresenter: DemoPresenter override fun setContentView() { setContentView(R.layout.activity_demo14_1) } override fun init() { //創(chuàng)建Presenter mPresenter = DemoPresenter(this) val btnGetData = findViewById<Button>(R.id.btn_get_data) btnGetData.click { //通過(guò)Presenter調(diào)用接口 mPresenter.requestIndustry(lifecycleScope) } } //回調(diào)再次觸發(fā) override fun showLoading() { LoadingDialogManager.get().showLoading(this) } override fun hideLoading() { LoadingDialogManager.get().dismissLoading() } override fun getIndustrySuccess(list: List<Industry>?) { //popupIndustryData } override fun getIndustryFailed(msg: String?) { //showErrorMessage } }
當(dāng)時(shí)MVP框架是火遍一時(shí),當(dāng)時(shí)面試要不會(huì)這個(gè),那都不好意思說(shuō)是做安卓的。
雖然它有一些缺點(diǎn),比如太復(fù)雜,每次都要寫(xiě)重復(fù)的View,修改麻煩,回調(diào)地獄,數(shù)據(jù)交互體驗(yàn)不佳,無(wú)法感知生命周期,重建頁(yè)面無(wú)法自動(dòng)恢復(fù)數(shù)據(jù),耦合還是有很多,等等。但是在當(dāng)時(shí)沒(méi)有替代品的選擇下,它是當(dāng)之無(wú)愧的王。
但是當(dāng)谷歌出了Jetpack,當(dāng)ViewModel+LiveData+Lifecycles的出現(xiàn)給了我們新的選擇 MVVM框架開(kāi)始出現(xiàn)并迅猛發(fā)展。
三. MVVM框架
這里先說(shuō)一點(diǎn)有爭(zhēng)議的點(diǎn)。 有些人認(rèn)為,只要用上ViewModel+LiveData這些就算MVVM框架 Model+View+ViewModel嘛。 有些人認(rèn)為,MVVM的意思是數(shù)據(jù)驅(qū)動(dòng),最大的亮點(diǎn)是數(shù)據(jù)綁定,使用DataBinding的才算MVVM。 其實(shí)這個(gè)也沒(méi)有官方的定義,世上本無(wú)框架,用的人多了才出現(xiàn)框架名字,約定俗成的東西,你想怎么定義就怎么定義,那我姑且稱(chēng)為前者為半MVVM后者為MVVM吧
3.1 半MVVM框架
其實(shí)可以理解為MVP的升級(jí)版,去掉了View的接口回調(diào),保存了ViewModel的特性
Model層:還是MVC那個(gè)Model。
View層:Activity,用于操作相應(yīng)的UI。
ViewModel:還是MVP那個(gè)Presenter,只是用ViewModel實(shí)現(xiàn)。
ViewModel實(shí)現(xiàn): 可以看到代碼確實(shí)相比MVP少了很多
class DemoViewModel @ViewModelInject constructor( private val mRepository: Demo5Repository, @Assisted val savedState: SavedStateHandle ) : BaseViewModel() { val liveData = MutableLiveData<List<Industry>?>() //獲取行業(yè)數(shù)據(jù) fun requestIndustry() { viewModelScope.launch { //開(kāi)始Loading loadStartLoading() val result = mRepository.getIndustry() result.checkResult({ //處理成功的信息 toast("list:$it") liveData.value = it }, { //失敗 liveData.value = null }) loadHideProgress() } } }
Activity的實(shí)現(xiàn):
@AndroidEntryPoint class MVVMActivity : BaseVMActivity<DemoViewModel>() { override fun getLayoutIdRes(): Int = R.layout.activity_demo14_1 override fun init() { //自動(dòng)注入ViewModel,調(diào)用接口通過(guò)LiveData回調(diào) mViewModel.requestIndustry() } override fun startObserve() { //獲取到網(wǎng)絡(luò)數(shù)據(jù)之后改變xml對(duì)應(yīng)的值 mViewModel.liveData.observe(this) { it?.let { // popopIndustryData } } } }
3.2 帶DataBinding的MVVM框架
特別是現(xiàn)在kotlin那種直接拿id使用的插件已經(jīng)被官方標(biāo)記為過(guò)時(shí),還不趕緊用DataBinding或ViewBinding?
ViewModel實(shí)現(xiàn):
class DemoViewModel @ViewModelInject constructor( private val mRepository: Demo5Repository, @Assisted val savedState: SavedStateHandle ) : BaseViewModel() { val liveData = MutableLiveData<List<Industry>?>() //獲取行業(yè)數(shù)據(jù) fun requestIndustry() { viewModelScope.launch { //開(kāi)始Loading loadStartLoading() val result = mRepository.getIndustry() result.checkResult({ //處理成功的信息 toast("list:$it") liveData.value = it }, { //失敗 liveData.value = null }) loadHideProgress() } } }
Repository的實(shí)現(xiàn):其實(shí)和Model差不多的意思,數(shù)據(jù)倉(cāng)庫(kù)而已,下面的一些注解是用到了Hilt依賴(lài)注入,不用直接new對(duì)象也是可以的,不要在意一些細(xì)節(jié)。
@Singleton class Demo5Repository @Inject constructor() : BaseRepository() { suspend inline fun getIndustry(): OkResult<List<Industry>> { return extRequestHttp { DemoRetrofit.apiService.getIndustry( Constants.NETWORK_CONTENT_TYPE, Constants.NETWORK_ACCEPT_V1 ) } } }
Activity的實(shí)現(xiàn): 內(nèi)部做了一些基類(lèi)的封裝,事件處理封裝為對(duì)象,viewmodel和事件對(duì)象在xml中做了引用
@AndroidEntryPoint class MVVM2Activity : BaseVDBActivity<DemoViewModel, ActivityDemo142Binding>() { private val clickProxy: ClickProxy by lazy { ClickProxy() } override fun getDataBindingConfig(): DataBindingConfig { return DataBindingConfig(R.layout.activity_demo14_2, BR.viewModel, mViewModel) .addBindingParams(BR.click, clickProxy) } override fun init() { } override fun startObserve() { } /** * DataBinding事件處理 */ inner class ClickProxy { fun getData() { //MVVM直接調(diào)用網(wǎng)絡(luò)請(qǐng)求,結(jié)果在xml中自動(dòng)顯示 mViewModel.requestIndustry() } } }
Xml的實(shí)現(xiàn): 注意引用指向的包名要寫(xiě)對(duì),寫(xiě)對(duì)了可以直接跳轉(zhuǎn)過(guò)去的。
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:binding="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="viewModel" type="com.guadou.kt_demo.demo.demo14_mvi.mvvm1.DemoViewModel" /> <variable name="click" type="com.guadou.kt_demo.demo.demo14_mvi.mvvm2.MVVM2Activity.ClickProxy" /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/picture_color_blue" android:orientation="vertical"> <com.guadou.lib_baselib.view.titlebar.StatusbarGrayView android:id="@+id/status_view" android:layout_width="match_parent" android:layout_height="wrap_content" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="獲取數(shù)據(jù)" binding:clicks="@{click.getData}" /> <TextView android:id="@+id/tv_message" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{viewModel.liveData.toString()}" /> </LinearLayout> </layout>
這樣就完成了一個(gè)基于數(shù)據(jù)驅(qū)動(dòng)的DataBinding的MVVM。 如果上面一些代碼如果看不太明白,后面我可能會(huì)出DataBinding的封裝并開(kāi)源。
截止到發(fā)稿日期為止,目前市面上最流行的還是MVVM框架,此框架唯一的槽點(diǎn)可能就是Databinding的不好調(diào)試吧,一旦出問(wèn)題,有時(shí)候報(bào)錯(cuò)信息莫名其妙的,沒(méi)有指向XML中某個(gè)數(shù)據(jù)或語(yǔ)法的錯(cuò)誤,需要對(duì)DataBinding有一定的了解。 不過(guò)AS現(xiàn)在貌似越來(lái)越智能了,報(bào)錯(cuò)信息都還指向蠻清晰的。MVVM完全可用的。
四. MVI框架
由于是出來(lái)沒(méi)多久,具體是不是叫MVI框架這個(gè)名字還不確定,大家都這么叫,姑且就叫MVI吧。伴隨Compose出現(xiàn)的框架,主流用于Compose應(yīng)用。
MVI框架是由Model View Intent組成的??梢运闵螹VVM的升級(jí)版,在之前我們都是通過(guò)在Activity中直接調(diào)用ViewModel的方法,現(xiàn)在改為發(fā)出操作指令,由ViewModel解析指令,調(diào)用對(duì)應(yīng)的方法,回調(diào)給Activity。
比如一個(gè)DemoActivity需要獲取行業(yè)數(shù)據(jù),學(xué)校數(shù)據(jù),等。那么就可以把數(shù)據(jù)和操作都封裝成指定的對(duì)象。
//當(dāng)前頁(yè)面所需的數(shù)據(jù)與狀態(tài) data class Demo14ViewState( val industrys: List<Industry> = emptyList(), val schools: List<SchoolBean> = emptyList(), var isChanged: Boolean = false ) : BaseViewState() //當(dāng)前頁(yè)面需要的事件定義 sealed class DemoAction { object RequestIndustry : DemoAction() object RequestSchool : DemoAction() object RequestAllData : DemoAction() data class UpdateChanged(val isChange: Boolean) : DemoAction() }
Activity調(diào)用相關(guān)的接口就不是直接調(diào)用ViewModel的方法,而是:
override fun init() { //發(fā)送Intent指令,具體的實(shí)現(xiàn)由ViewModel實(shí)現(xiàn) mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestAllData) }
那么ViewModel就需要解析指令:
//Action分發(fā)入口 fun dispatch(action: DemoAction) { when (action) { is DemoAction.RequestIndustry -> requestIndustry() is DemoAction.RequestSchool -> requestSchool() is DemoAction.RequestAllData -> getTotalData() is DemoAction.UpdateChanged -> changeData(action.isChange) } } //獲取行業(yè)數(shù)據(jù) private fun requestIndustry() { //xxx }
完整的代碼如下:
ViewModel的實(shí)現(xiàn):
class Damo14ViewModel @ViewModelInject constructor( private val mRepository: Demo5Repository, @Assisted val savedState: SavedStateHandle ) : BaseViewModel() { private val _viewStates: MutableLiveData<Demo14ViewState> = MutableLiveData(Demo14ViewState()) //只需要暴露一個(gè)LiveData,包括頁(yè)面所有狀態(tài) val viewStates: LiveData<Demo14ViewState> = _viewStates //Action分發(fā)入口 fun dispatch(action: DemoAction) { when (action) { is DemoAction.RequestIndustry -> requestIndustry() is DemoAction.RequestSchool -> requestSchool() is DemoAction.RequestAllData -> getTotalData() is DemoAction.UpdateChanged -> changeData(action.isChange) } } //獲取行業(yè)數(shù)據(jù) private fun requestIndustry() { viewModelScope.launch { //開(kāi)始Loading loadStartLoading() val result = mRepository.getIndustry() result.checkSuccess { _viewStates.setState { copy(industrys = it ?: emptyList()) } } loadHideProgress() } } //獲取學(xué)校數(shù)據(jù) private fun requestSchool() { viewModelScope.launch { //開(kāi)始Loading loadStartLoading() val result = mRepository.getSchool() result.checkSuccess { _viewStates.setState { copy(schools = it ?: emptyList()) } } loadHideProgress() } } //獲取全部數(shù)據(jù) private fun getTotalData() { //默認(rèn)執(zhí)行在主線程的協(xié)程-必須用(可選擇默認(rèn)執(zhí)行在IO線程的協(xié)程) launchOnUI { //開(kāi)始Loading loadStartProgress() val industryResult = async { mRepository.getIndustry() } val schoolResult = async { mRepository.getSchool() } //一起處理數(shù)據(jù) val industry = industryResult.await() val school = schoolResult.await() //如果都成功了才一起返回 if (industry is OkResult.Success && school is OkResult.Success) { loadHideProgress() //設(shè)置多種LiveData _viewStates.setState { copy(industrys = industry.data ?: emptyList(), schools = school.data ?: emptyList()) } } } } //改變狀態(tài) private fun changeData(isChanged: Boolean) { _viewStates.setState { copy(isChanged = isChanged) } } //當(dāng)前頁(yè)面所需的數(shù)據(jù)與狀態(tài) data class Demo14ViewState( val industrys: List<Industry> = emptyList(), val schools: List<SchoolBean> = emptyList(), var isChanged: Boolean = false ) : BaseViewState() //如果想再度封裝,也可以把回調(diào)的結(jié)果封裝成類(lèi)似Action的對(duì)象,由頁(yè)面判斷回調(diào)的是哪一種類(lèi)型,進(jìn)行相關(guān)的操作 //這樣就不需要使用LiveData回調(diào)了,LiveData就只是作為保存數(shù)據(jù)的功能,由DemoEvent回調(diào) // sealed class DemoEvent { // object PopBack : DemoEvent() // data class ErrorMessage(val message: String) : DemoEvent() // } //當(dāng)前頁(yè)面需要的事件定義 sealed class DemoAction { object RequestIndustry : DemoAction() object RequestSchool : DemoAction() object RequestAllData : DemoAction() data class UpdateChanged(val isChange: Boolean) : DemoAction() } }
Activity的實(shí)現(xiàn):
@AndroidEntryPoint class Demo14Activity : BaseVDBActivity<Damo14ViewModel, ActivityDemo14Binding>() { private val clickProxy: ClickProxy by lazy { ClickProxy() } companion object { fun startInstance() { commContext().let { it.startActivity(Intent(it, Demo14Activity::class.java).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }) } } } override fun getDataBindingConfig(): DataBindingConfig { return DataBindingConfig(R.layout.activity_demo14) .addBindingParams(BR.click, clickProxy) } @SuppressLint("SetTextI18n") override fun startObserve() { //監(jiān)聽(tīng)兩者數(shù)據(jù)變化 mViewModel.viewStates.observeState( this, Damo14ViewModel.Demo14ViewState::industrys, Damo14ViewModel.Demo14ViewState::schools ) { industry, school -> YYLogUtils.w("industry: $industry ; school: $school") } //只監(jiān)聽(tīng)changed的變換 mViewModel.viewStates.observeState(this, Damo14ViewModel.Demo14ViewState::isChanged) { if (it) { val industry = mViewModel.viewStates.value?.industrys val school = mViewModel.viewStates.value?.schools mBinding.tvMessage.text = "industry: $industry ; school: $school" } } } override fun init() { //發(fā)送Intent指令,具體的實(shí)現(xiàn)由ViewModel實(shí)現(xiàn) mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestAllData) } /** * DataBinding事件處理 */ inner class ClickProxy { fun getData() { //發(fā)送Intent指令,具體的實(shí)現(xiàn)由ViewModel實(shí)現(xiàn) // mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestIndustry) // mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestSchool) mViewModel.dispatch(Damo14ViewModel.DemoAction.UpdateChanged(true)) } } }
注意,有些MVI的寫(xiě)法是回調(diào)給Activity的方式也是用對(duì)象封裝如我注釋的代碼:
//如果想再度封裝,也可以把回調(diào)的結(jié)果封裝成類(lèi)似Action的對(duì)象,由頁(yè)面判斷回調(diào)的是哪一種類(lèi)型,進(jìn)行相關(guān)的操作 //這樣就不需要使用LiveData回調(diào)了,LiveData就只是作為保存數(shù)據(jù)的功能,由DemoEvent回調(diào) // sealed class DemoEvent { // object PopBack : DemoEvent() // data class ErrorMessage(val message: String) : DemoEvent() // }
也可以使用LiveData返回,我這里使用擴(kuò)展方法observeState方法來(lái)監(jiān)聽(tīng),這樣可以保證只有你監(jiān)聽(tīng)的對(duì)象發(fā)生了變化才會(huì)收到回調(diào)。這個(gè)擴(kuò)展方法在MVVM框架也能使用。
擴(kuò)展方法如下:
import androidx.lifecycle.* import kotlin.reflect.KProperty1 /** * @auther Newki * @date 2022/2/10 * @description LiveData的擴(kuò)展 支持MVI模式 訂閱單個(gè)LiveData實(shí)現(xiàn)監(jiān)聽(tīng)不同的操作與數(shù)據(jù) */ //監(jiān)聽(tīng)一個(gè)屬性 fun <T, A> LiveData<T>.observeState( lifecycleOwner: LifecycleOwner, prop1: KProperty1<T, A>, action: (A) -> Unit ) { this.map { StateTuple1(prop1.get(it)) }.distinctUntilChanged().observe(lifecycleOwner) { (a) -> action.invoke(a) } } //監(jiān)聽(tīng)兩個(gè)屬性 fun <T, A, B> LiveData<T>.observeState( lifecycleOwner: LifecycleOwner, prop1: KProperty1<T, A>, prop2: KProperty1<T, B>, action: (A, B) -> Unit ) { this.map { StateTuple2(prop1.get(it), prop2.get(it)) }.distinctUntilChanged().observe(lifecycleOwner) { (a, b) -> action.invoke(a, b) } } //監(jiān)聽(tīng)三個(gè)屬性 fun <T, A, B, C> LiveData<T>.observeState( lifecycleOwner: LifecycleOwner, prop1: KProperty1<T, A>, prop2: KProperty1<T, B>, prop3: KProperty1<T, C>, action: (A, B, C) -> Unit ) { this.map { StateTuple3(prop1.get(it), prop2.get(it), prop3.get(it)) }.distinctUntilChanged().observe(lifecycleOwner) { (a, b, c) -> action.invoke(a, b, c) } } internal data class StateTuple1<A>(val a: A) internal data class StateTuple2<A, B>(val a: A, val b: B) internal data class StateTuple3<A, B, C>(val a: A, val b: B, val c: C) //更新State fun <T> MutableLiveData<T>.setState(reducer: T.() -> T) { this.value = this.value?.reducer() }
太干了,一張圖都沒(méi)上,最后總結(jié)一下:
世界上本無(wú)框架,用的人多了就成了框架,適合自己的才是好的。不是一定說(shuō)出了最新框架我就要用最新的框架,理解之后再使用才能得心應(yīng)手。
個(gè)人目前平時(shí)開(kāi)發(fā)中用的也是MVVM框架。后期會(huì)出一些MVVM的封裝和用法開(kāi)源。
以上就是Android開(kāi)發(fā)框架MVC-MVP-MVVM-MVI的演變Demo的詳細(xì)內(nèi)容,更多關(guān)于Android框架MVC MVP MVVM MVI的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android使用Rotate3dAnimation實(shí)現(xiàn)3D旋轉(zhuǎn)動(dòng)畫(huà)效果的實(shí)例代碼
利用Android的ApiDemos的Rotate3dAnimation實(shí)現(xiàn)了個(gè)圖片3D旋轉(zhuǎn)的動(dòng)畫(huà),圍繞Y軸進(jìn)行旋轉(zhuǎn),還可以實(shí)現(xiàn)Z軸的縮放。點(diǎn)擊開(kāi)始按鈕開(kāi)始旋轉(zhuǎn),點(diǎn)擊結(jié)束按鈕停止旋轉(zhuǎn)。2018-05-05詳解Android 進(jìn)程間通信的幾種實(shí)現(xiàn)方式
在Android SDK中提供了4種用于跨進(jìn)程通訊的方式。這篇文章主要介紹了詳解Android 進(jìn)程間通信的幾種實(shí)現(xiàn)方式,有興趣的可以了解一下。2017-01-01android實(shí)現(xiàn)一個(gè)圖片驗(yàn)證碼倒計(jì)時(shí)功能
本文通過(guò)實(shí)例代碼給大家介紹了android實(shí)現(xiàn)一個(gè)圖片驗(yàn)證碼倒計(jì)時(shí)功能,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-11-11Android編程之匿名內(nèi)部類(lèi)與回調(diào)函數(shù)用法分析
這篇文章主要介紹了Android編程之匿名內(nèi)部類(lèi)與回調(diào)函數(shù)用法,結(jié)合實(shí)例形式分析了Android編程中所涉及的java匿名內(nèi)部類(lèi)與回調(diào)函數(shù)的概念、定義、使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2016-10-10Android Service的啟動(dòng)過(guò)程分析
這篇文章主要介紹了Android Service的啟動(dòng)過(guò)程分析的相關(guān)資料,需要的朋友可以參考下2017-04-04android實(shí)現(xiàn)關(guān)閉或開(kāi)啟移動(dòng)網(wǎng)絡(luò)數(shù)據(jù)
本篇文章是對(duì)android實(shí)現(xiàn)關(guān)閉或開(kāi)啟移動(dòng)網(wǎng)絡(luò)數(shù)據(jù)進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-06-06Android ViewPager無(wú)限循環(huán)實(shí)現(xiàn)底部小圓點(diǎn)動(dòng)態(tài)滑動(dòng)
這篇文章主要為大家詳細(xì)介紹了Android ViewPager無(wú)限循環(huán)實(shí)現(xiàn)底部小圓點(diǎn)動(dòng)態(tài)滑動(dòng)的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-03-03Android三種方式實(shí)現(xiàn)ProgressBar自定義圓形進(jìn)度條
這篇文章主要介紹了Android三種方式實(shí)現(xiàn)ProgressBar自定義圓形進(jìn)度條的相關(guān)資料,需要的朋友可以參考下2016-03-03Android6.0獲取動(dòng)態(tài)權(quán)限代碼示例
這篇文章主要介紹了Android6.0以上獲取動(dòng)態(tài)權(quán)限代碼示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-11-11