【译】Android 架构:基于消息的 MVC
译自 <http://mindtherobot.com/blog/675/android-architecture-message-based-mvc/>
本文地址:http://www.cnblogs.com/sunshy/archive/2012/07/09/2582712.html
注:由于本文原文链接 mindtherobot 网站被浏览器报告为恶意网站,故本文清除了链接内容,但是本人访问过该链接,并未发现异常,如果放心的话可以复制链接地址自己去浏览。文中有个别地方在原文中有链接,本文一律加上<>处理,不提供直接链接。
译序:Android 平台的开发框架貌似有点 MVC 的意思,比如用 xml 文件做 View 之类的,但是这样写出的代码还是会出现冗长的 Activity。网上很多文章在介绍 Android MVC 的时候都没有根本解决这一问题。偶尔发现这么一篇文章讲的很不错,于是决定翻译过来和大家共享。有些语言和原文不太一致,但都是为了表词达意用了自己的话,但相信还是保留了原作者的意思。第一次翻译技术文章,不当之处欢迎指正。
想在你的 Android 应用里优雅地分离出应用状态、用户交互和数据表现吗?
一个开发平台的设计者是的不会有闲工夫为你量身打造一个高层的应用架构的。但是你恰恰可以利用这一点,充分利用你的自由,根据应用需求和规模来实现你自己的架构。如果你仅仅要做些简单的小玩意儿,那么好吧,你可以随意在你的 widgets(像 text fields、spinners 之类的)中存储数据,然后就在诸如 OnClickListener 中去操控它们。可是话又说回来,如果你要写些宏伟的应用,或者想让你的小玩意儿更丰富迷人一点呢?那你还是考虑考虑分个层吧,因为一个好的架构会鼎力支持你去添加新特性,满足你梦寐以求的高性能、高灵活性、高度响应性等等需求。当然,你的代码也会因此摆脱蓬头垢面心乱如麻的糟糕状态,从此过上幸福的生活……
好吧,说正经的。在接下来的几分钟的光阴里,我将带你看一看如何根据大名鼎鼎的 MVC,来把你的代码大卸三块。呃……没这么恐怖,三块之间还是藕断丝连的,比如我会告诉你怎么用 Android 的 messaging 框架,来把 view (V) 和 controller (C) 连一连。我把这一实践应用到我自己的代码中,也许它并非 100% 的符合学术标准,也可能不是对每一个 app 都能成为天作之合,但是当我的应用变得越来越庞大的时候,我还是很享受它所给我带来的好处的。
Intro
关于这个 MVC,我就不多说了吧,到处都可以找到它的“广告”。我也不介意你到我写的 <an article on how to apply this architecture to a single component> 里面看一看。你最好先了解了解 Model,View,Controller 这三个东西。另外呢,如果你还没有看过 <the article on Handler>,最好还是去看看吧。尽管这篇文章里没有完整描述 messaging 这朵奇葩,你还是可以在里面打听到一些关于线程模型的八卦。接下来我会一直带你绕着一个样例转一转,转到最后你会看到样例的源代码。
Part 1: Model
把 Model 层泛化,并且让它只能接触到主逻辑,这是一个不错的开端。我们的样例看起来其实是一目了然加了无趣味,但还是有些有趣的地方。首先,我们有一个单独的类来包含 model 的“数据”——指的是,在应用的生命周期中, Model 层内可能发生变化的各种状态。
1 @Immutable 2 public final class ModelData implements Serializable { 3 4 private static final long serialVersionUID = 1L; 5 6 private final int answer; 7 8 public ModelData(int answer) { 9 this.answer = answer; 10 } 11 12 public final int getAnswer() { 13 return answer; 14 } 15 }
呃,你没有看走眼,我的确喜欢加个标注叫 @Immutable,这样一来我就会铭记这个类事实上是永恒不变的。这样有很多好处,比如说我能够在线程之间共享一个类的实例。另外,我还让这个类 Serializable,这样可以允许我实现一些例如 “Save/Load” 之类的特性。
至于 Model 类本身,它长成这个样子:
1 @ThreadSafe 2 public class Model { 3 public interface Listener { 4 void onModelStateUpdated(Model model); 5 } 6 7 private ModelData data = new ModelData(0); 8 9 private final List<Listener> listeners = new ArrayList<Listener>(); 10 11 public Model() { 12 13 } 14 15 public final ModelData getData() { 16 synchronized (this) { 17 return data; 18 } 19 } 20 21 public final void updateData() { // takes a while! 22 SystemClock.sleep(5000); 23 ModelData newData = new ModelData(new Random().nextInt(10) + 1); 24 25 synchronized (this) { 26 data = newData; 27 } 28 29 synchronized (listeners) { 30 for (Listener listener : listeners) { 31 listener.onModelStateUpdated(this); 32 } 33 } 34 } 35 36 public final void addListener(Listener listener) { 37 synchronized (listeners) { 38 listeners.add(listener); 39 } 40 } 41 42 public final void removeListener(Listener listener) { 43 synchronized (listeners) { 44 listeners.remove(listener); 45 } 46 } 47 }
方法 updateData() 要花一点时间来执行,在真实世界里,它可能是在请求服务器、执行繁重的计算等。data 被 synchronized 好好地保护了起来,不会被并发地访问。Listeners 也能安全地从并发线程中被添加或删除。如果你已经和 MVC 有过交往,你会意识到 model 的 listeners 是这个模式的中坚力量——它们让你能把任何代码附加到 model 上,而 model 却对此一无所知(因此 model 的代码就可以免受任何不相干的逻辑注入的伤害)。
现在来看最有意思的部分——Controller 层。
Part 2: Controller
其实吧,你可以通过很多种方式实现 Controller 这一层。 我选择的方式是基于 Handler, HandlerThread 与 Message 类的方法。当然,你多少要懂点它们的工作方式。
说到底,Android 平台让你可以通过这几个类来实现应用中各部分之间高效、安全且强大的 messaging 通信。在我们的例子里,我想做的的东西是:
- 送给 Controller 一个 inbox handler——这样 View 层就能给他写信(message)了,信的内容可以是“用户让我退出”或者“用户让我更新这些数据”以及其它一些甜言蜜语。
- 送给 Controller 一个或多个 outbox handlers——这样 Controller 就也能给 View(或 Views,比较多情,你懂得)写信(message)了,比如“ model 已经更新完了”或者“你自杀吧,我要退出了”,等等。
- 通过这样的方式,View 只能把用户事件装在信封(message)里寄给 Controller,并不用自己决定怎么对用户事件进行反应。一旦 Controller 给 View 寄来一封信(message),View 就会无脑地处理 Controller 告诉她的事情,并且给用户呈现出一些应用的状态。
通过这样一个悲情的故事,我们很干净地就实现了 MVC 的架构,虽然有点不太仁义。
由于 Handler 需要一个 Looper,我们需要选择一个线程,来运行 Controller 的 inbox handler。我们可以选择在 UI 线程中(它既能处理 UI 消息,又能处理 Controller 的消息)或者一个新的单独的 HandlerThread 中来运行。通常我们的 Controller 不会被困在一个很慢的操作中处理它的消息,这样它才能即使对 View 作出相应。所有很慢的操作都会在后台线程中运行。因此我们可以放心让 UI 线程来处理 Controller 消息。但是我还是选择运行一个单独的线程,仅仅是因为这样看起来架构会比较干净,也可能因为这样 Controller 的响应会有一点点的更快吧。既然 messageing 框架帮助我们摆脱了并发问题的牢笼,我们可以不用修改大量代码,就轻易地从一个 solution 跳到另外一个。
来看看我们管理 inbox handler 和 outbox handler 的代码:
1 public class Controller { 2 3 // ... some code omitted ... 4 5 private final HandlerThread inboxHandlerThread; 6 private final Handler inboxHandler; 7 private final List<Handler> outboxHandlers = new ArrayList<Handler>(); 8 9 // ... some code omitted ... 10 11 public Controller(Model model) { 12 this.model = model; 13 14 inboxHandlerThread = new HandlerThread("Controller Inbox"); // note you can also set a priority here 15 inboxHandlerThread.start(); 16 17 // ... some code omitted ... 18 19 inboxHandler = new Handler(inboxHandlerThread.getLooper()) { 20 @Override 21 public void handleMessage(Message msg) { 22 Controller.this.handleMessage(msg); 23 } 24 }; 25 } 26 27 public final void dispose() { 28 // ask the inbox thread to exit gracefully 29 inboxHandlerThread.getLooper().quit(); 30 } 31 32 public final Handler getInboxHandler() { 33 return inboxHandler; 34 } 35 36 public final void addOutboxHandler(Handler handler) { 37 outboxHandlers.add(handler); 38 } 39 40 public final void removeOutboxHandler(Handler handler) { 41 outboxHandlers.remove(handler); 42 } 43 44 final void notifyOutboxHandlers(int what, int arg1, int arg2, Object obj) { 45 if (outboxHandlers.isEmpty()) { 46 Log.w(TAG, String.format("No outbox handler to handle outgoing message (%d)", what)); 47 } else { 48 for (Handler handler : outboxHandlers) { 49 Message msg = Message.obtain(handler, what, arg1, arg2, obj); 50 msg.sendToTarget(); 51 } 52 } 53 } 54 55 // ... some code omitted ... 56 }
这些并不是 Controller 的完整代码。先说点其它的我们马上回来。你现在能看到的是我们如何初始化 inbox handler 与它的线程,如何提交一个 handler 到 outbox handlers 中去,以及如何用一个消息通知所有的 outbox handlers。
有了这些代码,我们可以得到 inbox handler,并可以从 View 层向它发送消息,同时我们有绑定了 View 的 outbox handler,能够让 Controller 与它的 Views 有效地交换异步消息。
为了能处理收到的消息并做点什么,我们需要在 Controller 类中实现 handleMessage() 方法。在大部分应用中,你都可能想在这里实现一个设计模式中的状态模式的实例,因为根据应用正在处理的工作的不同,我们可能需要对一个消息进行不同的处理,乃至忽略一些消息。例如,在我们的应用中,一旦你开始刷新模型了,你肯定不希望用户在刷新完成之前再刷新一次。为了表明这一点,我给出了 ControllerState 接口和它的 ReadyState 的实现:
1 public interface ControllerState { 2 boolean handleMessage(Message msg); 3 } 4 5 final class ReadyState implements ControllerState { 6 7 private final Controller controller; 8 9 public ReadyState(Controller controller) { 10 this.controller = controller; 11 } 12 13 @Override 14 public final boolean handleMessage(Message msg) { 15 switch (msg.what) { 16 case V_REQUEST_QUIT: 17 onRequestQuit(); 18 return true; 19 case V_REQUEST_UPDATE: 20 onRequestUpdate(); 21 return true; 22 case V_REQUEST_DATA: 23 onRequestData(); 24 return true; 25 } 26 return false; 27 } 28 29 private void onRequestData() { 30 // send the data to the outbox handlers (view) 31 controller.notifyOutboxHandlers(C_DATA, 0, 0, controller.getModel().getData()); 32 } 33 34 private void onRequestUpdate() { 35 // we can't just call model.updateState() here because it will block 36 // the inbox thread where this processing is happening. 37 // thus we change the state to UpdatingState that will launch and manage 38 // a background thread that will do that operation 39 40 controller.changeState(new UpdatingState(controller)); 41 } 42 43 private void onRequestQuit() { 44 controller.quit(); 45 } 46 }
把 Controller 和它的状态们(stages)放到同一个 package 中,我们可以让它们使用默认访问级别就能访问到 Controller 的内部。我就不把 UpdatingState 的代码贴这儿了,在页底的附件中都有。
这里看看 Controller 是怎么把消息处理委托给它的 state 的:
1 public class Controller { 2 3 // ... some code omitted ... 4 private ControllerState state; 5 6 public Controller(Model model) { 7 this.model = model; 8 9 // ... some code omitted ... 10 11 this.state = new ReadyState(this); 12 13 // ... some code omitted ... 14 } 15 16 // ... some code omitted ... 17 18 private void handleMessage(Message msg) { 19 Log.d(TAG, "Received message: " + msg); 20 21 if (! state.handleMessage(msg)) { 22 Log.w(TAG, "Unknown message: " + msg); 23 } 24 } 25 26 final Model getModel() { 27 return model; 28 } 29 30 final void quit() { 31 notifyOutboxHandlers(C_QUIT, 0, 0, null); 32 } 33 34 final void changeState(ControllerState newState) { 35 Log.d(TAG, String.format("Changing state from %s to %s", state, newState)); 36 state = newState; 37 } 38 }
好了,Controller 大概就是这般模样,现在让我们介绍个 View 给它认识认识吧。
Part 3: View
不用过多解释了,我猜你已经知道怎么做了。上代码(DemoActivity):
public class DemoActivity extends Activity implements Handler.Callback, OnClickListener { private static final String TAG = DemoActivity.class.getSimpleName(); private Controller controller; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ((Button) findViewById(R.id.update)).setOnClickListener(this); ((Button) findViewById(R.id.quit)).setOnClickListener(this); controller = new Controller(new Model()); controller.addOutboxHandler(new Handler(this)); // messages will go to .handleMessage() controller.getInboxHandler().sendEmptyMessage(V_REQUEST_DATA); // request initial data } @Override protected void onDestroy() { // I think it is a good idea to not fail in onDestroy() try { controller.dispose(); } catch (Throwable t) { Log.e(TAG, "Failed to destroy the controller", t); } super.onDestroy(); } @Override public boolean handleMessage(Message msg) { Log.d(TAG, "Received message: " + msg); switch (msg.what) { case C_QUIT: onQuit(); return true; case C_DATA: onData((ModelData) msg.obj); return true; case C_UPDATE_STARTED: onUpdateStarted(); return true; case C_UPDATE_FINISHED: onUpdateFinished(); return true; } return false; } private void onUpdateStarted() { ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar); progressBar.setVisibility(View.VISIBLE); } private void onUpdateFinished() { ProgressBar progressBar = (ProgressBar) findViewById(R.id.progress_bar); progressBar.setVisibility(View.GONE); // request the updated data controller.getInboxHandler().sendEmptyMessage(V_REQUEST_DATA); } private void onData(ModelData data) { TextView dataView = (TextView) findViewById(R.id.data_view); dataView.setText("The answer is "+ data.getAnswer()); } private void onQuit() { Log.d(TAG, "Activity quitting"); finish(); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.update: controller.getInboxHandler().sendEmptyMessage(V_REQUEST_UPDATE); break; case R.id.quit: controller.getInboxHandler().sendEmptyMessage(V_REQUEST_QUIT); break; } } }
在这一样例中,Activity 实际上占据了 Controller 的实例,而这个 Controller 是控制着 model 的。然而,如果你觉得 Service 的生命周期更能匹配 Controller/Model 的生命周期的话,你也可以考虑把 Controller 转移到一个 Service 中。
Summary
你现在应该很清楚了,基于 message 的方法的的确确能允许我构建一个强大的、复杂的 Controller 与一个完全百依百顺的贤惠的 View。这样我们就有了一个很好的 MVC 的实现。
正如我在开头所写的那样,这一解决方案从理论上讲不一定完全有效,也不一定就适合你的 app,但是我已经经历了构建一个非常复杂应用的过程,这中间有很多种状态,并且它完全依赖于消息。这一架构会有很多弱点,比如你要在某个地方聚合 message 的代码(见 ControllerProtocol)并在文档里定义很多 message 参数。但是别忘了好处:它很好地实现了层间的分离,当你的 app 越长越大时,你更会体会到这一框架的美妙。
源代码:async-mvc.zip