編輯:關於Android編程
為什麼使用MVP,網上有很多說法,最主要就是減輕了Activity的責任,相比於MVC中的Activity承擔的責任太多,因此有必要講講MVP。
在MVC框架中,View是可以直接讀取Model模型中的數據的,Model模型數據發生改變是會通知View數據顯示發生相應的改變。而在MVP中Model和View之間的沒有任何聯系,是兩個完全獨立的模塊,當Model模型發生數據改變時,通過Presenter通知View視圖發生相應的UI改變。因此,個人覺得:MVP才是正真的視圖和模型完全分離,也就是Model模型進行業務數據處理和View視圖顯示沒有任何關聯。可以通過下圖看出:
以下是MVC的框架圖:
以下是MVP的框架圖:
有關MVP說明的文章現在網上一大堆,最近自己也在嘗試使用MVP去構建應用,關於MVP三層的定義,可以看看下面相關資料:
數據加工處理廠
model是整個應用或界面的數據加工處理廠,所謂數據加工廠就是對數據的獲取,數據的解析,數據的存儲,數據的分發,數據的增刪改查等操作。意思就是凡是涉及到數據操作都是在model進行的,所以model不僅僅只是實體類的集合,同時還包含關於數據的各種處理操作。
三種數據源
數據的數據源有三種:內存,磁盤(文件或數據庫等),網絡。為了提升app的性能,有必要把經常訪問的數據臨時存入內存中;同時也為了提升app性能和為用戶省流量省電,有必要把數據存入磁盤中;還有的數據是有必要從網絡讀取的。三個數據源不一定同時存在,比如不與網絡交互的app,不存在網絡數據源。所以凡是涉及到關於數據發生於三個數據源加工處理的操作的代碼都要放在model中。
model為上層提供的服務:
model從黑盒的角度來看為上層(指依賴於model的層比如present)提供的服務無非就2種:model為上層提供數據,model處理上層傳遞的數據
model為上層提供數據:
上層會從model中去數據,那model會從三數據源中取數據,取的順序是
先內存,內存取到數據返回 其次磁盤,磁盤取到數據,如有必要把數據存儲在內存中,則需要進行存儲,返回數據 最後網絡,網絡取到數據,如有必要在磁盤或內存中存儲,則進行存儲,返回數據上面的取數據過程是最簡單的情況,復雜些還會涉及到從內存或磁盤中取到的數據是否過期,過期的話就應該從網絡獲取。從網絡取得數據後需要把內存或磁盤的數據更新。
model處理上層傳遞的數據:
model接收到上層傳遞的數據後,model會依次把數據扔給三個數據源去處理,有可能三個數據源都會處理數據,有可能只是其中一個處理,model會把處理的結果返回。
所以model會把解析好的數據提供給上層,上層對於數據的來源完全是透明的,上層完全不需要關心數據到底是來自內存,還是磁盤甚至是網絡。同理上層只需要的把數據扔給model,上層唯一做的事情就是愉快的等待處理結果。
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的話,就特別簡單了關於線程切換的事情。
view層就很好理解了,就是用戶直接看到的界面,mvp中的view是很省心的,比如更新view,接收數據。這些操作它都不需要操心,也不需要知道數據到底來自哪裡,給我啥我顯示啥就可以了。
一個view可以同時擁有多個presenter,也可以只有一個presenter。
Android中的Activity,Fragment在mvp中是作為view來使用的,這些Activity,Fragment的責任就小了,只關心界面相關的事情足矣。各種Adapter是放在view層的。
既然清楚了MVP的一些概念,現在就可以創建一個項目,整個項目很簡單,通過點擊按鈕獲取天氣信息並顯示在界面上,這裡面我們使用MVP來搭建整個項目,先從Mode開始到Presenter最後View的創建。(天氣接口使用的是心知天氣提供的SDK http://www.thinkpage.cn/doc )
代碼展示:
package weather.weatherproject.mode;
import com.thinkpage.lib.api.TPCity;
import com.thinkpage.lib.api.TPListeners;
import com.thinkpage.lib.api.TPWeatherManager;
import com.thinkpage.lib.api.TPWeatherNow;
import weather.weatherproject.WeatherApplication;
/**
* 天氣管理類
* Created by glh on 2016-06-23.
*/
public class WeatherManager {
public static final WeatherManager instance = new WeatherManager();
public interface WeatherListener{
void onSuccess(TPWeatherNow response);
void onFailed(String errString);
}
/**
* 獲取指定城市的實況天氣。
*
* @param city
* @param listener
*/
public void getNowWeather(String city, final WeatherListener listener) {
WeatherApplication.weatherManager.getWeatherNow(new TPCity(city), TPWeatherManager.TPWeatherReportLanguage.kSimplifiedChinese, TPWeatherManager.TPTemperatureUnit.kCelsius, new TPListeners.TPWeatherNowListener() {
@Override
public void onTPWeatherNowAvailable(TPWeatherNow tpWeatherNow, String s) {
if (tpWeatherNow != null) {
listener.onSuccess(tpWeatherNow);
} else {
listener.onFailed(s);
}
}
});
}
}
我們知道Model層主要用於數據的輸入和輸出,因此這裡創建了一個天氣管理類,通過獲取天氣信息的方法獲取數據,最後將數據傳遞給Presenter,Presenter只需要將城市名傳遞給Model層,而它只需監聽獲取信息的狀態,因此內部創建了一個天氣信息獲取的監聽接口,通過它使得Presenter進行信息獲取的監聽。
代碼展示:
package weather.weatherproject.presenter;
/**
* View的基礎接口
* Created by glh on 2016-06-23.
*/
public interface IView {
void initView();
}
package weather.weatherproject.presenter;
/**
*
* Created by glh on 2016-06-23.
*/
public interface IPresenter {
void onStop();
void onResume();
void onDestroy();
void onPause();
void onStart();
void init(V view);
}
IView是一個基礎接口,內部定義了一個initView方法,用於View的初始化。
IPresenter也是一個基礎接口,內部定義了一些 Activity或Fragment常用的生命周期方法,用於View與 Activity或Fragment的聯動。
package weather.weatherproject.presenter.weather;
import weather.weatherproject.presenter.IPresenter;
import weather.weatherproject.presenter.IView;
import weather.weatherproject.view.bean.NowWeather;
/**
* 獲取天氣的約定類,用於組合IWeatherView和IWeatherPresenter
* Created by glh on 2016-06-23.
*/
public interface WeatherContract {
interface IWeatherView extends IView {
//獲取指定城市的實況天氣
void showNowWeather(NowWeather result);
void error(String error);
}
interface IWeatherPresenter extends IPresenter {
void getWeather(String city);
}
}
可以看到WeatherContract接口內部定義兩個接口:
1、IWeatherView接口,用於Presenter與View的數據傳遞。
2、IWeatherPresenter接口,是連接Model與View的中間層。
package weather.weatherproject.presenter.weather;
import com.thinkpage.lib.api.TPWeatherNow;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import weather.weatherproject.mode.WeatherManager;
import weather.weatherproject.presenter.bean.BeanUtil;
/**
* 獲取天氣的Presenter
* Created by glh on 2016-06-23.
*/
public class WeatherPresenter implements WeatherContract.IWeatherPresenter {
private WeatherContract.IWeatherView mIWeatherView;
private WeatherManager mWeatherManager = WeatherManager.instance;
private ExecutorService mExecutorService = Executors.newFixedThreadPool(5);
@Override
public void init(WeatherContract.IWeatherView view) {
this.mIWeatherView = view;
mIWeatherView.initView();
}
@Override
public void getWeather(final String city) {
mExecutorService.execute(new Runnable() {
@Override
public void run() {
mWeatherManager.getNowWeather(city, new WeatherManager.WeatherListener() {
@Override
public void onSuccess(TPWeatherNow response) {
mIWeatherView.showNowWeather(BeanUtil.createNowWeather(response));
}
@Override
public void onFailed(String errString) {
mIWeatherView.error(errString);
}
});
}
});
}
@Override
public void onStop() {
}
@Override
public void onResume() {
}
@Override
public void onDestroy() {
}
@Override
public void onPause() {
}
@Override
public void onStart() {
}
}
WeatherPresenter主要做了以下幾件事情:
1、初始化了View。
2、通過View傳遞過來的指令向Model層請求數據。
3、監聽Model層的狀態,並將結果刷新到View上。
請求數據屬於耗時操作因此我們開辟了線程用與請求處理,最後通過UI線程刷新View。BeanUtil用於封裝我們所需的數據。
代碼展示:
package weather.weatherproject.view.base;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import java.util.HashSet;
import java.util.Set;
import weather.weatherproject.presenter.IPresenter;
/**
* 基類Activity,所有業務界面都繼承此BaseActivity。
* Created by glh on 2016-06-23.
*/
public abstract class BaseActivity extends FragmentActivity {
private Set mAllPresenters = new HashSet<>(1);
/**
* 獲取layout的id,具體由子類實現
*
* @return
*/
protected abstract int getLayoutResId();
/**
* 需要子類來實現,獲取子類的IPresenter,一個activity有可能有多個IPresenter
*/
protected abstract IPresenter[] getPresenters();
/**
* 初始化presenters
*/
protected abstract void onInitPresenters();
/**
* 事件監聽
*/
protected abstract void initEvent();
/**
* 從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(presenters[i]);
}
}
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResId());
if (getIntent() != null) {
parseArgumentsFromIntent(getIntent());
}
addPresenters();
onInitPresenters();
initEvent();
}
@Override
protected void onResume() {
super.onResume();
//依次調用IPresenter的onResume方法
for (IPresenter presenter : mAllPresenters) {
if (presenter != null) {
presenter.onResume();
}
}
}
@Override
protected void onStop() {
super.onStop();
//依次調用IPresenter的onStop方法
for (IPresenter presenter : mAllPresenters) {
if (presenter != null) {
presenter.onStop();
}
}
}
@Override
protected void onPause() {
super.onPause();
//依次調用IPresenter的onPause方法
for (IPresenter presenter : mAllPresenters) {
if (presenter != null) {
presenter.onPause();
}
}
}
@Override
protected void onStart() {
super.onStart();
//依次調用IPresenter的onStart方法
for (IPresenter presenter : mAllPresenters) {
if (presenter != null) {
presenter.onStart();
}
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//依次調用IPresenter的onDestroy方法
for (IPresenter presenter : mAllPresenters) {
if (presenter != null) {
presenter.onDestroy();
}
}
}
}
BaseActivity只是做了一下封裝,方便我們展示界面的使用,最後我們看看展示界面WeatherActivity:
package weather.weatherproject.view.weather;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import weather.weatherproject.R;
import weather.weatherproject.presenter.IPresenter;
import weather.weatherproject.presenter.weather.WeatherContract;
import weather.weatherproject.presenter.weather.WeatherPresenter;
import weather.weatherproject.view.base.BaseActivity;
import weather.weatherproject.view.bean.NowWeather;
/**
* 天氣界面
* Created by glh on 2016-06-23.
*/
public class WeatherActivity extends BaseActivity implements WeatherContract.IWeatherView {
private WeatherPresenter mWeatherPresenter = new WeatherPresenter();
private TextView tv_show;
private Button btn_now_weather;
@Override
protected int getLayoutResId() {
return R.layout.activity_main;
}
@Override
protected IPresenter[] getPresenters() {
return new IPresenter[]{mWeatherPresenter};
}
@Override
protected void onInitPresenters() {
mWeatherPresenter.init(this);
}
@Override
protected void initEvent() {
btn_now_weather.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(WeatherActivity.this, "onclick", Toast.LENGTH_SHORT).show();
mWeatherPresenter.getWeather("shanghai");
}
});
}
@Override
public void showNowWeather(NowWeather result) {
tv_show.setText(result.toString());
}
@Override
public void error(String error) {
tv_show.setText(error);
}
@Override
public void initView() {
tv_show = (TextView) findViewById(R.id.tv_show);
btn_now_weather = (Button) findViewById(R.id.btn_now_weather);
}
}
最後可以看到我們的Activity非常的簡潔,到了這裡我們的項目已經搭建完成,這樣做有以下好處:
1、學習過設計模式的人都知道,這樣做基本符合了單一職責原則。
2、符合單一職責原則後,導致類與類組織更清晰。
3、View層與Model層交互需要通過Presenter層進行,這樣v與m層級間的耦合性降低。
4、通過這種分層處理,每一層的測試也相對簡單,維護性更高。
MVPDemo請點擊這裡
Java中的內存洩漏java內存洩漏大家都不陌生了,簡單粗俗的講,就是該被釋放的對象沒有釋放,一直被某個或某些實例所持有卻不再被使用導致 GC 不能回收。在Java中,內
在Android中,任何一個控件都是可以滾動的,因為在View類當中有scrollTo()和scrollBy()這兩個方法,如下圖所示: 這兩個方法的主要作用是將View
4AppBarLayout滑動原理在CoordinatorLayout的measure和layout裡,其實介紹過一點AppBarLayout,這篇將重點講解AppBar
目錄:1.重要類概述2.重要類的常用方法2.簡單View繪制(圓、圓弧、矩形、弧形、圓角矩形、橢圓、文字等)3.setXfermode(Xfermode xfermode