我是一個Android猴, 主要從Android端來談一下對各種結構的看法,總結一下基礎架構的核心是什么?因此,這篇文章對于對架構一詞不是很了解,或者一知半解的朋友來講, 梳理一下大局觀;并且我覺得同樣的作為移動開發,IOS與Android的架構差異化并不是很大。
從移動端談架構,其實有點夸大了。因為移動端的項目往往不是很大,或者模塊不是很大。一般架構這個詞,可能用在Web端比較好一點,也更有效點,架構好了,意味這更穩健的運行效率, 更大體量。從移動端來談架構,無非是讓代碼可以優雅一點,解決一下常見的耦合等問題。
從Android誕生至今,移動端的架構變更了很多次,從最初的MVC到MVP, 從冷門的Flutter(由RN引入到移動端)到Google的AAC/MVVM;好像架構的思想一直在變,但是大抵都是換湯不換藥的,為什么這么說呢 ? 讓我們來總結一下。
MVC
MVP
MVVM
Flutter
AAC
以上的架構中MVX系列先不說,剩下的兩個是什么? 先來解釋一下。
Flutter
此Flutter非目前炒得火熱的Flutter, 而是由React Native衍生而來的,適用于移動端的框架。是的,這也是一種框架思想。Flutter的元素分為3種: View(不必多說), Model(也不必解釋吧), Store(這個要說一下,用于處理Action的核心類,類似Presenter的作用), Dispatcher(Action路由), Action(事件)。該框架類似于MVP, 只是通信模塊由接口,改為路由系統。
AAC([Android Architecture components]
不知道的可以查看一下官網:https://developer.android.com/topic/libraries/architecture/guide.html
public class UserProfileViewModel extends ViewModel {
private String userId;
private User user;
public void init(String userId) {
this.userId = userId;
}
public User getUser() {
return user;
}
}
public class UserProfileFragment extends Fragment {
private static final String UID_KEY = "uid";
private UserProfileViewModel viewModel;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
String userId = getArguments().getString(UID_KEY);
viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);
viewModel.init(userId);
}
@Override
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.user_profile, container, false);
}
}
以上是來自官網的AAC-Demo的源碼,看結構其實就清楚了,如果允許自己命名的話,或者我們可以稱為MVVM了;ViewModel這個模塊,是不是像極了Controller或者Presenter。
通過以上框架的分析, 我們應該可以得出結論:
局部的架構,為什么說是局部架構?因為項目級得架構肯定就是大結構的組件化與插件化。
1.分層
從代碼的實現解耦。對于現在狹義上的架構,M和V是必然單獨的兩層,因為數據處理和UI嘛,界限很清楚。難以劃分層次的就是邏輯實現,也就是我們的業務處理。而Controller, Presenter, VM這些模塊的功能都是一致的
所以分層的維度幾乎已經確定,就是 數據處理(Model), UI顯示(View), 業務處理(X)
2.通信
不同的模塊,好像層次劃分都是一致的,雖然騷氣的取了不同的名字。但是區別就在于通信方式。
MVC/AAC的通信方式是對象, View與Model的交互是完全通過對象來實現的,如下
public class Model {
/**
* 這是Model模塊,負責數據處理。處理的方式不盡相同,但是本質一樣。如網絡請求, 數據庫, 文件等等
*/
public void postLogin(String username, String password, Callback callback) {
// 執行登陸請求,驗證帳號密碼是否正確
callback.onResponse("登錄結果");
}
public interface Callback {
void onResponse(String result);
}
}
public class View {
X controllerOrPresenter;
public void onClick() {
// 點擊登錄
controllerOrPresenter.login("xxx", "xxx", new Model.Callback() {
@Override
public void onResponse(String result) {
if ("success".equals(result)) {
// 登錄成功,則提示登錄成功,保存信息
} else {
// 登錄失敗,則提示失敗
}
}
});
}
}
public class X {
Model model;
View view;
public void login(String username, String password, Model.Callback callback) {
// 驗證帳號, 密碼格式是否正確
model.postLogin(username, password, callback);
}
}
以上代碼是MVC模式, 是不是這么處理的?通過持有對象來達到模塊之間的通信。當時可能會覺得這樣耦合度比較高,這樣就出現了解決方案。于是MVC做成了MVP(哦,當然,那時候還不這么叫),通過接口的方式來通信,或許接口化沒那么徹底而已。AAC通過綁定頁面的周期做到隨頁面釋放并包裝
MVP的通信方式是完全接口式通信, V和P之間,甚至M和P之間也可以
MVP的方式來解釋一下,V和P分別實現自己的接口, IV和IP。然后分別傳如自己的接口達到調用目的。代碼如下:
public interface IP {
void login(String username, String password);
}
public interface IV {
void loginSuccess();
void loginFailed();
}
public class View implements IV{
X iP;
public void onClick() {
// 點擊登錄
iP.login("xxx", "xxx");
}
@Override
public void loginSuccess() {
// 保存信息
// 提示登錄成功了
}
@Override
public void loginFailed() {
// 提示登錄失敗
}
}
public class X implements IP{
/**
* X 相當于MVP中的Presenter
*/
Model model;
IV iV;
public void login(String username, String password) {
// 驗證帳號, 密碼格式是否正確
model.postLogin(username, password, new Model.Callback() {
@Override
public void onResponse(String result) {
if("success".equals(result)) {
// 登錄成功
iV.loginSuccess();
}else {
// 登錄失敗
iV.loginFailed();
}
}
});
}
}
以上是MVP的通信方式,完全的接口相互調用。可能一些架構思維比較前衛的公司,在前期就把MVC改造成了如今的MVP,還是那句話,只是那時候不那么叫罷了。
Flutter是以路由機制來實現解耦通信
在現在組件化風行的時代,相信各位對路由沒那么陌生了。就算不了解, 那么路由器總知道吧,那么先說路由器。
路由或路由器,分兩個模式: 接收信號, 發出信號。分別是多對一和一對多的關系。
舉個例子, 現實中的路由器,接收只有一個入口,但發出口有很多個,畢竟如果只有一個出口,那么路由器就沒用了。在路由機制中,入口也可以有多個。所以就是上面說的,接收是多對一(一當然是路由器), 發出是一對多(一還是路由器)。
簡單由一個圖來說明一下路由機制,圖不是很規整,明白就好。
在Flutter模式下,我們如何對應呢?
M/V/P或C不同模塊之間不能有耦合,即不持有對象,且不持有接口,完全解耦。那么各模塊怎么通信呢?通過向路由器發信號。所以M V P/C 都是上圖的信號源
M/V/P或C 不同模塊要交互,那么怎么得到信號呢? 這時候他們的角色就轉變了,不僅可以發信號,也可以接口信號,來做對應處理。 所以M V P/C 也同樣是上圖的手機(信號接口器)
以上的解釋, 不同模塊既可以發出信號,也可以接收信號。和Android中的一個組件很相似,就是Handler. Handler既可以發出消息,同時消息又在里面處理。
既然有發送信號的, 有接收信號的,那么必然有一個路由器負責接收與發送,在Flutter中就是Dispatcher。Dispatcher保存了不同信號與接收器的對應關系, 以此完成消息的分發
上面都提到了消息,現在正式的介紹一下,消息是角色是Action
看一下Flutter的元素
Model
View
Store(Controller或Presenter) 處理業務邏輯
Action 消息對象,帶有動作與數據
Dispathcer 路由,管理消息與發送消息,保存有一張對應表
接下來通過代碼了解一下
public class Model {
public void postLogin(User user, Callback callback) {
// 請求登錄
callback.onResponse(1);
}
public interface Callback {
void onResponse(int result);
}
}
public class User {
public String username;
public String password;
public User(String username, String password) {
this.username = username;
this.password = password;
}
}
public class View implements Dispatcher.IReceiver {
public void onCreate() {
Dispatcher.getDispatcher().register(this);
}
public void onDestroy() {
Dispatcher.getDispatcher().unregister(this);
}
void onClick() {
Dispatcher.getDispatcher()
.sendEvent(
new Action("login", new User("xxx", "xxx")));
}
@Override
public void onReceive(Action action) {
if (action.name.equals("login-success")) {
// 登錄成功提示
} else {
// 登錄失敗提示
}
}
}
public class X implements Dispatcher.IReceiver{
Model model;
public X() {
model = new Model();
Dispatcher.getDispatcher().register(this);
}
public void clearX() {
model = null;
Dispatcher.getDispatcher().unregister(this);
}
@Override
public void onReceive(Action action) {
if(action.name.equals("login")) {
model.postLogin((User) action.data, new Model.Callback() {
@Override
public void onResponse(int result) {
if(result == 1) {
// 登錄成功
Dispatcher.getDispatcher().sendEvent(new Action("login-success", null));
}else {
// 登錄失敗
Dispatcher.getDispatcher().sendEvent(new Action("login-failed", null));
}
}
});
}
}
}
public class Action<T> {
public String name; // 執行動作,比如“登錄”
public T data; // 數據,比如username, password
public Action(String name, T data) {
this.name = name;
this.data = data;
}
}
public class Dispatcher {
private static Dispatcher dispatcher = new Dispatcher();
private List<IReceiver> receivers = new ArrayList<>();
private Dispatcher(){}
public static Dispatcher getDispatcher() {
return dispatcher;
}
public void register(IReceiver receiver) {
if (receivers.contains(receiver)) {
throw new IllegalStateException("receiver has been registerd yet!");
} else {
receivers.add(receiver);
}
}
public void unregister(IReceiver receiver) {
if (receivers.contains(receiver)) {
receivers.remove(receiver);
}
}
public void sendEvent(Action action) {
if (action != null && action.name != null && action.name.length() > 0) {
for (IReceiver r : receivers) {
if (r != null) {
r.onReceive(action);
}
}
}
}
public interface IReceiver {
void onReceive(Action action);
}
}
解析一下以上代碼, 首先我們說過, 所有模塊既是消息發出者,也是消息接收者。代碼中,View和X都分別發出了消息進行登錄以及登錄結果成功或失敗;同時View和X也都注冊了接收器的接口,在onReceiver中可以接收消息。這樣做的好處是什么? View和X完全沒有耦合,既不持有對象, 也不持有接口,中間的通信都是通過Dispatcher進行分發的,解耦已經很徹底了。Dispathcer就是路由器,負責接收,并分發消息,應該很好理解,哪個組件想接收消息,那么就注冊一個接收器,這樣有合適的消息自然就接收到了。
當然,以上的代碼有點簡陋,可以從不同組件再行封裝,比如Action, 比如Dispatcher, 比如BaseView等等。我們可以通過給路由注冊機制添加Group與Tag(Action name)概念來優化效率問題,這里只是說思想。
我們知道了什么?
- 我稱移動端的架構思維為MVX,即是說按這個規則的分工被開發市場所接受了,我們不用費盡心思考慮狹義架構的分層問題了,就沿用Model-View-X來就可以。當然還可以自己加一些輔助的模塊層,如Worker負責異步, Converter負責轉換, Verify負責校驗等
2.移動端目前的架構,差異化在于通信機制。通過以上說明,通信機制主要分為3種:
1) 對象持有
2) 接口持有
3) 路由
- 通信方式中,對象持有是比較原始的,解耦率最低,建議放棄; 接口持有是個不錯的選擇,極大程度上實現解耦的訴求,但是解耦不徹底,相互持有交互方的接口。 路由機制也是個不錯的選擇,可以實現完全解耦,就像組件化一樣。但是路由機制的設計是個技術難點,怎么設計效率最高?更健壯?代碼可查閱性更好?這些都是值得思考的問題。
4.對于路由機制的優化,阿里的ARouter(用于組件通信)中,采用了分組的模式,我們可以采用;其次可以根據AnnotationProcessor的處理,為每一個注冊接收器的組件實現一個SupportActions來確保消息只發送給注冊了指定類型的模塊,也是個不錯的選擇。
通過以上分析與總結,可以看到,移動端的框架核心是一定的,只要理解了架構的核心思想,其實出多少新的框架,不過是加了一些解決部分問題的實現罷了。看懂了這篇文章,可能再出什么架構模式,或許看一眼就明白是怎么實現了呢?
如果你覺得這篇文章對你有幫助,請幫我打Call, 謝謝!!