mvp+dagger2+retrofit2+rxjava 項目框架 最佳實踐

概述

原來一直在用Android最原生的框架進行開發(fā)夫嗓,最多也就使用了butterknife,減少了很多的findviewById删窒。前段時間看google的IO大會宏多,偶爾聽到了新型的Android開發(fā)框架dagger2等等萧求,然后對此框架產(chǎn)生了濃厚的興趣腕巡。

通過一段時間的深入學(xué)習(xí),把我的學(xué)習(xí)分享出來嫩挤,希望大家能夠喜歡害幅。

mvp+dagger2+retrofit2+rxjava 一套開發(fā)模式自我感覺將是以后Android開發(fā)的趨勢,盡早的用起來吧岂昭。

使用新型框架能給我們帶來什么好處以现?

  • 解耦,降低模塊耦合度约啊。
  • 可以更方便的寫單元測試邑遏。
  • 減少Activity編碼
  • 提高團隊協(xié)作的效率
  • 提高編碼的效率
  • 提高代碼的可讀性

示例:

本文示例功能:

  • retrofit2+Rxjava進行Http和Https網(wǎng)絡(luò)請求封裝
  • MVP工程結(jié)構(gòu)
  • Rxjava的使用示例
  • dagger2的使用示例

說明

閱讀此文首先你要對以下技術(shù)有一定的了解。對以上技術(shù)還不熟悉的朋友可以先去了解一下恰矩。
在我閱讀過無數(shù)相關(guān)技術(shù)文章之后无宿,我給大家推薦這些技術(shù)學(xué)習(xí)的文章:

dagger2

理論:http://android.jobbole.com/82694/
實例(網(wǎng)絡(luò)上沒有找到合適的,我自己寫的一篇博文):http://www.reibang.com/p/269c3f70ec1e
官方:http://google.github.io/dagger/

mvp:

這個理論很簡單枢里,自己百度或者google吧
示例:https://github.com/googlesamples/android-architecture

retrofit2:

官方:http://square.github.io/retrofit/

rxjava:

偏理論:http://gank.io/post/560e15be2dca930e00da1083
偏實踐:http://blog.chinaunix.net/uid-20771867-id-5187376.html

對上面的技術(shù)有一定的了解后,我們開始一個示例:

架構(gòu)搭建

首先我們要一個示例的方式來詳細說明整體項目的架構(gòu)與思想

示例功能蹂午;

  • 登錄功能
    • 檢查用戶名和密碼是否合法
    • 登錄按鈕如果不合法則不可點擊栏豺,合法后登錄按鈕可以點擊
    • 調(diào)用登錄接口進行登錄
    • 將用戶名和密碼保存本地
  • 文章列表
    • 從網(wǎng)絡(luò)獲取文章列表并展示
    • 將文章列表保存到數(shù)據(jù)庫
    • 點擊列表進入文章詳情
    • 網(wǎng)絡(luò)獲取圖片
  • 單元測試
    • 集成測試
    • 單元測試

整體架構(gòu)圖

未命名.png
未命名.png

運行webserver json

  //安裝json-server
  $ npm install -g json-server
  //進入工程目錄
  $ cd AndroidArchitecture/
  //運行服務(wù)
  json-server --watch login.json

運行后可以通過此地址訪問接口
http://localhost:3000/users
http://localhost:3000/topics

關(guān)于工程

由于工程代碼較多,在這里就不一一將代碼貼出了豆胸,詳細的demo地址見我的github
https://github.com/wlj32011/AndroidArchitecture

下圖為demo目錄接口奥洼,查看demo源碼可以參考此結(jié)構(gòu)


AndroidArchitecture.png
AndroidArchitecture.png

關(guān)鍵代碼

  • 網(wǎng)絡(luò)請求返回消息體統(tǒng)一錯誤處理

    消息體結(jié)構(gòu)

//登錄
{
  "status_msg" : "登錄成功",
  "status_code" : 200,
  "data" : {
    "username" : "admin",
    "id" : 1,
    "password" : "123456",
    "gender" : "男"
  }
}

模型結(jié)構(gòu)

public class BaseResponse<T> {

    private int status_code;
    private String status_msg;
    private T data;


    public int getStatus_code() {
        return status_code;
    }

    public void setStatus_code(int status_code) {
        this.status_code = status_code;
    }

    public String getStatus_msg() {
        return status_msg;
    }

    public void setStatus_msg(String status_msg) {
        this.status_msg = status_msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}

使用Rxjava對象變換

public class BaseResponseFunc<T> implements Func1<BaseResponse<T>, Observable<T>> {


    @Override
    public Observable<T> call(BaseResponse<T> tBaseResponse) {
        //遇到非200錯誤統(tǒng)一處理,將BaseResponse轉(zhuǎn)換成您想要的對象
        if (tBaseResponse.getStatus_code() != 200) {
            return Observable.error(new Throwable(tBaseResponse.getStatus_msg()));
        }else{
            return Observable.just(tBaseResponse.getData());
        }
    }
}

自定義訂閱者

/**
 * 錯誤統(tǒng)一處理
 *
 * Created by wanglj on 16/7/4.
 */

public class ExceptionSubscriber<T> extends Subscriber<T> {

    private SimpleCallback<T> simpleCallback;
    private Application application;

    public ExceptionSubscriber(SimpleCallback simpleCallback, Application application){
        this.simpleCallback = simpleCallback;
        this.application = application;
    }

    @Override
    public void onStart() {
        super.onStart();
        if(simpleCallback != null)
            simpleCallback.onStart();
    }

    @Override
    public void onCompleted() {
        if(simpleCallback != null)
            simpleCallback.onComplete();
    }

    @Override
    public void onError(Throwable e) {
        e.printStackTrace();
        if (e instanceof SocketTimeoutException) {
           Toast.makeText(application, "網(wǎng)絡(luò)中斷,請檢查您的網(wǎng)絡(luò)狀態(tài)", Toast.LENGTH_SHORT).show();
        } else if (e instanceof ConnectException) {
           Toast.makeText(application, "網(wǎng)絡(luò)中斷晚胡,請檢查您的網(wǎng)絡(luò)狀態(tài)", Toast.LENGTH_SHORT).show();
        } else {
           Toast.makeText(application, "error:" + e.getMessage(), Toast.LENGTH_SHORT).show();
        }
        if(simpleCallback != null)
            simpleCallback.onComplete();
    }

    @Override
    public void onNext(T t) {
        if(simpleCallback != null)
            simpleCallback.onNext(t);
    }
}

簡單的回調(diào)模型

public interface SimpleCallback<T> {
    void onStart();
    void onNext(T t);
    void onComplete();
}

presenter層調(diào)用

public void login(String username,String password){
        apiManager.login(username, password, new SimpleCallback<User>() {
            @Override
            public void onStart() {
                loginView.showLoading();
            }

            @Override
            public void onNext(User user) {
                loginView.showUser(user);
            }

            @Override
            public void onComplete() {
                loginView.hideLoading();
            }
        });
    }
  • 對外提供ApiManager以及retrofit的封裝
@Module
public class ApiModule {
    @Provides
    @Singleton
    public OkHttpClient provideOkHttpClient() {
        final OkHttpClient.Builder builder = new OkHttpClient.Builder();
        //添加logo日志打印網(wǎng)絡(luò)請求的攔截器
        if (BuildConfig.DEBUG) {
            HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
            logging.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(logging);
        }

        builder.connectTimeout(60 * 1000, TimeUnit.MILLISECONDS)
                .readTimeout(60 * 1000, TimeUnit.MILLISECONDS);

        return builder.build();
    }

    @Provides
    @Singleton
    public Retrofit provideRestAdapter(OkHttpClient okHttpClient) {
        Retrofit.Builder builder = new Retrofit.Builder();
        builder.client(okHttpClient)
                .baseUrl(ApiService.SERVER_URL)
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create());
        return builder.build();
    }

    @Provides
    @Singleton
    public ApiService provideApiService(Retrofit restAdapter) {
        return restAdapter.create(ApiService.class);
    }

    @Provides
    @Singleton
    public ApiManager provideApiManager(Application application,ApiService githubApiService) {
        return new ApiManager(githubApiService,application);
    }

}
  • 所有的全局共用對象都可以在AppModule里對外提供灵奖,比如PreferencesManager DatabaseManager等等

更高級的用法--dagger2 劃分更細的scope

目前demo示例是將功能模塊直接依賴于整個APP,其實我們可以劃分更細的作用域估盘。使一個對象的生命周期存在于多個功能模塊中瓷患。

比如:項目中登錄成功后,獲取文章列表需要用戶信息遣妥,獲取文章詳情以及文章下的評論列表擅编,又需要當前文章和用戶的信息。那么我們就可以這樣設(shè)計我們的工程架構(gòu)如圖:

scope.png
scope.png

后記

由于寫的比較倉促,架構(gòu)圖中的紅色字體部分還未實現(xiàn)爱态,更細的scope還沒有在demo中體現(xiàn)出來谭贪,如果您對此感興趣,請關(guān)注锦担,后續(xù)會陸續(xù)更新俭识。

由于表達能力有限,可能有些地方解釋的不是很清楚洞渔,歡迎在下方評論套媚,一起討論一起進步~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市痘煤,隨后出現(xiàn)的幾起案子凑阶,更是在濱河造成了極大的恐慌,老刑警劉巖衷快,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宙橱,死亡現(xiàn)場離奇詭異,居然都是意外死亡蘸拔,警方通過查閱死者的電腦和手機师郑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來调窍,“玉大人宝冕,你說我怎么就攤上這事〉巳” “怎么了地梨?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長缔恳。 經(jīng)常有香客問我宝剖,道長,這世上最難降的妖魔是什么歉甚? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任万细,我火速辦了婚禮,結(jié)果婚禮上纸泄,老公的妹妹穿的比我還像新娘赖钞。我一直安慰自己,他們只是感情好聘裁,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布雪营。 她就那樣靜靜地躺著,像睡著了一般衡便。 火紅的嫁衣襯著肌膚如雪卓缰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音征唬,去河邊找鬼捌显。 笑死,一個胖子當著我的面吹牛总寒,可吹牛的內(nèi)容都是我干的扶歪。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼摄闸,長吁一口氣:“原來是場噩夢啊……” “哼善镰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起年枕,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤炫欺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后熏兄,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體品洛,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年摩桶,在試婚紗的時候發(fā)現(xiàn)自己被綠了桥状。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡硝清,死狀恐怖辅斟,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芦拿,我是刑警寧澤士飒,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站蔗崎,受9級特大地震影響酵幕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蚁趁,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望实胸。 院中可真熱鬧他嫡,春花似錦、人聲如沸庐完。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽门躯。三九已至淆党,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背染乌。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工山孔, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荷憋。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓台颠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親勒庄。 傳聞我的和親對象是個殘疾皇子串前,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,527評論 25 707
  • 出了人大,甚至僅僅出了新聞系实蔽,大概也很難再遇到這樣的師長荡碾,關(guān)心你的興趣,鼓勵你去做所謂的“無用之學(xué)”局装,鼓勵你做一個...
    請叫我黃姑娘閱讀 210評論 0 0
  • 最近在寫一個請假功能,要求選擇日期,然后我就用了pickView來做,在別的版本手機上選擇是沒問題的,但是到了版本...
    沒毛毛的大腿閱讀 268評論 0 0
  • 曾經(jīng)夢想著做一名作家贼邓,因為作家是心靈的記錄著阶冈,既探索者自己,也可以幫助他人塑径,喜歡文字輕輕的流過我的血液女坑,享受著與中...
    倔強兔子dream2016閱讀 293評論 0 0
  • 今年的日子,有一大半都泡在健身房统舀。 歙縣的汪教練匆骗,天津的酒店頂樓,民大的地下室誉简,cumbaya的快速路旁碉就,都留下了...
    胡安是娟娟閱讀 191評論 0 0