我想知道,如果 Android 支持 MVC(模型视图控制器)结构?如果支持那么 1. 什么是控制器?2.什么是型号?3. 什么是 View ?
请清除我。我对此有些困惑。
我想知道,如果 Android 支持 MVC(模型视图控制器)结构?如果支持那么 1. 什么是控制器?2.什么是型号?3. 什么是 View ?
请清除我。我对此有些困惑。
模型视图控制工作正常
实际的
Activity
类并没有扩展 android 的View
类,但它确实处理向用户显示窗口并处理该窗口的事件(onCreate
等onPause
)。这意味着,当您使用MVC 模式时,您的控制器实际上将是一个伪 View-Controller。因为它正在处理向用户显示一个窗口,以及您添加到其中的其他视图组件
setContentView
,并且还处理至少各种活动生命周期事件的事件。在 MVC 中,控制器应该是主入口点。如果将其应用于 android 开发时,这有点值得商榷,因为它
activity
是大多数应用程序的自然入口点。
模型= 具有主要业务逻辑的实体或类
View = 布局、资源和小部件,例如EditText
控制器= Activity
,Adaptor
模型= 内容提供者。
控制器= 活动、片段或服务。
视图= XML 布局。
MVC 已经在 Android 中实现
View = 布局、资源和内置类,如从 android.view.View 派生的 Button。
控制器 = 活动和片段
模型 = 实现应用程序逻辑的类
在经典 MVC 中,控制器是关于决策的,即下一步要运行哪个动作。视图从模型中读取数据并更新它自己的字段。
在 Android 活动中,两者都在做,它们决定运行什么动作来响应事件,并设置布局的字段。他们还从模型中读取数据并连接小部件。这些活动结合了经典控制器和经典视图两者的逻辑任务。
这就是为什么在大多数情况下我不会谈论 MVC。控制器和视图之间没有明确的分离。Java 代码和 XML 资源之间有一个清晰的分离。这是有道理的,因为在更大的团队中,不同的人负责视觉布局和编程。
您仍然可以编写自己的视图组件并将这部分代码作为视图来处理。它只是经典视图的被动部分,而逻辑已经在活动和片段中加入了控制器。我不会谈论视图,而是组件或小部件。越智能的小部件,它们再次占用的经典视图的逻辑就越多。
另一方面,如果你应用像 Room 这样的库,Android 会再次变得更像 MVC。Room 和 LiveData 使视图能够观察模型的变化,一直到数据库的变化。如果您干净地分离视图内容并将控制器减少到决策制定,您可以以某种方式构建您的架构,它真的值得再次命名为 MVC。
这取决于开发商。可以将真正的 MVC 应用于 Android,但这不是默认情况。
实现 MVC 模式的主要目标是,这样做可以让您稍后“拉出”其中任何一个,然后添加一个新的,而无需对其他两个进行很少或没有必要的更改。
模型:所有关于数据。操作什么,存储什么以及如何操作。
视图:关于 UI 或演示的所有内容。显示什么以及如何显示。
控制器:事件处理程序。指示其他两个响应事件何时运行。
在 Android 中,MVC 的这种实现具有扩展 Activity 类的类形式的控制器。毕竟,正是这个类最初接收构成 Android Activity 生命周期的“事件”(即 onStart()、onCreate()、onSuspend()、onStop()、onResume()、onDestroy)。这个生命周期可能会随着 Android 的发展而改变,因此将其表示为 MVC 模式的控制器组件是有意义的。
同样,通过这个 MVC 实现,我可以取出三个组件中的任何一个,然后放入一个全新的界面(视图)、一个全新的数据库(模型)或一个新的活动生命周期(控制器),而无需更改另外两个。唯一需要的是每个组件都尊重下面列出的样板模板。
在这个实现中,三个MVC组件分别用三个java类来表示:appView.java、appController.java、appModel.java
查看每个类时,记下成员变量 mController、mAppView 和 mAppModel,并查看它们在每个 java 文件中的引用方式和时间。这些成员变量是允许每个组件相互引用的“挂钩”。
此外,您会注意到 mAppModel 进一步分解并使用了一个名为 dbHelper 的附加类。这使您可以将“什么”数据与“如何”操作和存储数据分开。
public class appController extends Activity {
appView mAppView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAppView = new appView(this);
mAppView.onCreate(savedInstanceState);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
return mAppView.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
boolean result;
switch (item.getItemId()) {
case ....
return true;
case ....
return true;
default:
result = mAppView.onOptionsItemSelected(item);
}
if ( !result )
result = super.onOptionsItemSelected(item);
return result;
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
mAppView.onCreateContextMenu(menu, v, menuInfo);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
return mAppView.onContextItemSelected(item);
}
// When a startActivityForResult() is called
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mAppView.onActivityResult(requestCode, resultCode, data);
}
@Override
protected void onStop(){
super.onStop();
mAppView.onStop();
}
@Override
protected void onRestart(){
super.onRestart();
mAppView.onRestart();
}
@Override
protected void onDestroy(){
super.onDestroy();
mAppView.onDestroy();
mAppView = null;
}
控制器 appController 实现了大部分“活动生命周期”方法,并依次调用视图 appView 中的方法。这意味着标准的生命周期方法,onStop、onResume、onDestroy 等不仅在 Controller 中实现,而且在此 MVC 模式的 View 部分中实现。稍后您会看到模型部分也是如此。
您可以在下面的 View 实现中看到 appView,成员变量 mController 用于访问 Activity 方法,但允许将 Activity(控制器)与 UI(布局、菜单等)分离。
public class appView {
private Activity mController;
private Context mContext;
private appModel mAppModel;
public appView(Activity activity) {
this((Context) activity);
mController = activity;
}
// This can be called when there is not activity available.
public appView(Context context){
mContext = context;
mAppModel = new appModel(this);
}
protected void onCreate(Bundle savedInstanceState) {
mController.setContentView(R.layout.whatever_you_want_activity);
btnNewToDo = (Button) mController.findViewById(.....
// The New button.
btnNewToDo.setOnClickListener(......
lvToDos = (ListView) mController.findViewById(......
// One click will edit that selected item.
lvToDos.setOnItemClickListener(........
}
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = mController.getMenuInflater();
inflater.inflate(R.menu.whatever_you_want_menu, menu);
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
....
}
protected void onStop(){
mAppModel.onStop();
}
protected void onRestart(){
mAppModel.onRestart();
}
protected void onDestroy() {
mController = null;
mContext = null;
if(mAppModel != null ){
mAppModel.onDestroy();
mAppModel = null;
}
}
}
型号如下所示。查看此类如何在其构造函数中接收视图和控制器。控制器被视为 Context 类型,而不是 Activity。这允许您涉及任何 Context 类型的对象,而不一定是 Activity 对象。
此外,您将看到在构造函数中引入了一个帮助器类 dbHelper。
public class appModel {
private appView mAppView;
private Context mContext;
// Holds the database helper
private dbHelper mDBHelper;
public appModel(appView appView){
mAppView = appView;
mContext = mAppView.getContext();
mDBHelper = new dbHelper(mContext);
}
public boolean open() {
if (mDBHelper == null) return false;
return mDBHelper.open().isOpen();
}
public void close(){
mDBHelper.close();
}
// The App might get destroyed with calling onDestroy, and so close it.
protected void onStop(){
// close the db connection...
close();
}
protected void onRestart() {
// db likely closed.
open();
}
protected void onDestroy(){
mAppView = null;
mContext = null;
mDBHelper.onDestroy();
mDBHelper = null;
}
}
正如您在下面看到的,SQLite 是此应用程序中使用的数据库。但是,切换出这个帮助程序类 dbHelper,您可以使用完全不同的数据库,而其他组件也不明智。
下面包括一些基本方法(打开、关闭等),可让您了解此处执行的功能。此外,请注意 onDestroy() 方法在这里关闭数据库连接。它由上面的 View 调用,当它被销毁时又由 Controller 调用。
正是这个帮助类知道数据库中字段的名称。有了这个实现,视图、控制器甚至模型都不需要知道使用的数据库类型,甚至不需要知道字段名称。
public class dbHelper extends SQLiteOpenHelper {
private SQLiteDatabase mDB;
private Context mContext;
static final String DATABASE_NAME = "whatever";
static final String DATABASE_FILE = DATABASE_NAME + ".db";
static final String DBKEY_FIELD = "rowid";
static final int DATABASE_VERSION = 5;
// SQL Statement to create a new database.
private static final String DATABASE_CREATE = "CREATE TABLE IF NOT EXISTS " + DATABASE_NAME
+ "(.... );";
// SQL statement used to upgrade the database.
private final String ALTER_TABLE = "ALTER TABLE " + DATABASE_NAME + " ADD COLUMN anewfield VARCHAR;";
private final String SELECT_ALL = "SELECT " + DBKEY_FIELD + " AS _id, * FROM " + DATABASE_NAME + " ORDER BY somefield ASC";
private static final String DROP_TABLE = "DROP TABLE IF EXISTS " + DATABASE_NAME;
public dbHelper(Context controller) {
super(controller, DATABASE_FILE, null, DATABASE_VERSION);
mContext = controller;
}
// Called when no database exists or if there is a new 'version' indicated.
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
public dbHelper open() {
try {
mDB = getWritableDatabase();
} catch (SQLException ex) {
if (mDB != null && mDB.isOpen()) {
mDB.close();
}
if (mDB != null) {
mDB = null;
}
}
return this;
}
public boolean isOpen() {
return mDB != null && mDB.isOpen();
}
public void close() {
super.close();
// It's good to lose the reference here with the connection closed.
mDB = null;
}
protected void onDestroy() {
close();
// Just making sure.
mDB = null;
mContext = null;
}
public SQLiteDatabase getDatabaseInstance() {
return mDB;
}
private Cursor runQuery(String sqlStmt) {
Cursor records;
try {
records = mDB.rawQuery(sqlStmt, null);
} catch (RuntimeException ex) {
// If something goes wrong, return an empty cursor.
records = new MatrixCursor(new String[]{"empty"});
}
return records;
}
protected boolean dropTable() {
boolean dropped;
try {
mDB.execSQL("DROP TABLE IF EXISTS " + DATABASE_NAME);
dropped = true;
} catch (SQLException ex) {
dropped = false;
}
return dropped;
}
@Override
public void onConfigure(SQLiteDatabase db) {
}
@Override
public void onOpen(SQLiteDatabase db) {
}
// Called when the database needs to be upgraded to the current version.
@Override
public void onUpgrade(SQLiteDatabase _db, int _oldVersion, int _newVersion) {
if ( _oldVersion >= _newVersion){
Log.w(mContext.getClass().getSimpleName(), "Cannot 'upgrade' from version " + _newVersion + " to " + _oldVersion + ". Upgrade attempt failed.");
}
try {
_db.execSQL(ALTER_TABLE);
}catch(RuntimeException ex){
Log.e(mContext.getClass().getSimpleName(), "Database upgrade failed. Version " + _oldVersion + " to " + _newVersion);
throw ex;
}
// Log the version upgrade.
Log.i(mContext.getClass().getSimpleName(), "Database upgrade. Version " + _oldVersion + " to " + _newVersion);
}
}