編輯:關於Android編程
MVP模式是MVC模式在Android上的一種變體,要介紹MVP就得先介紹MVC。在MVC模式中,Activity應該屬於view這一層,而在實際開發中,它既承擔了view層,又包含了一些controller的東西。這對於開發與維護來說,都是不太友好的,耦合度太高。現在,把Activity中的View和Controller抽離出來就變成了View和Presenter,這就是MVP模式。
MVC模式的結構分為三部分:實體層的Mode,視圖層的View,控制層的Controller。
例如,View層接受用戶的輸入,然後通過Controller修改對應的model實例,同時,當Model實例的數據發生變化的時候,需要修改對應的UI界面,可以通過Controller達到更新界面的目的。當前,View層也可以直接更新Model數據,而不用通過Controller,這樣對於一些簡單的數據更新 小項目而言,會方便許多。
舉個簡單的例子,現在要實現一個飄雪的動態壁紙,可以給雪花定義一個實體類Snow,裡面存在XY軸的坐標,View層就是SurfaceView或其它師徒,為了實現雪花飄的效果,可以啟動一個後台線程,在線程裡不同更新snow實例裡的坐標值,這部分就是controller的工作了,controller裡還要定時更新SurfaceView上面的雪花。更進一步的話,可以在SurfaceView上監聽用戶的點擊,如果用戶點擊,只通過Controller對觸摸點周圍的Snow的坐標值進行調整,從而實現雪花在用戶點擊後彈開等效果。
在Android項目中,activity和fragment占據了大部分的開發工作,mvp模式就是專門為優化activity和fragment的代碼而產生的。
按照MVC的分層,activity和fragment(後面只說activity)應該屬於View層,用戶展示UI界面,接收用戶的輸入以及生命周期相關的工作。以登錄功能為例,如果把輸入 輸出以及具體實現登錄操作的代碼,都寫在一個Activity中的話,那這個Activity代碼將會顯得非常臃腫,稍微復雜一些的頁面,代碼量分分鐘就達到了上千行,並且這些功能不利於擴展復用,閱讀和維護起來也是相當頭疼的一件事。再讓我們看一下MVP模式的結構圖:
MVP模式的核心思想就是把Activity中的UI邏輯抽象成view接口,把Controller相關的業務邏輯抽象成presenter接口,Model還是原來的Model。
相信很多人閱讀代碼的時候,都是從Activity開始的,對著一個1000+行代碼的Activity,看了都覺得難受。
使用MVP之後,Activity就能瘦身許多了,基本上只有FindView、SetListener以及Init的代碼。其他的就是對Presenter的調用,還有對View接口的實現。這種情形下閱讀代碼就容易多了,而且你只要看Presenter的接口,就能明白這個模塊都有哪些業務,很快就能定位到具體代碼。Activity變得容易看懂,容易維護,以後要調整業務、刪減功能也就變得簡單許多。
一般單元測試都是用來測試某些新加的業務邏輯有沒有問題,如果采用傳統的代碼風格(習慣性上叫做MV模式,少了P),我們可能要先在Activity裡寫一段測試代碼,測試完了再把測試代碼刪掉換成正式代碼,這時如果發現業務有問題又得換回測試代碼,咦,測試代碼已經刪掉了!好吧重新寫吧……
MVP中,由於業務邏輯都在Presenter裡,我們完全可以寫一個PresenterTest的實現類繼承Presenter的接口,現在只要在Activity裡把Presenter的創建換成PresenterTest,就能進行單元測試了,測試完再換回來即可。萬一發現還得進行測試,那就再換成PresenterTest吧。
Android APP 發生OOM的最大原因就是出現內存洩露造成APP的內存不夠用,而造成內存洩露的兩大原因之一就是Activity洩露(Activity Leak)(另一個原因是Bitmap洩露(Bitmap Leak))。
Java一個強大的功能就是其虛擬機的內存回收機制,這個功能使得Java用戶在設計代碼的時候,不用像C++用戶那樣考慮對象的回收問題。然而,Java用戶總是喜歡隨便寫一大堆對象,然後幻想著虛擬機能幫他們處理好內存的回收工作。可是虛擬機在回收內存的時候,只會回收那些沒有被引用的對象,被引用著的對象因為還可能會被調用,所以不能回收。
Activity是有生命周期的,用戶隨時可能切換Activity,當APP的內存不夠用的時候,系統會回收處於後台的Activity的資源以避免OOM。
采用傳統的MV模式,一大堆異步任務和對UI的操作都放在Activity裡面,比如你可能從網絡下載一張圖片,在下載成功的回調裡把圖片加載到 Activity 的 ImageView 裡面,所以異步任務保留著對Activity的引用。這樣一來,即使Activity已經被切換到後台(onDestroy已經執行),這些異步任務仍然保留著對Activity實例的引用,所以系統就無法回收這個Activity實例了,結果就是Activity Leak。Android的組件中,Activity對象往往是在堆(Java Heap)裡占最多內存的,所以系統會優先回收Activity對象,如果有Activity Leak,APP很容易因為內存不夠而OOM。
采用MVP模式,只要在當前的Activity的onDestroy裡,分離異步任務對Activity的引用,就能避免 Activity Leak。
上面一張簡單的MVP模式的UML圖,從圖中可以看出,使用MVP,至少需要一下幾步:
1. 創建IPresenter接口,把所有業務邏輯相關的接口都放在這裡,並創建它的實現PresenterImpl
2. 創建IView接口,把所有視圖邏輯的接口都放在這裡,其實現類是當前Activity或Fragment
3. Activity裡面包含一個IPresenter,而PresenterImpl裡包含一個IView的引用,並包含Model。Activity只保留IPresenter的引用,其它工作都放在PresenterImpl中實現
4. Model不是必須的,依照實際情況,但一定會有View和Presenter的。
通過上面的介紹,MVP的主要特點就是把Activity裡的許多邏輯都抽離到View和Presenter接口中去,並由具體的實現類來完成。這種寫法多了許多IView和IPresenter的接口,在某種程度上加大了開發的工作量,剛開始使用MVP的小伙伴可能會覺得這種寫法比較別扭,而且難以記住。其實一開始想太多也沒有什麼卵用,只要在具體項目中多寫幾次,就能熟悉MVP模式的寫法,理解TA的意圖,以及享受其帶來的好處。
簡單做幾個計算器加的運算:
UI如下:
項目代碼結構圖如下:
包的命名和其它一樣,可以按照模塊進行命名,也可以直接按照頂層package進行明明。
首先,看一下MainActivity的代碼:
public class MainActivity extends AppCompatActivity implements ICalcView, View.OnClickListener { private EditText mNumber01; private EditText mNumber02; private TextView mResult; private ICalcPrestenter mCalcPrestener; private Button mBtnCalc; private Button mBtnClear; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mNumber01 = (EditText) findViewById(R.id.et_number01); mNumber02 = (EditText) findViewById(R.id.et_number02); mResult = (TextView) findViewById(R.id.tv_result); mBtnCalc = (Button) findViewById(R.id.btn_calc); mBtnClear = (Button) findViewById(R.id.btn_clear); mCalcPrestener = new CalcPresenter(this); mBtnClear.setOnClickListener(this); mBtnCalc.setOnClickListener(this); } @Override public void clearText() { mNumber01.setText(""); mNumber02.setText(""); mResult.setText(""); } @Override public void showResult(int result) { mResult.setText(String.valueOf(result)); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.btn_calc: String number01 = mNumber01.getText().toString().trim(); if (TextUtils.isEmpty(number01)) { showToast("第一個數字不能為空"); return; } String number02 = mNumber02.getText().toString().trim(); if (TextUtils.isEmpty(number02)) { showToast("第二個數字不能為空"); return; } mCalcPrestener.sum(Integer.parseInt(number01), Integer.parseInt(number02)); break; case R.id.btn_clear: mCalcPrestener.clear(); break; } } private void showToast(String msg) { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show(); } }
如上,activity只持有一個presenter的引用,實現了IView接口,具體邏輯相關的操作交由presenter進行處理。
ICalcPrestenter代碼如下:
public interface ICalcPrestenter { void sum(int a,int b); void clear(); }
其實現類CalcPrestenterImpl代碼:
public class CalcPresenter implements ICalcPrestenter { private ICalcView mCalcView; public CalcPresenter(ICalcView calcView) { mCalcView = calcView; } @Override public void sum(int a, int b) { mCalcView.showResult(a + b); } @Override public void clear() { mCalcView.clearText(); } }
presenter實現類中,持有對IView(Activity實現了IView)的引用,邏輯處理完成後,通過調用IView的視圖邏輯進行UI的更新。如果有其它Activity也需要用到相同的邏輯,可以直接進行復用,並且一個Activity中可以持有多個Presenter的引用,達到方便 耦合性低 閱讀性和維護性高的目標,這才是MVP的核心思想。
好了,簡單介紹這麼多,本文內容文字參考了網絡上部分文章,示例是根據自己的理解,做了一個超級簡單的demo,如有錯誤之處,歡迎指出~
Bitmap src = BitmapFactory.decodeResource(getResources(), imageId); //獲取Bitmap圖片Round
剛剛接手一個備份系統浏覽器書簽的模塊,現在把代碼貼出來,另外有幾點疑問請路過的大神指教 1、根據官方api應該是有以下幾個字段是可以獲取的 但是除了
接觸過自定義控件的開發者一看,笑了,立馬關了網頁。但是…你真的知道怎麼繪制居中文本嗎?我不會?開玩笑,不就是:X=控件寬度/2 - 文本寬度/2;Y=控件高
使用樣式文件,在values 目錄下新建styles.xml文件,編寫如下代碼: 復制代碼 代碼如下: Code highlighting produced by Act