編輯:關於Android編程
只要用過mvp這個問題可能很多人都知道。寫mvp的時候,presenter會持有view,如果presenter有後台異步的長時間的動作,比如網絡請求,這時如果返回退出了Activity,後台異步的動作不會立即停止,這裡就會有內存洩漏的隱患,所以會在presenter中加入一個銷毀view的方法。現在就在之前的項目中做一下修改
//presenter中添加mvpView 置為null的方法
public void onDestroy(){
mvpView = null;
}
//退出時銷毀持有Activity
@Override
protected void onDestroy() {
mvpPresenter.onDestroy();
super.onDestroy();
}
presenter中增加了類似的生命周期的方法,用來在退出Activity的時候取消持有Activity。
如果每一個Activity都需要做綁定和解綁操作就太麻煩了,現在我希望可以有一個通用的presenter來為我們添加view的綁定與銷毀。
public abstract class BasePresenter {
public T mView;
public void attach(T mView) {
this.mView = mView;
}
public void dettach() {
mView = null;
}
}
因為不能限定死傳入的View,所以使用泛型來代替傳入的對象。通過這個通用的presenter我就可以把原來的MvpPresenter簡化成下面的樣子
public class NewMvpPresenter extends BasePresenter {
private RequestBiz requestBiz;
private Handler mHandler;
public NewMvpPresenter() {
requestBiz = new RequestBiziml();
mHandler = new Handler(Looper.getMainLooper());
}
public void onResume(){
requestBiz.requestForData(new OnRequestListener() {
@Override
public void onSuccess(final List data) {
mHandler.post(new Runnable() {
@Override
public void run() {
mView.hideLoading();
mView.setListItem(data);
}
});
}
@Override
public void onFailed() {
mView.showMessage("請求失敗");
}
});
}
public void onItemClick(int position){
mView.showMessage("點擊了item"+position);
}
}
界面需要提供的UI方法中會有很多類似的UI方法,可以把它們提取到一個公共的父類接口中。比如提取顯示loading界面和隱藏loading界面的方法,其他的view層接口就可以直接繼承BaseView接口,不必重復的寫顯示和隱藏loading界面方法。
public interface BaseView {
void showLoading();
void hideLoading();
}
presenter綁定到activity和View的綁定和解綁操作是每個Activity都會去做的,同樣這裡我也希望能有一個父類來完成這個統一的操作。
public abstract class BaseMvpActivity> extends AppCompatActivity {
public T presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = initPresenter();
}
@Override
protected void onResume() {
super.onResume();
presenter.attach((V)this);
}
@Override
protected void onDestroy() {
presenter.dettach();
super.onDestroy();
}
// 實例化presenter
public abstract T initPresenter();
}
同樣使用泛型來提取通用的邏輯,presenter的初始化,以及view的綁定和解綁操作都提取到父類Activity中。向外部提供了一個 initPresenter();
方法用來初始化presenter,如果想創建不同參數的構造函數都可以隨意去創建。
通過上面的base父類,對之前的例子進行優化,寫一個更加好用的例子。
NewMvpView 繼承BaseView接口,添加自己的初始化ListView和Toast信息方法
public interface NewMvpView extends BaseView {
void setListItem(List data);
void showMessage(String message);
}
NewMvpPresenter 繼承BasePresenter類,增加網絡請求和處理點擊事件的方法
public class NewMvpPresenter extends BasePresenter {
private RequestBiz requestBiz;
private Handler mHandler;
public NewMvpPresenter() {
requestBiz = new RequestBiziml();
mHandler = new Handler(Looper.getMainLooper());
}
public void onResume(){
requestBiz.requestForData(new OnRequestListener() {
@Override
public void onSuccess(final List data) {
mHandler.post(new Runnable() {
@Override
public void run() {
mView.hideLoading();
mView.setListItem(data);
}
});
}
@Override
public void onFailed() {
mView.showMessage("請求失敗");
}
});
}
public void onItemClick(int position){
mView.showMessage("點擊了item"+position);
}
}
NewMvpActivity
public class NewMvpActivity extends BaseMvpActivity implements NewMvpView,AdapterView.OnItemClickListener{
ListView mvpListView;
ProgressBar pb;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mvp);
mvpListView = (ListView)findViewById(R.id.mvp_listview);
mvpListView.setOnItemClickListener(this);
pb = (ProgressBar) findViewById(R.id.mvp_loading);
}
@Override
protected void onResume() {
super.onResume();
presenter.onResume();
}
@Override
public NewMvpPresenter initPresenter() {
return new NewMvpPresenter();
}
@Override
public void onItemClick(AdapterView parent, View view, int position, long id) {
presenter.onItemClick(position);
}
@Override
public void setListItem(List data) {
ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,data);
mvpListView.setAdapter(adapter);
}
@Override
public void showMessage(String message) {
Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
}
@Override
public void showLoading() {
pb.setVisibility(View.VISIBLE);
}
@Override
public void hideLoading() {
pb.setVisibility(View.GONE);
}
}
最終的成果,我們只需要在Acitivity中傳入泛型對象,並寫好initPresenter()
Presenter的初始化的方法就可以直接去使用presenter,當然View的接口還是要自己去實現。
以上的方法只是一些比較簡單的封裝,下面來看看官方的MVP架構是怎麼寫的。
先來看看官方的代碼目錄(這裡只是功能模塊的目錄,不包括測試模塊,畢竟這裡分析的是官方的實現代碼)
谷歌的todomvp工程實現了一個類似記事本的功能,可以顯示任務列表(tasks 包),顯示任務詳情(taskdetail包),
添加和編輯任務(addedittask包),剩下的statistics包用來顯示任務的完成情況,data包數據模塊對應mvp的M,Util包就是通用的方法。這裡todomvp也有2個BasePresenter和BaseView2個基類不過看的出來這2個都是接口。
public interface BasePresenter {
void start();
}
public interface BaseView {
void setPresenter(T presenter);
}
各自簡單的聲明了一個方法,start()方法用來給Presenter做一些初始化的操作不用特別在意,BaseView聲明的方法就很有意思了,setPresenter(T presenter)
很明顯是給View綁定Presenter,而前文我們使用的方式給Presenter傳入View的方式來完成View和Presenter綁定的操作,這裡谷歌采用了相反的方式來操作,為什麼呢?
先把這個問題留下,看看單個模塊的文件結構。
vcjPzqpGcmFnZW1lbnS6zUFjdGl2aXR5z+CxyLj8z/HKx01WUNbQtcS1xFZpZXey46OsuNW6w7/J0tTC+tfjTVZQtcRWaWV3suO1xNKqx/OjrEFjdGl2aXR51PLX986q1+6439a4u9O52dPDwLS0tL2ous3Bqs+1Vmlld7rNUHJlc2VudGVyoaM8YnIgLz4NCglGcmFnbWVudNTaxr2w5bvy1d/GwcS7yc/T0LbguPZWaWV3yrG4/NPQ08XKxjwvcD4NCjwvYmxvY2txdW90ZT4NCjxwPs+4z7jP68/r1eLW1re9yr21xMi3uPy807rPwO2jrNXivs26zc7S1q7HsL2rQWN0aXZpdHnX986qVmlld7Ljz+CxyLj8vNO6z8Dt0ru146GjVmlld7XEzsrM4r3iys3N6sHLo6zU2b+0v7RUYXNrRGV0YWlsQ29udHJhY3Qg1eLW1ioqQ29udHJhY3QgvdO/2qOs1eLSssrHudm3vbbA09C1xLncwO23vbeoPC9wPg0KPGgyIGlkPQ=="contract">Contract
google的todomvp 工程中每個模塊都會有一個 **Contract 接口,來看看他的代碼
public interface TaskDetailContract {
interface View extends BaseView {
void setLoadingIndicator(boolean active);
void showMissingTask();
void hideTitle();
void showTitle(String title);
void hideDescription();
void showDescription(String description);
void showCompletionStatus(boolean complete);
void showEditTask(String taskId);
void showTaskDeleted();
void showTaskMarkedComplete();
void showTaskMarkedActive();
boolean isActive();
}
interface Presenter extends BasePresenter {
void editTask();
void deleteTask();
void completeTask();
void activateTask();
}
}
Contract其實就是一個包涵了Presenter和View的接口,Presenter所有的方法都以接口的方式聲明在Contract中View也是同樣的聲明接口,模塊實現的所有的界面變化,業務功能都能在Contract類中一目了然的看明白,具體的Presenter和View的實現類都是通過實現Contract接口來完成。這種方式既方便了管理和維護,也給開發點了一個導航燈。下面來看看Presenter如何引用到Fragment中以及View如何與Presenter建立聯系
/**
* Displays task details screen.
*/
public class TaskDetailActivity extends AppCompatActivity {
public static final String EXTRA_TASK_ID = "TASK_ID";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.taskdetail_act);
// Set up the toolbar.
//.... 設置toolbar 代碼省略
// Get the requested task id
String taskId = getIntent().getStringExtra(EXTRA_TASK_ID);
// 實例化taskDetailFragment
TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager()
.findFragmentById(R.id.contentFrame);
if (taskDetailFragment == null) {
//taskDetailFragment 添加到Activity
taskDetailFragment = TaskDetailFragment.newInstance(taskId);
ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
taskDetailFragment, R.id.contentFrame);
}
// Create the presenter 初始化Presenter
new TaskDetailPresenter(taskId,Injection.provideTasksRepository(getApplicationContext()),
taskDetailFragment);
}
..... 省去不重要代碼
TaskDetailActivity主要做了taskDetailFragment 初始化和TaskDetailPresenter的初始化操作,在做TaskDetailPresenter初始化時直接將taskDetailFragment作為參數傳入,因為taskDetailFragment實現了View層的接口,看到這裡有一個疑問,new TaskDetailPresenter()可以實例化一個Presenter,但是怎麼傳入到taskDetailFragment中呢?為了解開疑惑我查看了TaskDetailPresenterde 構造方法
public class TaskDetailPresenter implements TaskDetailContract.Presenter {
//....省去不重要的變量聲明
public TaskDetailPresenter(@Nullable String taskId,
@NonNull TasksRepository tasksRepository,
@NonNull TaskDetailContract.View taskDetailView) {
this.mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");
//實現綁定Presenter的關鍵方法
mTaskDetailView.setPresenter(this);
}
關鍵方法就是在BaseView中聲明的void setPresenter(T presenter);
方法實現的綁定,當然這裡只是調用View的方法,具體的綁定還要追蹤到實體類中,來看TaskDetailFragment
public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {
......省去不重要代碼
//聲明了一個mPresenter
private TaskDetailContract.Presenter mPresenter;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.taskdetail_frag, container, false);
....省去初始化代碼
return root;
}
@Override
public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {
//完成mPresenter的綁定
mPresenter = checkNotNull(presenter);
}
.....
}
TaskDetailFragment 實現了TaskDetailContract接口中的View接口,這樣在TaskDetailPresenter 構造方法中調用mTaskDetailView.setPresenter(this)方法後,TaskDetailFragment 的setPresenter也會調用,TaskDetailPresenter 就成功綁定到了TaskDetailFragment 中。這種綁定方式也解釋了上文提到的問題,為什麼谷歌采用了相反的方式操作,就是為了這後面的綁定操作。
谷歌的項目同樣會有當有後台異步任務時可能導致Acitivity內存洩漏的問題。谷歌也有自己的處理方法,上面提到的TaskDetailContract 接口有聲明一個方法 isActive()
public interface TaskDetailContract {
interface View extends BaseView {
....省去其他的方法
boolean isActive();
}
看看這方法的具體實現類
public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {
.....
@Override
public boolean isActive() {
return isAdded();
}
isAdded()方法如果返回true代表Fragment添加到了Activity,false代表沒有添加,通過調用isActive()方法就可以給後台異步任務添加判斷避免內存洩漏。
到這裡整體的架構就已經清楚了配上這張UML圖可以更好的理解。
谷歌的todomvp也是一個舉例,我們自己的項目到底怎麼寫合適,還是要根據項目情況決定。
畢竟自己的才是最好的
相關代碼
https://github.com/haibuzou/MVPSample
https://github.com/googlesamples/android-architecture/tree/todo-mvp/
上節中我們是手動拼接xml文件,但是上節中那樣的做法會有一個問題,比如: //插入消息的內容sBuffer.append(); sBuffer.append(s
本文實例講述了Android實現將應用崩潰信息發送給開發者並重啟應用的方法。分享給大家供大家參考,具體如下:在開發過程中,雖然經過測試,但在發布後,在廣大用戶各種各樣的運
這裡分享一個Android的非常經典實用而且簡單方便的第三方UI控件庫:BottomView(小米的米UI也用到了這個) 實現功能: 可以在底部彈出的Vie
這篇文章是android開發人員的必備知識,是我特別為大家整理和總結的,不求完美,但是有用。1.背景自適應且不失真問題的存在 制作自適應背景圖片是UI開發的一個廣泛問題