編輯:關於Android編程
前段時間,公司由個同事分享的時候,提到了MVP模式,自己之前也了解過,但是真正在自己的編碼過程中使用的非常少。最近在幫助一個朋友做畢業設計,心想這是一個很好的機會練習一把。網上也找了很多有關MVP的博客,說的也都差不多,就想找一個比較權威的,當然應該是google官網啦,就找到了Google在Github上開源項目,真找到了MVP例子,就記一篇博文,慢慢回味。
MVP(MVP模式):一種軟件設計模式
全稱為Model-View-Presenter,Model提供數據,View負責顯示,Controller/Presenter負責邏輯的處理。MVP與MVC有著一個重大的區別:在MVP中View並不直接使用Model,它們之間的通信是通過Presenter (MVC中的Controller)來進行的,所有的交互都發生在Presenter內部,而在MVC中View會直接從Model中讀取數據而不是通過 Controller。
goto 維基百科
goto 百度百科
MVP模式,一方面是體現單一職責原則,降低Android中類之間邏輯的耦合,使之高內聚低耦合;二是讓代碼更清晰,更簡潔,但是簡潔並不代表代碼少,更易擴展等。
#Android MVP(Google Samples/aandroid-architecture)
前言中提到過,Android官方提供了MVP Sample,這也是Google發現問題,給開發者提供的一種思路吧。
goto Google Sample
點擊上述鏈接,可以看到Google官方為大家推薦的應用開發框架,Android 架構藍圖,當然有心的你還會從Google Github發現更多有用的開源項目。
vcC0sO/W+r3ivvbV4tCps6O8+7XEzsrM4qGj1NrV4rj2z+7Ev9bQo6zM4bmpyrnTw7K7zay1xLzcubm6zbmkvt/Ktc/Wz+DNrLXE06bTw7PM0PKhozxiciAvPg0Kudm3vda7yse9q9Xi0KnX986qss6/vKOsvt/M5crHt/G6z8rKtPO80rfFyOvP7sS/1tCjrL7N0qq+38zlx+m/9r7fzOW31s72wcujrNbYtePKx7T6wuu94bm5o6zM5c+1veG5uaOst72x47LiytS6zb/Jzqy7pKOsv8nN2NW50NShozwvcD4NCjxoMiBpZD0="samples">Samples
在android-architecture項目下,提供了7個例子[穩定版],分別對應項目不同的分支下,大家可以下載下來,跑一跑,看一看。
1. todo-mvp/ - Basic Model-View-Presenter architecture.
2. todo-mvp-loaders/ - Based on todo-mvp, fetches data using Loaders.
3. todo-databinding/ - Based on todo-mvp, uses the Data Binding Library.
4. todo-mvp-clean/ - Based on todo-mvp, uses concepts from Clean Architecture.
5. todo-mvp-dagger/ - Based on todo-mvp, uses Dagger2 for Dependency Injection.
6.todo-mvp-contentproviders/ - Based on todo-mvp-loaders, fetches data using Loaders and uses Content Providers.
7.todo-mvp-rxjava/ - Based on todo-mvp, uses RxJava for concurrency and data layer abstraction.
8.todo-mvp-tablet/
很清晰的可以看出,Sample1就是單純的介紹MVP設計模式的,2-7是在1的基礎上,添加了一些快速開發的一些框架,本文主要記錄MVP,其他將後續跟進; 8官方正在開發中,應該還不算穩定版本。
如何下載,以及運行參考官方文檔即可,這個也是學習的一個過程。
它展示了一個簡單的MVP模式,沒有使用任何框架和架構。它使用手動依賴注入,以提供一個本地和遠程數據源的存儲庫。異步任務處理回調。
首先看下整體框架,左半邊是Model,右邊是View和Presenter,官方善意的注釋了下,圖片中的VIEW並不是android框架中的View,是MVP場景中的一個抽象,在代碼中就能體會到。途中藍色字體相當於抽象概念。
之所以使用Fragment:
首先看下整體的源碼結構:

從上圖可以很清晰的看出整個app的功能模塊,包括(Tasks, AddEditTask, TaskDetail, Statistics)
每個功能包含四大文件:Activity, Fragment, Contract, Presenter
就像上述提及的Activity相當於一個控制器;Fragment相當於MVP中的View;Contract是一個功能中View和Presenter間的協議,通俗的理解就是,這兩個是協同工作的;而Presenter就是具體的業務邏輯實現代碼。首先會根據當前的功能模塊進行分解成多個業務邏輯,然後制定View和Presenter的協議。
運行效果如下:




以Task功能來分析下具體的MVP模式的實現:
TasksContract:主要是定義相關的業務邏輯接口,還有UI的更新接口,可以很清晰的查看該功能界面的具體業務功能,不過當頁面比較復雜時,這個文件會不會很大???或者說我們就不該把一個頁面搞太復雜。網上看過不少例子,都沒有提到這個文件,個人感覺這個Contract還是很重要的!!!
/**
* 指定View和Presenter之間的協議
*/
public interface TasksContract {
interface View extends BaseView {
void setLoadingIndicator(boolean active);
void showTasks(List tasks);
void showAddTask();
void showTaskDetailsUi(String taskId);
void showTaskMarkedComplete();
void showTaskMarkedActive();
void showCompletedTasksCleared();
void showLoadingTasksError();
void showNoTasks();
void showActiveFilterLabel();
void showCompletedFilterLabel();
void showAllFilterLabel();
void showNoActiveTasks();
void showNoCompletedTasks();
void showSuccessfullySavedMessage();
boolean isActive();
void showFilteringPopUpMenu();
}
interface Presenter extends BasePresenter {
void result(int requestCode, int resultCode);
void loadTasks(boolean forceUpdate);
void addNewTask();
void openTaskDetails(@NonNull Task requestedTask);
void completeTask(@NonNull Task completedTask);
void activateTask(@NonNull Task activeTask);
void clearCompletedTasks();
void setFiltering(TasksFilterType requestType);
TasksFilterType getFiltering();
}
}
TasksActivity:主要是創建View(TasksFragment), 創建Presenter(TasksFragment), 並將View在Presenter構造過程中傳遞過去。
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null) {
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
}
// Create the presenter
mTasksPresenter = new TasksPresenter(
Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
TasksFragment:進行Presenter的初始化, 根據用戶交互,調用View接口定義的相關協議接口,執行Presenter中定義的邏輯接口
/**
* Display a grid of {@link Task}s. User can choose to view all, active or completed tasks.
*/
public class TasksFragment extends Fragment implements TasksContract.View {
private TasksContract.Presenter mPresenter;
...
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
@Override
public void setPresenter(@NonNull TasksContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mPresenter.result(requestCode, resultCode);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mNoTaskAddView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showAddTask();
}
});
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mPresenter.addNewTask();
}
});
// Set up progress indicator
final ScrollChildSwipeRefreshLayout swipeRefreshLayout =
(ScrollChildSwipeRefreshLayout) root.findViewById(R.id.refresh_layout);
swipeRefreshLayout.setColorSchemeColors(
ContextCompat.getColor(getActivity(), R.color.colorPrimary),
ContextCompat.getColor(getActivity(), R.color.colorAccent),
ContextCompat.getColor(getActivity(), R.color.colorPrimaryDark)
);
// Set the scrolling view in the custom SwipeRefreshLayout.
swipeRefreshLayout.setScrollUpChild(listView);
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
mPresenter.loadTasks(false);
}
});
setHasOptionsMenu(true);
return root;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_clear:
mPresenter.clearCompletedTasks();
break;
case R.id.menu_filter:
showFilteringPopUpMenu();
break;
case R.id.menu_refresh:
mPresenter.loadTasks(true);
break;
}
return true;
}
@Override
public void showFilteringPopUpMenu() {
PopupMenu popup = new PopupMenu(getContext(), getActivity().findViewById(R.id.menu_filter));
popup.getMenuInflater().inflate(R.menu.filter_tasks, popup.getMenu());
popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.active:
mPresenter.setFiltering(TasksFilterType.ACTIVE_TASKS);
break;
case R.id.completed:
mPresenter.setFiltering(TasksFilterType.COMPLETED_TASKS);
break;
default:
mPresenter.setFiltering(TasksFilterType.ALL_TASKS);
break;
}
mPresenter.loadTasks(false);
return true;
}
});
popup.show();
}
/**
* Listener for clicks on tasks in the ListView.
*/
TaskItemListener mItemListener = new TaskItemListener() {
@Override
public void onTaskClick(Task clickedTask) {
mPresenter.openTaskDetails(clickedTask);
}
@Override
public void onCompleteTaskClick(Task completedTask) {
mPresenter.completeTask(completedTask);
}
@Override
public void onActivateTaskClick(Task activatedTask) {
mPresenter.activateTask(activatedTask);
}
};
...
@Override
public void showTasks(List tasks) {
mListAdapter.replaceData(tasks);
mTasksView.setVisibility(View.VISIBLE);
mNoTasksView.setVisibility(View.GONE);
}
@Override
public void showNoActiveTasks() {
showNoTasksViews(
getResources().getString(R.string.no_tasks_active),
R.drawable.ic_check_circle_24dp,
false
);
}
@Override
public void showNoTasks() {
showNoTasksViews(
getResources().getString(R.string.no_tasks_all),
R.drawable.ic_assignment_turned_in_24dp,
false
);
}
@Override
public void showNoCompletedTasks() {
showNoTasksViews(
getResources().getString(R.string.no_tasks_completed),
R.drawable.ic_verified_user_24dp,
false
);
}
@Override
public void showSuccessfullySavedMessage() {
showMessage(getString(R.string.successfully_saved_task_message));
}
private void showNoTasksViews(String mainText, int iconRes, boolean showAddView) {
mTasksView.setVisibility(View.GONE);
mNoTasksView.setVisibility(View.VISIBLE);
mNoTaskMainView.setText(mainText);
mNoTaskIcon.setImageDrawable(getResources().getDrawable(iconRes));
mNoTaskAddView.setVisibility(showAddView ? View.VISIBLE : View.GONE);
}
@Override
public void showActiveFilterLabel() {
mFilteringLabelView.setText(getResources().getString(R.string.label_active));
}
@Override
public void showCompletedFilterLabel() {
mFilteringLabelView.setText(getResources().getString(R.string.label_completed));
}
@Override
public void showAllFilterLabel() {
mFilteringLabelView.setText(getResources().getString(R.string.label_all));
}
@Override
public void showAddTask() {
Intent intent = new Intent(getContext(), AddEditTaskActivity.class);
startActivityForResult(intent, AddEditTaskActivity.REQUEST_ADD_TASK);
}
@Override
public void showTaskDetailsUi(String taskId) {
// in it's own Activity, since it makes more sense that way and it gives us the flexibility
// to show some Intent stubbing.
Intent intent = new Intent(getContext(), TaskDetailActivity.class);
intent.putExtra(TaskDetailActivity.EXTRA_TASK_ID, taskId);
startActivity(intent);
}
@Override
public void showTaskMarkedComplete() {
showMessage(getString(R.string.task_marked_complete));
}
@Override
public void showTaskMarkedActive() {
showMessage(getString(R.string.task_marked_active));
}
@Override
public void showCompletedTasksCleared() {
showMessage(getString(R.string.completed_tasks_cleared));
}
@Override
public void showLoadingTasksError() {
showMessage(getString(R.string.loading_tasks_error));
}
private void showMessage(String message) {
Snackbar.make(getView(), message, Snackbar.LENGTH_LONG).show();
}
@Override
public boolean isActive() {
return isAdded();
}
...
}
TasksPresenter:監聽用戶的UI操作,實現Presenter協議中定義的業務邏輯,和Model層進行交換,然後更新UI。
/**
* Listens to user actions from the UI ({@link TasksFragment}), retrieves the data and updates the
* UI as required.
*/
public class TasksPresenter implements TasksContract.Presenter {
...
public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");
...
其中TasksContract.View實現了BaseView,實現setPresenter接口
TasksContract.Presenter實現了BasePresenter,實現了start接口
//TODO
Android更改桌面應用程序launcher的兩種方式
launcher,也就是Android的桌面應用程序。下圖是我正在使用的魅族手機的launcher應用程序: 接下來我們要開發一個自己的launcher,使其替
Android 4.4 Dialog 被狀態欄遮擋的解決方法
首先看不正常的圖,點擊tracing_dialog按鈕彈出對話框然後看理論上的效果圖觀察兩張圖發現,不正常的圖最上方被狀態欄遮擋住了,而該問題存在於android4.4版
android 自定義dialog初探
1,創建dialog的布局,如 2,在style中聲明如下風格 1
從源碼分析Android的Volley庫的工作流程
Volley現在已經被官方放到AOSP裡面,已經逐步成為Android官方推薦的網絡框架。類抽象對Http協議的抽象Requeset顧名思義,對請求的封裝,實現了Comp