編輯:關於Android編程
前言
寫Android:如何編寫“萬能”的Activity的這篇文章到現在已經好久了,但是由於最近事情較多,寫重構篇的計劃就一直被無情的耽擱下來了,借這幾天還算有點空余時間,把自己這樁心事了解下。
其實大家也知道Android:如何編寫“萬能”的Activity的這篇文章只是個引子,其實我真正想引出的是mvp設計模式,因為最近自己最近在用mvp做項目,自己對mvp有一些感悟,因此我將用mvp進行“萬能”activity的重構。
同時也有一些朋友與我交流mvp,他們會被mvp中的m,v,p都應該放什麼邏輯而困惑?會被mvp中的m應該怎麼寫而困惑?會被一個界面,怎麼按照mvp來進行重構感到困惑?會被listview的adapter應該放在m層還是p層困惑?
我希望通過本文的講解能幫助大家對mvp有一個更深的了解,現在進入主題內容。
正文內容
本文內容分為2部分:
第一部分會深入了解mvp到底是什麼,它的好處等知識。第二部分會講解如何利用mvp來重構“萬能”Activity。
帶你了解mvp
任何軟件都是以數據為中心,為了能與用戶進行交互,就需要提供界面支持用戶對數據進行增刪改查操作。
不管是mvc,mvp還是mvvm始終都在做一件事情:怎麼樣能更好的解決數據與界面之間的關系,以達到數據與界面之間的耦合更低,代碼的復用性更高,代碼的可測性更好。
本文的重點是講解mvp,因此讓我們開始了解下mvp是怎麼組織數據與界面之間的關系的。我們先從mvp的結構圖說起。
mvp的面容
網上有些關於mvp的結構圖基本是以下樣子
mvp結構圖.png
我覺得這張圖是有問題的,問題在於presenter把請求轉交給model,model應該把處理結果返回給presenter,這張圖是沒有反映這個過程的。
正確的mvp的結構圖是這樣子的
mvp結構
我們先看下能從這張圖中得到哪些信息?
看了mvp的整體結構圖,我們以從底層到上層的順序依次來介紹model,presenter,view。
model
先說下一些關於model的錯誤理解:
關於model的正確理解我們會在文中看到。
數據加工處理廠
通過應用mvp後的感受,我個人的感覺model是最難寫的一層,並且也是最難懂的,因為model是整個應用或界面的數據加工處理廠,所謂數據加工廠就是對數據的獲取,數據的解析,數據的存儲,數據的分發,數據的增刪改查等操作。意思就是凡是涉及到數據操作都是在model進行的,所以model不僅僅只是實體類的集合,同時還包含關於數據的各種處理操作。
三種數據源
數據的數據源有三種:內存,磁盤(文件或數據庫等),網絡。為了提升app的性能,有必要把經常訪問的數據臨時存入內存中;同時也為了提升app性能和為用戶省流量省電,有必要把數據存入磁盤中;還有的數據是有必要從網絡讀取的。三個數據源不一定同時存在,比如不與網絡交互的app,不存在網絡數據源。所以凡是涉及到關於數據發生於三個數據源加工處理的操作的代碼都要放在model中。
model為上層提供的服務
model從黑盒的角度來看為上層(指依賴於model的層比如present)提供的服務無非就2種:model為上層提供數據,model處理上層傳遞的數據
model為上層提供數據
上層會從model中去數據,那model會從三數據源中取數據,取的順序是
上面的取數據過程是最簡單的情況,復雜些還會涉及到從內存或磁盤中取到的數據是否過期,過期的話就應該從網絡獲取。從網絡取得數據後需要把內存或磁盤的數據更新。
model處理上層傳遞的數據
model接收到上層傳遞的數據後,model會依次把數據扔給三個數據源去處理,有可能三個數據源都會處理數據,有可能只是其中一個處理,model會把處理的結果返回。
所以model會把解析好的數據提供給上層,上層對於數據的來源完全是透明的,上層完全不需要關心數據到底是來自內存,還是磁盤甚至是網絡。同理上層只需要的把數據扔給model,上層唯一做的事情就是愉快的等待處理結果。
tip
mvc中的model是要和view進行交互的,而mvp中的model不會知道任何view的細節。
model中的所有操作都發生於普通線程。
關於model的介紹先到此,我們在來看下presenter。
presenter
presenter翻譯成漢語的意思是主持人,提出者。從它的意思可以看出它有控制全場的作用。首先presenter是處於mvp的中間層,在view和model中起一個承上啟下的作用。presenter會把view交給自己的命令進行一定的校驗等操作交給model處理,會把model處理的結果交給view。
presenter封裝業務
presenter不僅起一個橋梁的作用,它還會把業務邏輯代碼給包攬下來。這樣就可以減輕Activity的負擔了,讓Activity全心全意做它的view工作。那估計就有朋友犯迷糊了,哪些代碼屬於業務邏輯呢?比如一些校驗代碼。或者可以這樣想只要是不屬於view和model的代碼基本都可以放在presenter中。
presenter負責刷新view
mvc或以前的關於view的寫法一般都是這樣,view在接收到數據後,自己來進行view的刷新或其他操作。但是mvp中presenter負責對view進行刷新,比如從model獲取的數據,presenter會根據獲取的數據成功與否來通知view應該是顯示成功界面還是失敗界面。這樣就讓Activity變的更輕了,變成了聽別人指揮的傻白甜了。這時候的presenter就有點主持人,掌控者的味道了。
presenter持有的線程
Android中view的操作需要在ui線程裡執行,其他耗時操作需要在普通線程執行。presenter會持有這2種線程:ui線程,普通線程。刷新view時,它切換為ui線程進行刷新,從model取數據切換為普通線程。假如使用rxjava的話,就特別簡單了關於線程切換的事情。
tip
presenter從model中獲取的數據就是解析好的數據,不需要出現解析數據的代碼。
接著我們來看下view。
view
view層就很好理解了,就是用戶直接看到的界面,mvp中的view是很省心的,比如更新view,接收數據。這些操作它都不需要操心,也不需要知道數據到底來自哪裡,給我啥我顯示啥就可以了。
一個view可以同時擁有多個presenter,也可以只有一個presenter。
Android中的Activity,Fragment在mvp中是作為view來使用的,這些Activity,Fragment的責任就小了,只關心界面相關的事情足矣。
各種Adapter是放在view層的。
總結
我們初步認識了mvp,mvp中的model,present,view到底是什麼,他們之間的關系是什麼樣的,這只是初步認識mvp,關於mvp中還有很多細節需要介紹,比如android clean architecture 中model和presenter之間多了一層interactor,多的這層interactor是用來做什麼的,model層是怎麼架構的。google mvpmodel層要比android clean architecture 簡單等,希望能在我後面的章節看到相關關於每層的詳細介紹。我們開始進入我們的重構"萬能"Activity的部分。
使用mvp設計模式對"萬能"Activity進行重構
回憶下“萬能”Activity的樣子
我在上篇文章的“萬能”的LoginActivity基礎上增加了登錄對話框的功能,“萬能”LoginActivity的代碼如下:
public LoginActivity extends Activity{ private EditText mUserNameView, mPasswordView; private Button mLoginView; public void initViews(){ ....... 各種findViewById.....代碼 //給登陸按鈕加監聽器 mLoginView.OnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String userName = mUserNameView.getText(); String password = mPasswordView.getText(); //驗證用戶輸入的密碼是否合法 if(!validate(userName) || !validate(password)){ 告訴用戶輸入的用戶名或密碼不合法 } else{ //開始登陸 login(userName,password); } } }); } //登陸方法,用偽代碼來寫下網絡請求 private void login(String userName,String password){ //增加登錄進度對話框給用戶友好用戶體驗 顯示登錄進度對話框... HttpClient.getInstance().login(userName,password, new ResponseListener(){ public void failed(Failed failed){ 把登錄進度對話框消失... 做失敗相關的處理工作,比如給用戶提示 把密碼輸入框清空,還比如登陸次數限制等 } public void success(Response response){ 把登錄進度對話框消失... 做成功相關的處理工作 //暫且把用戶信息的類叫做UserInfo,從json中解析數據,假設response.getContent()存在 String jsonContent = response.getContent(); JsonObject jsonObject = new JsonObject(jsonContent); UserInfo userInfo = new UserInfo(); userInfo.name = jsonObject.optString("name"); userInfo.userId = jsonObject.optString("userId"); 其他字段的解析...... //保存userInfo信息到數據表中,假設userDatabase已經存在 userDatabase.save(userInfo); 跳到app的主頁 } }); } //驗證給定的字符串是否合法,true 合法,false 不合法 private boolean validate(String str){ } }
我們回憶了“萬能”LoginActivity的代碼後,開始重構。
開始重構
model
在使用mvp時,我一般有個習慣就是首先從model->presenter->view的順序寫代碼,所以重構“萬能”LoginActivity也先從model開始。前半部分關於model介紹過,model從黑盒的角度來說只有2個功能:一個是輸出數據,一個是輸入數據。因此登錄中presenter只需要把賬號,密碼交給model,presenter唯一做的事情就是監聽登錄狀態即可。model會把presenter傳遞的賬號,密碼交給服務器,model在把服務器返回的數據進行解析,存儲在磁盤或內存中,把解析好的數據傳遞給presenter。那我們看下偽代碼:
//管理登錄的類,它是單例的,這就不寫單例方法了 public class LoginManager{ //登錄的監聽器 public static interface LoginListener{ //登錄成功 void onSuccessLogin(UserEntity user); //登錄失敗 void onFailedLogin(Failed failed); } //登錄方法 public void login(String name,String password,final LoginListener loginListener){ //假設HttpClient是一個請求網絡服務的類 HttpClient.getInstance().login(userName,password, new ResponseListener(){ public void failed(Failed failed){ loginListener.onFailedLogin(failed); } public void success(Response response){ //假設UserParse類已經存在,主要用來從response中解析UserEntity UserEntity userEntity = UserParse(response.getContent()); //假設userDatabase是數據庫存儲類 userDatabase.store(userEntity); //還可以把userEntity存入內存中,這得根據業務需求進行處理 loginListener.onSuccessLogin(userEntity); } }); } }
登錄的model層我們沒有做的那麼復雜,比如把服務器返回的用戶信息存儲在內存中,把服務器返回的token存儲在磁盤中,實現自動登錄功能等,本例子只是一個特別簡單的登錄功能,實際應用中登錄需要考慮很多的東西,登錄的modle層到此重構完畢。
presenter
上文中提到過presenter,presenter起連接view與model的作用,presenter封裝業務作用,presenter有負責刷新view的作用。
我們梳理下presenter都應該包含哪些功能:
那讓我們開始寫代碼,presenter層的類組織結構我是參照google mvp的presenter類組織結構來進行的,因為我認為google mvp presenter類結構更清晰,看下偽代碼:
//登錄的條約接口,分別定義了登錄view的一些方法,和登錄presenter的一些方法 public interface LoginContract{ //需要view層來實現的登錄view接口,IView是所有view的基類 interface ILoginView extends IView{ void onShowSuccessLoginView(UserInfo userInfo); void onShowFailedLoginView(int failed); void showLoginingView(); void dissLoginingView(); } //定義了登錄presenter的一些方法,IPresenter是所有Presenter的基類 interface ILoginPresenter extends IPresenter<ILoginView>{ void login(String name,String password); } } public interface IView{ void initView(); } //IPresenter提供了一些基礎方法,其實這些方法是對應Activity或Fragment的生命周期方法 public interface IPresenter<V extends IVew>{ void onStop(); void onResume(); void onDestroy(); void onPause(); void onStart(); void init(V view); } //登錄的presenter public class LoginPresenter implements ILoginPresenter{ private ILoginView mLoginView; private LoginManager mLoginManager = LoginManager.getInstance(); public void init(ILoginView loginView){ mLoginView = loginView; mLoginView.initView(); } public void login(String name,String password){ //驗證name,password的合法性, if(validate(name) && validate(password)){ //假設NormalThread.exe方法可以讓操作在普通線程裡執行 mLoginView.showLoginingView(); NormalThread.exe(new Runnable(){ public void run(){ mLoginManager.login(name,password, new LoginListener(){ public void onSuccessLogin(UserEntity userEntity){ //UserMapper類,負責把底層的UserEntity轉化為view層使用的UserInfo UserInfo userInfo = UserMapper.map(userEntity); //下面的代碼在ui線程中執行,這就不寫具體的實現了 mLoginView.onShowSuccessLoginView(userInfo); mLoginView.dissLoginingView(); } public void onFailedLogin(Failed failed){ //下面的代碼在ui線程中執行,這就不寫具體的實現了 mLoginView.onShowFailedLoginView(failed.failedState); mLoginView.dissLoginingView(); } }); } } }else{ //假設1代表賬號,密碼不合法 mLoginView.onShowFailedLoginView(1); } } }
以上登錄的Presenter層的偽代碼都是關鍵代碼,讓我們看下以上代碼都做了什麼?
view
view層就很簡單了,只是需要把基礎設施建立好,直接看偽代碼:
public abstract class BaseActivity extends FragmentActivity{ private Set<IPresenter> mAllPresenters = new HashSet<IPresenter>(1); /** * 獲取layout的id,具體由子類實現 * @return */ protected abstract int getLayoutResId(); /** *需要子類來實現,獲取子類的IPresenter,一個activity有可能有多個IPresenter */ protected abstract IPresenter[] getPresenters(); //初始化presenters, protected abstract void onInitPresenters(); /** * 從intent中解析數據,具體子類來實現 * @param argIntent */ protected void parseArgumentsFromIntent(Intent argIntent){ } private void addPresenters(){ IPresenter[] presenters = getPresenters(); if(presenters != null){ for(int i = 0; i < presenters.length; i++){ mAllPresenters.add(presneters[i]); } } } @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(getLayoutResId()); if(getIntent() != null){ parseArgumentsFromIntent(getIntent()); } addPresenters(); onInitPresents(); } @Override protected void onResume() { super.onResume(); //依次調用IPresenter的onResume方法 for (IPresenter presenter:mAllPresenters ) { if(presenter != null){ presenter.onResume(); } } } ...其他生命周期方法也是類似,調用IPresenter中相應的生命周期方法... }
基礎設施已經ok了,這時候我們就該重構"萬能“LoginActivity了。
public class LoginActivity extends BaseActivity implements LoginConstract.ILoginView{ private LoginPresenter mLoginPresenter = new LoginPresenter(); protected int getLayoutResId(){ return R.layout.activity_login; } protected IPresenter[] getPresenters(){ return new IPresneter[]{ mLoginPresenter}; } //初始化presenters, protected void onInitPresenters(){ mLoginPresenter.init(this); } public void initView(){ ...初始化view的代碼... // mLoginButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mLoginPresenter.login(name,password); } }); } public void onShowSuccessLoginView(UserInfo userInfo){ ....顯示登錄成功界面.... } public void onShowFailedLoginView(int failed){ ...顯示登錄失敗界面... } public void showLoginingView(){ ...顯示登錄進度條對話框... } public void dissLoginingView(){ ...消失登錄進度條對話框... } }
我們著重說下基礎設施BaseActivity:
因為一個Activity是有可能包含多個Presenter的,所以需要在BaseActivity中是有必要把這些Presenter收集起來。
需要在BaseActivity的生命周期方法裡面調用每個Presenter的相應周期方法。
重構以後的LoginActivity是不是很清爽,只保留與view相關的邏輯。
小結重構後的類結構
重構“萬能”LoginActivity到此結束,我們小結下重構後的類結構:
重構感悟
疑惑
估計會有細心的朋友發現model層是LoginManager而不是像android clean architecture 中model層使用了respository,我個人覺得model層也沒必要這麼嚴格的按respository的架構方式來組織類結構,因為本例中登錄功能實在是太簡單了,所以就用最簡單的一個LoginManager類來供上層調用。
優缺點
使用mvp重構“萬能”Activity以後,帶來了以下好處:
但同時也帶來了一些缺點,比如創建的類多了很多,管理這些類的成本會增加。但是萬事萬物都有兩面性,就看利與弊的大小了。我個人覺得mvp的利肯定是大於弊的,所以有必要采用這種架構來設計你的app。
提高生產效率
以上偽代碼中我沒有使用rxjava,dagger2,假如把它們應用於mvp中會讓你事半功倍,rxjava可以讓你在寫presenter層和model層時,可以讓presenter與model交互更簡單,可以是model層變的尤為的簡單比如從三大數據源取數據操作,我們自己用代碼實現是可以的但是畢竟要花很多時間,但是用rxjava的一些操作符很容易做到。dagger2可以更好的幫助你進行依賴注入, 還有鼎鼎有名的retrofit也是可以提高效率的。
總結
本文我們了解了mvp以及每一層,以及使用mvp來重構“萬能”Activity,其實每一層需要注意的東西還有很多,比如model層是最難寫的一層。
以上就是本文的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,同時也希望多多支持本站!
直接上效果圖 功能特色: 1、可以設置刮開後顯示文字或圖片 2、可以統計已刮開區域所占百分比 Demo下載地址:RubberDem
GridView網格視圖,網格視圖組件,九宮圖顯示數據表格(一種控件)ScrollView滾動視圖是一個單一容器,只能包含一個組件。ViewPager左右滑動SlideM
本次分兩個大方向去講解Web Api,1、如何實現Web Api?2、如何Android端如何調用Web Api?對於Web Api是什麼?有什麼優缺點?為什麼用WebA
我們來講一下自定義組合控件,相信大家也接觸過自定義組合控件吧,話不多說,直接干(哈~哈~):大家看到這個覺得這不是很簡單的嗎,這不就是寫個布局文件就搞定嘛,沒錯,確實直接