Android系統(24)---Android應用架構

前言


android架構多是論壇討論最多的話題了,mvc mvp和mvvm不絕於耳,後面又有模塊化和插件化。對此,關於哪一種架構更好的爭論從未中止。php

個人觀點:脫離實際項目比較這些模式優劣毫無心義,各類模式都有優勢和缺點,沒有好壞之分。越高級的架構實現起來越複雜,須要更多的學習成本更多的人力,因此說技術選型關鍵是在你本身項目的特色,團隊的水平,資源的配備,開發時間的限制,這些纔是重點!可是很多團隊本末倒置,把mvvm往本身的項目硬套。html

下面我從兩大塊講下我理解的Android架構:代碼層面,主要是MVC和MVP的理解。項目層面,主要是怎麼搭建整個項目,怎麼劃分模塊。java


正文


先上結論:
android

  • MVC:Model-View-Controller,經典模式,很容易理解,主要缺點有兩個:數據庫

    •  View對Model的依賴,會致使View也包含了業務邏輯;緩存

    • Controller會變得很厚很複雜。網絡

  • MVP:Model-View-Presenter,MVC的一個演變模式,將Controller換成了Presenter,主要爲了解決上述第一個缺點,將View和Model解耦,不過第二個缺點依然沒有解決。架構

  • MVVM:Model-View-ViewModel,是對MVP的一個優化模式,採用了雙向綁定:View的變更,自動反映在ViewModel,反之亦然。mvc

MVC

簡單的說:咱們平時寫的Demo都是MVC,controller就是咱們的activity,model(數據提供者)就是讀取數據庫,網絡請求這些咱們通常有專門的類處理,View通常用自定義控件。app

但這一切,只是看起來很美。

想象實際開發中,咱們的activity代碼實際上是愈來愈多,model和controller根本沒有分離,控件也須要關係數據和業務。

640?wx_fmt=png

image.png

因此說,MVC的真實存在是MC(V),Model和Controller根本沒辦法分開,而且數據和View嚴重耦合。這就是它的問題。舉個簡單例子 :獲取天氣數據展現在界面上

640?wx_fmt=png

image.png

  • Model層

public interface WeatherModel { 
    void getWeather(String cityNumber, OnWeatherListener listener); 
} 
................ 
public class WeatherModelImpl implements WeatherModel { 
        @Override 
    public void getWeather(String cityNumber, final OnWeatherListener listener) { 
        /*數據層操做*/ 
        VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html, 
                Weather.class, new Response.Listener<weather>() { 
                    @Override 
                    public void onResponse(Weather weather) { 
                        if (weather != null) { 
                            listener.onSuccess(weather); 
                        } else { 
                            listener.onError(); 
                        } 
                    } 
                }, new Response.ErrorListener() { 
                    @Override 
                    public void onErrorResponse(VolleyError error) { 
                        listener.onError(); 
                    } 
                }); 
    } 
}
  • Controllor(View)層

public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener { 
    private WeatherModel weatherModel; 
    private EditText cityNOInput; 
    private TextView city; 
    ... 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
        weatherModel = new WeatherModelImpl(); 
        initView(); 
    } 
    //初始化View 
    private void initView() { 
        cityNOInput = findView(R.id.et_city_no); 
        city = findView(R.id.tv_city); 
        ... 
        findView(R.id.btn_go).setOnClickListener(this); 
    } 
    //顯示結果 
    public void displayResult(Weather weather) { 
        WeatherInfo weatherInfo = weather.getWeatherinfo(); 
        city.setText(weatherInfo.getCity()); 
        ... 
    } 
    @Override 
    public void onClick(View v) { 
        switch (v.getId()) { 
            case R.id.btn_go: 
                weatherModel.getWeather(cityNOInput.getText().toString().trim(), this); 
                break; 
        } 
    } 
    @Override 
    public void onSuccess(Weather weather) { 
        displayResult(weather); 
    } 
    @Override 
    public void onError() { 
        Toast.makeText(this, 獲取天氣信息失敗, Toast.LENGTH_SHORT).show(); 
    } 
    private T findView(int id) { 
        return (T) findViewById(id); 
    } 
}

簡單分析下這個例子:

  • activity裏面的控件必須關心業務和數據,才能知道本身怎麼展現。換句話說,咱們很難讓兩我的在不互相溝通的狀況下,一人負責獲取數據,一人負責展現UI,而後完成這個功能。

  • 因此的邏輯都在activity裏面。

完美的體現了MVC的兩大缺點,下面看看MVP怎麼解決第一個缺點的

MVP

640?wx_fmt=png

image.png

看上圖能夠看出,從MVC中View被拆成了Presenter和View,真正實現了邏輯處理和View的分離。下面寫一個實例:模擬一個登陸界面,輸入用戶名和密碼,能夠登陸以及清除密碼

  • Model層

/** 定義業務接口 */ 
public interface IUserBiz { 
    public void login(String username, String password, OnLoginListener loginListener); 
} 
/** 結果回調接口 */ 
public interface OnLoginListener { 
    void loginSuccess(User user); 
    void loginFailed(); 
} 
/** 具體Model的實現 */ 
public class UserBiz implements IUserBiz { 
    @Override 
    public void login(final String username, final String password, final OnLoginListener loginListener)    { 
        //模擬子線程耗時操做 
        new Thread() 
        { 
            @Override 
            public void run()            { 
                try 
                { 
                    Thread.sleep(2000); 
                } catch (InterruptedException e) 
                { 
                    e.printStackTrace(); 
                } 
                //模擬登陸成功 
                if ("zhy".equals(username) && "123".equals(password)) 
                { 
                    User user = new User(); 
                    user.setUsername(username); 
                    user.setPassword(password); 
                    loginListener.loginSuccess(user); 
                } else 
                { 
                    loginListener.loginFailed(); 
                } 
            } 
        }.start(); 
    } 
}
  • View

上面說到View層是以接口的形式定義,咱們不關心數據,不關心邏輯處理!只關心和用戶的交互,那麼這個登陸界面應該有的操做就是(把這個界面想成一個容器,有輸入和輸出)。

獲取用戶名,獲取密碼,現實進度條,隱藏進度條,跳轉到其餘界面,展現失敗dialog,清除用戶名,清除密碼。接下來定義接口:

public interface IUserLoginView  {  
    String getUserName();  
    String getPassword();  
    void clearUserName();  
    void clearPassword();  
    void showLoading();  
    void hideLoading();  
    void toMainActivity(User user);  
    void showFailedError();  
}

而後Activity實現這個這個接口:

public class UserLoginActivity extends ActionBarActivity implements IUserLoginView { 
    private EditText mEtUsername, mEtPassword; 
    private Button mBtnLogin, mBtnClear; 
    private ProgressBar mPbLoading; 
    private UserLoginPresenter mUserLoginPresenter = new UserLoginPresenter(this); 
    @Override 
    protected void onCreate(Bundle savedInstanceState)    { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_user_login); 
        initViews(); 
    } 
    private void initViews()    { 
        mEtUsername = (EditText) findViewById(R.id.id_et_username); 
        mEtPassword = (EditText) findViewById(R.id.id_et_password); 
        mBtnClear = (Button) findViewById(R.id.id_btn_clear); 
        mBtnLogin = (Button) findViewById(R.id.id_btn_login); 
        mPbLoading = (ProgressBar) findViewById(R.id.id_pb_loading); 
        mBtnLogin.setOnClickListener(new View.OnClickListener() 
        { 
            @Override 
            public void onClick(View v)            { 
                mUserLoginPresenter.login(); 
            } 
        }); 
        mBtnClear.setOnClickListener(new View.OnClickListener() 
        { 
            @Override 
            public void onClick(View v)            { 
                mUserLoginPresenter.clear(); 
            } 
        }); 
    } 
    @Override 
    public String getUserName()    { 
        return mEtUsername.getText().toString(); 
    } 
    @Override 
    public String getPassword()    { 
        return mEtPassword.getText().toString(); 
    } 
    @Override 
    public void clearUserName()    { 
        mEtUsername.setText(""); 
    } 
    @Override 
    public void clearPassword()    { 
        mEtPassword.setText(""); 
    } 
    @Override 
    public void showLoading()    { 
        mPbLoading.setVisibility(View.VISIBLE); 
    } 
    @Override 
    public void hideLoading()    { 
        mPbLoading.setVisibility(View.GONE); 
    } 
    @Override 
    public void toMainActivity(User user)    { 
        Toast.makeText(this, user.getUsername() + 
                " login success , to MainActivity", Toast.LENGTH_SHORT).show(); 
    } 
    @Override 
    public void showFailedError()    { 
        Toast.makeText(this, 
                "login failed", Toast.LENGTH_SHORT).show(); 
    } 
}

  • Presenter

Presenter的做用就是從View層獲取用戶的輸入,傳遞到Model層進行處理,而後回調給View層,輸出給用戶!

public class UserLoginPresenter { 
    private IUserBiz userBiz; 
    private IUserLoginView userLoginView; 
    private Handler mHandler = new Handler(); 
//Presenter必需要能拿到View和Model的實現類 
    public UserLoginPresenter(IUserLoginView userLoginView)    { 
        this.userLoginView = userLoginView; 
        this.userBiz = new UserBiz(); 
    } 
    public void login()    { 
        userLoginView.showLoading(); 
        userBiz.login(userLoginView.getUserName(), userLoginView.getPassword(), new OnLoginListener() 
        { 
            @Override 
            public void loginSuccess(final User user)            { 
                //須要在UI線程執行 
                mHandler.post(new Runnable() 
                { 
                    @Override 
                    public void run()                    { 
                        userLoginView.toMainActivity(user); 
                        userLoginView.hideLoading(); 
                    } 
                }); 
            } 
            @Override 
            public void loginFailed()            { 
                //須要在UI線程執行 
                mHandler.post(new Runnable() 
                { 
                    @Override 
                    public void run()                    { 
                        userLoginView.showFailedError(); 
                        userLoginView.hideLoading(); 
                    } 
                }); 
            } 
        }); 
    } 
    public void clear()    { 
        userLoginView.clearUserName(); 
        userLoginView.clearPassword(); 
    } 
}

分析下這個例子:

  • 咱們有了IUserLoginView 這個接口(協議),activity裏面的控件根本不須要關心數據,只要實現這個接口在每一個方法中「循序漸進」的展現UI就好了。換句話說,咱們讓兩我的一塊兒開發這個功能,一人要處理數據而且制定接口(協議),另外一人直接用activity實現這個接口,閉着眼睛就能夠在每一個回調裏展現UI,合做很愉快。

  • MVP成功解決了MVC的第一個缺點,可是邏輯處理仍是雜糅在Activity。

MVC到MVP簡單說,就是增長了一個接口下降一層耦合。那麼,用樣的MVP到MVVM就是再加一個接口唄。實際項目我建議用MVP模式,MVVM仍是複雜了對於中小型項目有點過分設計,這裏就不展開講。

模塊化

640?wx_fmt=png

image.png

上圖是一個項目常見的架構方式

  • 最底層是基礎庫,放置與業務無關的模塊:好比基礎網絡請求,圖片壓縮等等,能夠按需分爲邏輯模塊,通用UI模塊和第三方庫。(建議採用獨立的svn分支)

  • 中間層是通用業務層,放置公司多個android項目的通用業務模塊(和業務相關的),好比登陸流程,文件上傳/下載等。

  • 最上層就是應用層了,好比公司有三個android項目:LbBoss,BV和BVHD。咱們還能夠針對類似的項目再抽取通用層(好比這裏的BV和BV PAD版,通用層爲BVCommon)。

新建一個app,咱們每每有兩種模塊劃分方法:

  • 按照類型劃分

640?wx_fmt=png

image.png

  • 按照業務劃分

每個包都是一個業務模塊,每一個模塊下再按照類型來分。

  • 怎麼選

我建議中小型的新項目按照類型比較好,由於開始代碼量很少按照業務來分不切實際,一個包只放幾個文件?? 何況前期業務不穩定,等到開發中期業務定型了,再進行重構難度也不大。

上面講的模塊劃分既不屬於模塊化也不屬於插件化,僅僅是一個簡單package結構不一樣而已,app仍是一個app並無產生什麼變化。一般講的模塊化,是指把業務劃分爲不一樣的moduler(類型是library),每一個moduler之間都不依賴,app(類型是application)只是一個空殼依賴全部的moduler。

640?wx_fmt=png

image.png

每一個紅色箭頭都是一個業務模塊,紅色框是咱們的app裏面只包含簡單的業務:自定義Application,入口Activity,build.gradle編譯打包配置。看下項目的依賴關係:

640?wx_fmt=png

image.png

這樣架構後,帶來最大的不一樣就是:不一樣業務模塊徹底分離,好處就是不一樣模塊的開發絕對不會互相耦合了,由於你在模塊A 根本訪問不到模塊B的API。此時模塊間通訊急需解決,Intent隱式跳轉能夠處理部分Activity的跳轉,但真正的業務場景遠不止兩個界面跳一跳。你以前封裝的業務通用方法,工具類,數據緩存如今其餘模塊都拿不到了,本原本能夠複用的控件,fragment都不能共享,而這些都是和業務耦合沒辦法拿到底層基礎庫。

模塊間通訊

針對上面問題有兩個解決辦法,根據本身項目實際狀況,若是項目的前期搭建已經很優秀,有完善的基礎庫,不一樣模塊間的通訊不是不少,能夠本身實現。若是項目比較龐大,不一樣業務間頻繁調用建議使用阿里巴巴的開源庫。

本身實現

首先每一個moduler有個目錄叫include,裏面有三個類,此處以一個bbs論壇模塊爲例說明,

  • IBBSNotify:裏面是一堆interface,做用是該模塊對外的回調,只能被動被觸發。

  • IBBService:裏面是一堆interface,做用是對外暴露的方法,讓別的模塊來主動調,好比enterBbsActivity 

  • IBBSServiceImpl:很明顯是IBBService的實現,好比enterBbsActivity就是具體怎麼跳轉到論壇界面,傳遞什麼數據。

640?wx_fmt=png

image.png

每一個模塊方法和回調都有了,in和out都具有了,別的模塊怎麼使用呢?就該app該上場了,app不能只是一個殼裏面要定義一個ModulerManager implements 全部模塊的對外interface,做爲每一個模塊的中轉站,A模塊告訴ModulerManager我想跳轉到論壇模塊,接着ModulerManager調用IBBService.enterBbsActivity,IBBSServiceImpl是IBBService的具體實現(多態)而後調用IBBSServiceImpl.enterBbsActivity跳轉到BBS界面。

通訊是解決了,其實踩坑纔剛剛開始:

  • 這裏的app是咱們新建的,那麼以前項目的app模塊要降爲library:

apply plugin: 'com.android.library'

app的build.gradle配置:

apply plugin: 'com.android.application'

性質發生巨大變化。裏面的自定義application,build.gradle,代碼混淆配置等所有移到app

  • R.java在Lib類型的moduler中不是final的,全部switch case語句所有替換成if else

  • 必定要再建一個common模塊,放置通用數據,緩存等

  • 還有不少通用功能,例如分享,推送,儘可能剝離業務放到common

  • 其餘與項目相關的細節


結語


插件化其實最後發佈的產品也是一個apk,只不過大小能夠控制(能夠隨意去掉某些模塊),支持用戶動態加載子apk。所以,插件化就是動態加載apk。有人說我用intent隱式能夠直接跳轉到另外一個apk啊,幹嗎還要插件化。

實際上是兩碼事,intent只是指定一個Activity跳過去,後面的交互完成不受你控制,2個apk也是運行在獨立的進程數據沒法共享。而插件化可讓兩個apk運行在一個進程,能夠徹底像同一個apk同樣開發。不過,我以爲插件化只適合須要多部門並行開發的那種,好比支付寶這種超級app,通常的app開發除非特殊須要,不然用不到。

插件化也有成熟的框架,在此不詳細說了。另外,每一個人的習慣不同,組件化,模塊化在我看來差很少,不必糾結兩個名詞。

相關文章
相關標籤/搜索