之前我们已经讨论了模型(Models),视图(Views)和控制器(Controllers)。现在我们需要把他们组合在一次成为真正符合 MVC 规范。

Activity 是我们程序的每一个新视图的入口, 我们需要在 Activity 的 onCreate 方法实例化模型和控制器。其实一个典型的 MVC 模型视图是不会去实例化模型和控制器的,但是..android 有点特殊。模型一旦创建了,我们需要把 activity 注册到模型中。再者,如果你想从控制器获取消息,你也需要向控制器注册。让我们看看他们长什么模样的。

##Activity:

:::java
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
 
    counter = new CounterVo();
    counter.addListener(this);
    controller = new TapController(counter);
    controller.addOutboxHandler(new Handler(this));
    // ... other set up code like referencing widgets/views ...
}

这里视图在模型和控制器上都注册了。让我们看看模型对于视图的回调函数。

:::java
@Override
public void onChange(CounterVo counter) {
    mHandler.sendEmptyMessage(UPDATE_VIEW);
}

一旦模型通知视图有改变,我们就在 UI 线程修改 UI控件。这就是你数据的绑定。现在让我们来看看处理从控制器发来的信息。

:::java
@Override
public boolean handleMessage(Message msg) {
    switch(msg.what) {
        case TapListController.MESSAGE_MODEL_UPDATED:
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    adapter.notifyDataSetChanged();
                }
            });
            return true;
    }
    return false;
}

就跟之前讨论的那样,这个实现很像控制器处理消息的实现。这里视图选择性地接收 MESSAGE_MODEL_UPATED 消息,然后通知列表适配器去更新显示。如果有其他非 MESSAGE_MODEL_UPDATED 的消息传进来,它会忽略掉的。也就是说它不是处理所有的信息。你可以增加或减少视图可接收的信息。

我可以在 Activity 中开线程或者创建后台服务吗?

记住视图的特性。我们一直努力让视图变得足够笨,它只是做我们叫它做的事情。一旦 Activity 很不幸要做很多其他的事情,我们应该保持他们的逻辑足够简单。在 onRestartonResumeonDestory中放代码是可以的,但是如果逻辑太多和复杂,我们应该把他们抽到控制器去实现,只要写个代理就可以了。

##Controller:

控制器向模型传递了引用,然后与视图用消息来沟通。它是 MVC 的大脑。我们已经讨论了控制器如何收发消息了。接着我们讨论它如何处理这些信息。答案就是:想怎么办就怎么办!一个好的控制器内部如何处理这些信息的逻辑需要封闭(开放封闭原则)。现在就讨论一下技术上如何实现。在我们的例子中,当接收到 TapListController.MESSAGE_INCREMENT_COUNTER ,控制器就把模型的 count 加一,然后通过 DAO 代理来实现数据的持久化(这个之后会说),最后通知外部有更新。所有这些都在一个独立的线程里面完成。所有这些实现都是外部不可见的。让我们看看代码:

:::java
package com.musselwhizzle.tapcounter.controllers;
 
import java.util.ArrayList;
 
import android.os.Handler;
import android.os.HandlerThread;
 
import com.musselwhizzle.tapcounter.daos.CounterDao;
import com.musselwhizzle.tapcounter.vos.CounterVo;
 
public class TapListController extends Controller {
    private static final String TAG = TapListController.class.getSimpleName();
    private HandlerThread workerThread;
    private Handler workerHandler;
 
    public static final int MESSAGE_GET_COUNTERS = 1;
    public static final int MESSAGE_MODEL_UPDATED = 2;
    public static final int MESSAGE_DELETE_COUNTER = 3;
    public static final int MESSAGE_INCREMENT_COUNTER = 4;
    public static final int MESSAGE_DECREMENT_COUNTER = 5;
 
    private ArrayList model;
    public ArrayList getModel() {
        return model;
    }
 
    public TapListController(ArrayList model) {
        this.model = model;
        workerThread = new HandlerThread("Worker Thread");
        workerThread.start();
        workerHandler = new Handler(workerThread.getLooper());
    }
 
    @Override
    public void dispose() {
        super.dispose();
        workerThread.getLooper().quit();
    }
 
    @Override
    public boolean handleMessage(int what, Object data) {
        switch(what) {
            case MESSAGE_GET_COUNTERS:
                getCounters();
                return true;
            case MESSAGE_DELETE_COUNTER:
                deleteCounter((Integer)data);
                getCounters();
                return true;
            case MESSAGE_INCREMENT_COUNTER:
                changeCount(1, (CounterVo)data);
                getCounters();
                return true;
            case MESSAGE_DECREMENT_COUNTER:
                changeCount(-1, (CounterVo)data);
                getCounters();
                return true;
        }
        return false;
    }
 
    private void changeCount(final int amount, final CounterVo counter) {
        workerHandler.post(new Runnable() {
            @Override
            public void run() {
                synchronized (counter) {
                    counter.setCount(counter.getCount() + amount);
                    CounterDao dao = new CounterDao();
                    dao.update(counter);
                }
            }
        });
 
    }
 
    private void getCounters() {
        workerHandler.post(new Runnable() {
            @Override
            public void run() {
                CounterDao dao = new CounterDao();
                ArrayList counters = dao.getAll();
                synchronized (model) {
                    while(model.size() > 0) {
                        model.remove(0);
                    }
                    for (CounterVo counter : counters) {
                        model.add(counter);
                    }
                    notifyOutboxHandlers(MESSAGE_MODEL_UPDATED, 0, 0, null);
                }
            }
        });
    }
 
    private void deleteCounter(final int itemId) {
        workerHandler.post(new Runnable() {
            @Override
            public void run() {
                CounterDao dao = new CounterDao();
                dao.delete(itemId);
            }
        });
    }
}

##其他一些思考:

在这个程序中,每一个 activity/view 都有特定的 controller 和 model 。TapActivity 有 TapController 和 TapListActivity 有 TapListController 。有时我会允许当 activity 销毁的时候 model 也销毁。在另外一些时候, 我有一个程序级的 model 这个 model 有很多 activity 绑定在上面,那么我就不想某个 activity 去销毁它。我通过使用单例模式来实现这个程序级 model 的功能。虽然这不应该在这个系列中讨论,但是我觉得有必要提一下。一个使用单例的例子是有一个 RSS 程序,其中一个 activity 显示摘要,另一个 activity 显示详细内容,拿不想在两个 activity 之间重复加载数据,这个时候你就可以使用这个模式来保证模型在内存中。无论如何,你是否销毁模型不那么重要,它仍然是 MVC 。你只需要根据需求来修改就好了。

##总结

到这里,你应该很清楚的知道这三个部分如何在 android 程序中有机结合了。花些时间来检查一下代码吧。但你看到 TapController 的时候可能会觉得陌生,但是它所做的只是代理消息而已。我们会深入讨论一下状态模式的使用和为什么要使用它。