編輯:關於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(Listtasks) { 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
launcher,也就是Android的桌面應用程序。下圖是我正在使用的魅族手機的launcher應用程序: 接下來我們要開發一個自己的launcher,使其替
首先看不正常的圖,點擊tracing_dialog按鈕彈出對話框然後看理論上的效果圖觀察兩張圖發現,不正常的圖最上方被狀態欄遮擋住了,而該問題存在於android4.4版
1,創建dialog的布局,如 2,在style中聲明如下風格 1
Volley現在已經被官方放到AOSP裡面,已經逐步成為Android官方推薦的網絡框架。類抽象對Http協議的抽象Requeset顧名思義,對請求的封裝,實現了Comp