MVVMHabit的使用(1)

MVVMHabit

MVVMHabit介紹

基于谷歌最新AAC架構,MVVM設計模式的一套快速開發(fā)庫,整合Okhttp+RxJava+Retrofit+Glide等主流模塊,滿足日常開發(fā)需求。使用該框架可以快速開發(fā)一個高質量、易維護的Android應用。

目前,android流行的MVC、MVP模式的開發(fā)框架很多,然而一款基于MVVM模式開發(fā)框架卻很少。MVVMHabit是以谷歌DataBinding+LiveData+ViewModel框架為基礎,整合Okhttp+RxJava+Retrofit+Glide等流行模塊,加上各種原生控件自定義的BindingAdapter,讓事件與數據源完美綁定的一款容易上癮的實用性MVVM快速開發(fā)框架。從此告別findViewById(),告別setText(),告別setOnClickListener()...

框架流程

在這里插入圖片描述

框架特點

快速開發(fā)

只需要寫項目的業(yè)務邏輯,不用再去關心網絡請求、權限申請、View的生命周期等問題,擼起袖子就是干。

維護方便

MVVM開發(fā)模式,低耦合,邏輯分明。Model層負責將請求的數據交給ViewModel;ViewModel層負責將請求到的數據做業(yè)務邏輯處理,最后交給View層去展示,與View一一對應;View層只負責界面繪制刷新,不處理業(yè)務邏輯,非常適合分配獨立模塊開發(fā)。

流行框架

retrofit+okhttp+rxJava負責網絡請求;gson負責解析json數據;glide負責加載圖片;rxlifecycle負責管理view的生命周期;與網絡請求共存亡;rxbinding結合databinding擴展UI事件;rxpermissions負責Android 6.0權限申請;material-dialogs一個漂亮的、流暢的、可定制的material design風格的對話框。

基類封裝

專門針對MVVM模式打造的BaseActivity、BaseFragment、BaseViewModel,在View層中不再需要定義ViewDataBinding和ViewModel,直接在BaseActivity、BaseFragment上限定泛型即可使用。普通界面只需要編寫Fragment,然后使用ContainerActivity盛裝(代理),這樣就不需要每個界面都在AndroidManifest中注冊一遍。

全局操作

  • 全局的Activity堆棧式管理,在程序任何地方可以打開、結束指定的Activity,一鍵退出應用程序。
  • LoggingInterceptor全局攔截網絡請求日志,打印Request和Response,格式化json、xml數據顯示,方便與后臺調試接口
  • 全局Cookie,支持SharedPreferences和內存兩種管理模式。
  • 通用的網絡請求異常監(jiān)聽,根據不同的狀態(tài)碼或異常設置相應的message。
  • 全局的異常捕獲,程序發(fā)生異常時不會崩潰,可跳入異常界面重啟應用。
  • 全局事件回調,提供RxBus、Messenger兩種回調方式。
  • 全局任意位置一行代碼實現文件下載進度監(jiān)聽(暫不支持多文件進度監(jiān)聽)。
  • 全局點擊事件防抖動處理,防止點擊過快。

依賴

在主工程app的build.gradle的android {}中加入:

dataBinding {
    enabled true
}

在根目錄的build.gradle中加入

allprojects {
    repositories {
        ...
        google()
        jcenter()
        maven { url 'https://jitpack.io' }
    }
}

在主項目app的build.gradle中依賴

dependencies {
    ...
    implementation 'com.github.goldze:MVVMHabit:3.1.6'
}

下載例子程序,在主項目app的build.gradle中依賴例子程序中的mvvmhabit:

dependencies {  
    ...
    implementation project(':mvvmhabit')
}

配置config.gradle

如果不是遠程依賴,而是下載的例子程序,那么還需要將例子程序中的config.gradle放入你的主項目根目錄中,然后在根目錄build.gradle的第一行加入:

apply from: "config.gradle"

注意: config.gradle中的

android = [] 是你的開發(fā)相關版本配置,可自行修改

support = [] 是你的support相關配置,可自行修改

dependencies = [] 是依賴第三方庫的配置,可以加新庫,但不要去修改原有第三方庫的版本號,不然可能會編譯不過

androidX

androidX分支

AndroidX分支目前還處于過度階段,如果你的項目需要AndroidX的支持,那么請使用本地依賴的方式導入該分支的Library。

添加權限

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

配置Application

繼承mvvmhabit中的BaseApplication,或者調用

BaseApplication.setApplication(this);

來初始化你的Application

可以在你的自己AppApplication中配置

//是否開啟日志打印
KLog.init(true);
//配置全局異常崩潰操作
CaocConfig.Builder.create()
    .backgroundMode(CaocConfig.BACKGROUND_MODE_SILENT) //背景模式,開啟沉浸式
    .enabled(true) //是否啟動全局異常捕獲
    .showErrorDetails(true) //是否顯示錯誤詳細信息
    .showRestartButton(true) //是否顯示重啟按鈕
    .trackActivities(true) //是否跟蹤Activity
    .minTimeBetweenCrashesMs(2000) //崩潰的間隔時間(毫秒)
    .errorDrawable(R.mipmap.ic_launcher) //錯誤圖標
    .restartActivity(LoginActivity.class) //重新啟動后的activity
    //.errorActivity(YourCustomErrorActivity.class) //崩潰后的錯誤activity
    //.eventListener(new YourCustomEventListener()) //崩潰后的錯誤監(jiān)聽
    .apply();

第一個案例:登陸案例

以大家都熟悉的登錄操作為例:三個文件LoginActivty.java、LoginViewModel.java、activity_login.xml

關聯ViewModel

在activity_login.xml中關聯LoginViewModel。


    <data>
         <variable
            name="viewModel"
            type="com.goldze.mvvmhabit.ui.login.LoginViewModel" />
    </data>

variable - type:類的全路徑
variable - name:變量名

繼承BaseActivity

LoginActivity繼承BaseActivity

public class LoginActivity extends BaseActivity<ActivityLoginBinding, LoginViewModel> {
    //ActivityLoginBinding類是databinding框架自定生成的,對activity_login.xml
    @Override
    public int initContentView(Bundle savedInstanceState) {
        return R.layout.activity_login;
    }

    @Override
    public int initVariableId() {
        return BR.viewModel;
    }

    @Override
    public LoginViewModel initViewModel() {
        //View持有ViewModel的引用,如果沒有特殊業(yè)務處理,這個方法可以不重寫
        return ViewModelProviders.of(this).get(LoginViewModel.class);
    }
}

保存activity_login.xml后databinding會生成一個ActivityLoginBinding類。(如果沒有生成,試著點擊Build->Clean Project)

BaseActivity是一個抽象類,有兩個泛型參數,一個是ViewDataBinding,另一個是BaseViewModel,上面的ActivityLoginBinding則是繼承的ViewDataBinding作為第一個泛型約束,LoginViewModel繼承BaseViewModel作為第二個泛型約束。

重寫B(tài)aseActivity的二個抽象方法

initContentView() 返回界面layout的id
initVariableId() 返回變量的id,對應activity_login中name="viewModel",就像一個控件的id,可以使用R.id.xxx,這里的BR跟R文件一樣,由系統(tǒng)生成,使用BR.xxx找到這個ViewModel的id。

選擇性重寫initViewModel()方法,返回ViewModel對象

@Override
public LoginViewModel initViewModel() {
    //View持有ViewModel的引用,如果沒有特殊業(yè)務處理,這個方法可以不重寫
    return ViewModelProviders.of(this).get(LoginViewModel.class);
}

注意: 不重寫initViewModel(),默認會創(chuàng)建LoginActivity中第二個泛型約束的LoginViewModel,如果沒有指定第二個泛型,則會創(chuàng)建BaseViewModel

繼承BaseViewModel

LoginViewModel繼承BaseViewModel

public class LoginViewModel extends BaseViewModel {
    public LoginViewModel(@NonNull Application application) {
        super(application);
    }
    ....
}

BaseViewModel與BaseActivity通過LiveData來處理常用UI邏輯,即可在ViewModel中使用父類的showDialog()、startActivity()等方法。在這個LoginViewModel中就可以盡情的寫你的邏輯了!

BaseFragment的使用和BaseActivity一樣,詳情參考Demo。

數據綁定

擁有databinding框架自帶的雙向綁定,也有擴展

傳統(tǒng)綁定

綁定用戶名:
在LoginViewModel中定義

//用戶名的綁定
public ObservableField<String> userName = new ObservableField<>("");

在用戶名EditText標簽中綁定

android:text="@={viewModel.userName}"

這樣一來,輸入框中輸入了什么,userName.get()的內容就是什么,userName.set("")設置什么,輸入框中就顯示什么。 注意: @符號后面需要加=號才能達到雙向綁定效果;userName需要是public的,不然viewModel無法找到它。

點擊事件綁定:

在LoginViewModel中定義

//登錄按鈕的點擊事件
public View.OnClickListener loginOnClick = new View.OnClickListener() {
    @Override
    public void onClick(View v) {
            
    }
};

在登錄按鈕標簽中綁定

android:onClick="@{viewModel.loginOnClick}"

這樣一來,用戶的點擊事件直接被回調到ViewModel層了,更好的維護了業(yè)務邏輯

這就是強大的databinding框架雙向綁定的特性,不用再給控件定義id,setText(),setOnClickListener()。

但是,光有這些,完全滿足不了我們復雜業(yè)務的需求啊!MVVMHabit閃亮登場:它有一套自定義的綁定規(guī)則,可以滿足大部分的場景需求,請繼續(xù)往下看。

自定義綁定

還拿點擊事件說吧,不用傳統(tǒng)的綁定方式,使用自定義的點擊事件綁定。

在LoginViewModel中定義

//登錄按鈕的點擊事件
public BindingCommand loginOnClickCommand = new BindingCommand(new BindingAction() {
    @Override
    public void call() {
            
    }
});

在activity_login中定義命名空間

xmlns:binding="http://schemas.android.com/apk/res-auto"

在登錄按鈕標簽中綁定

binding:onClickCommand="@{viewModel.loginOnClickCommand}"

這和原本傳統(tǒng)的綁定不是一樣嗎?不,這其實是有差別的。使用這種形式的綁定,在原本事件綁定的基礎之上,帶有防重復點擊的功能,1秒內多次點擊也只會執(zhí)行一次操作。如果不需要防重復點擊,可以加入這條屬性

binding:isThrottleFirst="@{Boolean.TRUE}"

那這功能是在哪里做的呢?答案在下面的代碼中。

//防重復點擊間隔(秒)
public static final int CLICK_INTERVAL = 1;

/**
* requireAll 是意思是是否需要綁定全部參數, false為否
* View的onClick事件綁定
* onClickCommand 綁定的命令,
* isThrottleFirst 是否開啟防止過快點擊
*/
@BindingAdapter(value = {"onClickCommand", "isThrottleFirst"}, requireAll = false)
public static void onClickCommand(View view, final BindingCommand clickCommand, final boolean isThrottleFirst) {
    if (isThrottleFirst) {
        RxView.clicks(view)
        .subscribe(new Consumer<Object>() {
            @Override
            public void accept(Object object) throws Exception {
                if (clickCommand != null) {
                    clickCommand.execute();
                }
            }
        });
    } else {
        RxView.clicks(view)
        .throttleFirst(CLICK_INTERVAL, TimeUnit.SECONDS)//1秒鐘內只允許點擊1次
        .subscribe(new Consumer<Object>() {
            @Override
            public void accept(Object object) throws Exception {
                if (clickCommand != null) {
                    clickCommand.execute();
                }
            }
        });
    }
}

onClickCommand方法是自定義的,使用@BindingAdapter注解來標明這是一個綁定方法。在方法中使用了RxView來增強view的clicks事件,.throttleFirst()限制訂閱者在指定的時間內重復執(zhí)行,最后通過BindingCommand將事件回調出去,就好比有一種攔截器,在點擊時先做一下判斷,然后再把事件沿著他原有的方向傳遞。

是不是覺得有點意思,好戲還在后頭呢!

自定義ImageView圖片加載

綁定圖片路徑:

在ViewModel中定義

public String imgUrl = "http://img0.imgtn.bdimg.com/it/u=2183314203,562241301&fm=26&gp=0.jpg";

在ImageView標簽中

binding:url="@{viewModel.imgUrl}"

url是圖片路徑,這樣綁定后,這個ImageView就會去顯示這張圖片,不限網絡圖片還是本地圖片。

如果需要給一個默認加載中的圖片,可以加這一句

binding:placeholderRes="@{R.mipmap.ic_launcher_round}"

R文件需要在data標簽中導入使用,如:import type="com.goldze.mvvmhabit.R" />

BindingAdapter中的實現

@BindingAdapter(value = {"url", "placeholderRes"}, requireAll = false)
public static void setImageUri(ImageView imageView, String url, int placeholderRes) {
    if (!TextUtils.isEmpty(url)) {
        //使用Glide框架加載圖片
        Glide.with(imageView.getContext())
            .load(url)
            .placeholder(placeholderRes)
            .into(imageView);
    }
}

很簡單就自定義了一個ImageView圖片加載的綁定,學會這種方式,可自定義擴展。

如果你對這些感興趣,可以下載源碼,在binding包中可以看到各類控件的綁定實現方式

RecyclerView綁定

RecyclerView也是很常用的一種控件,傳統(tǒng)的方式需要針對各種業(yè)務要寫各種Adapter,如果你使用了mvvmhabit,則可大大簡化這種工作量,從此告別setAdapter()。

在ViewModel中定義:

//給RecyclerView添加items
public final ObservableList<NetWorkItemViewModel> observableList = new ObservableArrayList<>();
//給RecyclerView添加ItemBinding
public final ItemBinding<NetWorkItemViewModel> itemBinding = ItemBinding.of(BR.viewModel, R.layout.item_network);

ObservableList<>和ItemBinding<>的泛型是Item布局所對應的ItemViewModel

<android.support.v7.widget.RecyclerView
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    binding:itemBinding="@{viewModel.itemBinding}"
    binding:items="@{viewModel.observableList}"
    binding:layoutManager="@{LayoutManagers.linear()}"
    binding:lineManager="@{LineManagers.horizontal()}" />

layoutManager控制是線性(包含水平和垂直)排列還是網格排列,lineManager是設置分割線

網格布局的寫法:binding:layoutManager="@{LayoutManagers.grid(3)}
水平布局的寫法:binding:layoutManager="@{LayoutManagers.linear(LinearLayoutManager.HORIZONTAL,Boolean.FALSE)}"

用到相關類,則需要導入該類才能使用,和導入Java類相似

<import type="me.tatarka.bindingcollectionadapter2.LayoutManagers" />
<import type="me.goldze.mvvmhabit.binding.viewadapter.recyclerview.LineManagers" />
<import type="android.support.v7.widget.LinearLayoutManager" />

這樣綁定后,在ViewModel中調用ObservableList的add()方法,添加一個ItemViewModel,界面上就會實時繪制出一個Item。在Item對應的ViewModel中,同樣可以以綁定的形式完成邏輯

可以在請求到數據后,循環(huán)添加observableList.add(new NetWorkItemViewModel(NetWorkViewModel.this, entity));詳細可以參考例子程序中NetWorkViewModel類。

注意: 在以前的版本中,ItemViewModel是繼承BaseViewModel,傳入Context,新版本3.x中可繼承ItemViewModel,傳入當前頁面的ViewModel

網絡請求

網絡請求一直都是一個項目的核心,現在的項目基本都離不開網絡,一個好用網絡請求框架可以讓開發(fā)事半功倍。

Retrofit+Okhttp+RxJava

現今,這三個組合基本是網絡請求的標配,如果你對這三個框架不了解,建議先去查閱相關資料。

quare出品的框架,用起來確實非常方便。MVVMHabit中引入了

api "com.squareup.okhttp3:okhttp:3.10.0"
api "com.squareup.retrofit2:retrofit:2.4.0"
api "com.squareup.retrofit2:converter-gson:2.4.0"
api "com.squareup.retrofit2:adapter-rxjava2:2.4.0"

構建Retrofit時加入

Retrofit retrofit = new Retrofit.Builder()
    .addConverterFactory(GsonConverterFactory.create())
    .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
    .build();

或者直接使用例子程序中封裝好的RetrofitClient。

網絡攔截器

LoggingInterceptor: 全局攔截請求信息,格式化打印Request、Response,可以清晰的看到與后臺接口對接的數據,

LoggingInterceptor mLoggingInterceptor = new LoggingInterceptor
    .Builder()//構建者模式
    .loggable(true) //是否開啟日志打印
    .setLevel(Level.BODY) //打印的等級
    .log(Platform.INFO) // 打印類型
    .request("Request") // request的Tag
    .response("Response")// Response的Tag
    .addHeader("version", BuildConfig.VERSION_NAME)//打印版本
    .build()

構建okhttp時加入

OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .addInterceptor(mLoggingInterceptor)
    .build();

CacheInterceptor: 緩存攔截器,當沒有網絡連接的時候自動讀取緩存中的數據,緩存存放時間默認為3天。

創(chuàng)建緩存對象

//緩存時間
int CACHE_TIMEOUT = 10 * 1024 * 1024
//緩存存放的文件
File httpCacheDirectory = new File(mContext.getCacheDir(), "goldze_cache");
//緩存對象
Cache cache = new Cache(httpCacheDirectory, CACHE_TIMEOUT);

構建okhttp時加入

OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .cache(cache)
    .addInterceptor(new CacheInterceptor(mContext))
    .build();

Cookie管理

MVVMHabit提供兩種CookieStore:PersistentCookieStore (SharedPreferences管理)和MemoryCookieStore (內存管理),可以根據自己的業(yè)務需求,在構建okhttp時加入相應的cookieJar

kHttpClient okHttpClient = new OkHttpClient.Builder()
    .cookieJar(new CookieJarImpl(new PersistentCookieStore(mContext)))
    .build();

或者

OkHttpClient okHttpClient = new OkHttpClient.Builder()
    .cookieJar(new CookieJarImpl(new MemoryCookieStore()))
    .build();

綁定生命周期

請求在ViewModel層。默認在BaseActivity中注入了LifecycleProvider對象到ViewModel,用于綁定請求的生命周期,View與請求共存亡。

RetrofitClient.getInstance().create(DemoApiService.class)
    .demoGet()
    .compose(RxUtils.bindToLifecycle(getLifecycleProvider())) // 請求與View周期同步
    .compose(RxUtils.schedulersTransformer())  // 線程調度
    .compose(RxUtils.exceptionTransformer())   // 網絡錯誤的異常轉換
    .subscribe(new Consumer<BaseResponse<DemoEntity>>() {
        @Override
        public void accept(BaseResponse<DemoEntity> response) throws Exception {
                       
        }
    }, new Consumer<ResponseThrowable>() {
        @Override
        public void accept(ResponseThrowable throwable) throws Exception {
                        
        }
    });

在請求時關鍵需要加入組合操作符.compose(RxUtils.bindToLifecycle(getLifecycleProvider()))

注意: 由于BaseActivity/BaseFragment都實現了LifecycleProvider接口,并且默認注入到ViewModel中,所以在調用請求方法時可以直接調用getLifecycleProvider()拿到生命周期接口。如果你沒有使用 mvvmabit 里面的BaseActivity或BaseFragment,使用自己定義的Base,那么需要讓你自己的Activity繼承RxAppCompatActivity、Fragment繼承RxFragment才能用RxUtils.bindToLifecycle(lifecycle)方法。

網絡異常處理

網絡異常在網絡請求中非常常見,比如請求超時、解析錯誤、資源不存在、服務器內部錯誤等,在客戶端則需要做相應的處理(當然,你可以把一部分異常甩鍋給網絡,比如當出現code 500時,提示:請求超時,請檢查網絡連接,此時偷偷將異常信息發(fā)送至后臺(手動滑稽))。

在使用Retrofit請求時,加入組合操作符.compose(RxUtils.exceptionTransformer()),當發(fā)生網絡異常時,回調onError(ResponseThrowable)方法,可以拿到異常的code和message,做相應處理。

mvvmhabit中自定義了一個ExceptionHandle,已為你完成了大部分網絡異常的判斷,也可自行根據項目的具體需求調整邏輯。

注意: 這里的網絡異常code,并非是與服務端協(xié)議約定的code。網絡異常可以分為兩部分,一部分是協(xié)議異常,即出現code = 404、500等,屬于HttpException,另一部分為請求異常,即出現:連接超時、解析錯誤、證書驗證失等。而與服務端約定的code規(guī)則,它不屬于網絡異常,它是屬于一種業(yè)務異常。在請求中可以使用RxJava的filter(過濾器),也可以自定義BaseSubscriber統(tǒng)一處理網絡請求的業(yè)務邏輯異常。由于每個公司的業(yè)務協(xié)議不一樣,所以具體需要你自己來處理該類異常。

事件總線

事件總線存在的優(yōu)點想必大家都很清楚了,android自帶的廣播機制對于組件間的通信而言,使用非常繁瑣,通信組件彼此之間的訂閱和發(fā)布的耦合也比較嚴重,特別是對于事件的定義,廣播機制局限于序列化的類(通過Intent傳遞),不夠靈活。

RxBus

RxBus并不是一個庫,而是一種模式。相信大多數開發(fā)者都使用過EventBus,對RxBus也是很熟悉。由于MVVMabit中已經加入RxJava,所以采用了RxBus代替EventBus作為事件總線通信,以減少庫的依賴。

使用方法:

在ViewModel中重寫registerRxBus()方法來注冊RxBus,重寫removeRxBus()方法來移除RxBus

//訂閱者
private Disposable mSubscription;
//注冊RxBus
@Override
public void registerRxBus() {
    super.registerRxBus();
    mSubscription = RxBus.getDefault().toObservable(String.class)
        .subscribe(new Consumer<String>() {
            @Override
            public void accept(String s) throws Exception {

            }
        });
    //將訂閱者加入管理站
    RxSubscriptions.add(mSubscription);
}

//移除RxBus
@Override
public void removeRxBus() {
    super.removeRxBus();
    //將訂閱者從管理站中移除
    RxSubscriptions.remove(mSubscription);
}

在需要執(zhí)行回調的地方發(fā)送

RxBus.getDefault().post(object);

Messenger

Messenger是一個輕量級全局的消息通信工具,在我們的復雜業(yè)務中,難免會出現一些交叉的業(yè)務,比如ViewModel與ViewModel之間需要有數據交換,這時候可以輕松地使用Messenger發(fā)送一個實體或一個空消息,將事件從一個ViewModel回調到另一個ViewModel中。

使用方法:

定義一個靜態(tài)String類型的字符串token

public static final String TOKEN_LOGINVIEWMODEL_REFRESH = "token_loginviewmodel_refresh";

在ViewModel中注冊消息監(jiān)聽

//注冊一個空消息監(jiān)聽 
//參數1:接受人(上下文)
//參數2:定義的token
//參數3:執(zhí)行的回調監(jiān)聽
Messenger.getDefault().register(this, LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH, new BindingAction() {
    @Override
    public void call() {
    
    }
});

//注冊一個帶數據回調的消息監(jiān)聽 
//參數1:接受人(上下文)
//參數2:定義的token
//參數3:實體的泛型約束
//參數4:執(zhí)行的回調監(jiān)聽
Messenger.getDefault().register(this, LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH, String.class, new BindingConsumer<String>() {
    @Override
    public void call(String s) {
         
    }
});

在需要回調的地方使用token發(fā)送消息

/發(fā)送一個空消息
//參數1:定義的token
Messenger.getDefault().sendNoMsg(LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH);

//發(fā)送一個帶數據回調消息
//參數1:回調的實體
//參數2:定義的token
Messenger.getDefault().send("refresh",LoginViewModel.TOKEN_LOGINVIEWMODEL_REFRESH);

token最好不要重名,不然可能就會出現邏輯上的bug,為了更好的維護和清晰邏輯,建議以aa_bb_cc的格式來定義token。aa:TOKEN,bb:ViewModel的類名,cc:動作名(功能名)。

為了避免大量使用Messenger,建議只在ViewModel與ViewModel之間使用,View與ViewModel之間采用ObservableField去監(jiān)聽UI上的邏輯,可在繼承了Base的Activity或Fragment中重寫initViewObservable()方法來初始化UI的監(jiān)聽

注冊了監(jiān)聽,當然也要解除它。在BaseActivity、BaseFragment的onDestroy()方法里已經調用Messenger.getDefault().unregister(viewModel);解除注冊,所以不用擔心忘記解除導致的邏輯錯誤和內存泄漏。

文件下載

文件下載幾乎是每個app必備的功能,圖文的下載,軟件的升級等都要用到,mvvmhabit使用Retrofit+Okhttp+RxJava+RxBus實現一行代碼監(jiān)聽帶進度的文件下載。

String loadUrl = "你的文件下載路徑";
String destFileDir = context.getCacheDir().getPath();  //文件存放的路徑
String destFileName = System.currentTimeMillis() + ".apk";//文件存放的名稱
DownLoadManager.getInstance().load(loadUrl, new ProgressCallBack<ResponseBody>(destFileDir, destFileName) {
    @Override
    public void onStart() {
        //RxJava的onStart()
    }

    @Override
    public void onCompleted() {
        //RxJava的onCompleted()
    }

    @Override
    public void onSuccess(ResponseBody responseBody) {
        //下載成功的回調
    }

    @Override
    public void progress(final long progress, final long total) {
        //下載中的回調 progress:當前進度 ,total:文件總大小
    }

    @Override
    public void onError(Throwable e) {
        //下載錯誤回調
    }
});

在ProgressResponseBody中使用了RxBus,發(fā)送下載進度信息到ProgressCallBack中,繼承ProgressCallBack就可以監(jiān)聽到下載狀態(tài)?;卣{方法全部執(zhí)行在主線程,方便UI的更新,詳情請參考例子程序。

ContainerActivity

一個盛裝Fragment的一個容器(代理)Activity,普通界面只需要編寫Fragment,使用此Activity盛裝,這樣就不需要每個界面都在AndroidManifest中注冊一遍

使用方法:

在ViewModel中調用BaseViewModel的方法開一個Fragment

startContainerActivity(你的Fragment類名.class.getCanonicalName())

在ViewModel中調用BaseViewModel的方法,攜帶一個序列化實體打開一個Fragment

Bundle mBundle = new Bundle();
mBundle.putParcelable("entity", entity);
startContainerActivity(你的Fragment類名.class.getCanonicalName(), mBundle);

在你的Fragment中取出實體

Bundle mBundle = getArguments();
if (mBundle != null) {
    entity = mBundle.getParcelable("entity");
}

6.0權限申請

使用方法:

例如請求相機權限,在ViewModel中調用

//請求打開相機權限
RxPermissions rxPermissions = new RxPermissions((Activity) context);
rxPermissions.request(Manifest.permission.CAMERA)
    .subscribe(new Consumer<Boolean>() {
        @Override
        public void accept(Boolean aBoolean) throws Exception {
            if (aBoolean) {
                ToastUtils.showShort("權限已經打開,直接跳入相機");
            } else {
                ToastUtils.showShort("權限被拒絕");
            }
        }
    });

圖片壓縮

為了節(jié)約用戶流量和加快圖片上傳的速度,某些場景將圖片在本地壓縮后再傳給后臺,所以特此提供一個圖片壓縮的輔助功能。

使用方法:

RxJava的方式壓縮單張圖片,得到一個壓縮后的圖片文件對象

String filePath = "mnt/sdcard/1.png";
ImageUtils.compressWithRx(filePath, new Consumer<File>() {
    @Override
    public void accept(File file) throws Exception {
        //將文件放入RequestBody
        ...
    }
});

RxJava的方式壓縮多張圖片,按集合順序每壓縮成功一張,都將在onNext方法中得到一個壓縮后的圖片文件對象

List<String> filePaths = new ArrayList<>();
filePaths.add("mnt/sdcard/1.png");
filePaths.add("mnt/sdcard/2.png");
ImageUtils.compressWithRx(filePaths, new Subscriber() {
    @Override
    public void onCompleted() {
    
    }
    
    @Override
    public void onError(Throwable e) {
    
    }
    
    @Override
    public void onNext(File file) {

    }
});

其他輔助類

ToastUtils: 吐司工具類

MaterialDialogUtils: Material風格對話框工具類

SPUtils: SharedPreferences工具類

SDCardUtils: SD卡相關工具類

ConvertUtils: 轉換相關工具類

StringUtils: 字符串相關工具類

RegexUtils: 正則相關工具類

KLog: 日志打印,含json格式打印

混淆

子程序中給出了最新的【MVVMHabit混淆規(guī)則】,包含MVVMHabit中依賴的所有第三方library,可以將規(guī)則直接拷貝到自己app的混淆規(guī)則中。在此基礎上你只需要關注自己業(yè)務代碼以及自己引入第三方的混淆,【MVVMHabit混淆規(guī)則】請參考app目錄下的proguard-rules.pro文件。

組件化:MVVMHabitComponent

進階Android組件化方案,請移步:MVVMHabitComponent

基于MVVMHabit框架,結合阿里ARouter打造的一套Android-Databinding組件化開發(fā)方案。

?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容