Android开发Android技术知识Android开发

MVC、MVP、MVVM,谈谈我对Android应用架构的理解

2019-04-06  本文已影响3人  迷途小码农h

正文

先上结论:

MVC

简单的说:我们平时写的Demo都是MVC,controller就是我们的activity,model(数据提供者)就是读取数据库,网络请求这些我们一般有专门的类处理,View一般用自定义控件。

但这一切,只是看起来很美。

想象实际开发中,我们的activity代码其实是越来越多,model和controller根本没有分离,控件也需要关系数据和业务。

所以说,MVC的真实存在是MC(V),Model和Controller根本没办法分开,并且数据和View严重耦合。这就是它的问题。举个简单例子 :获取天气数据展示在界面上

public interface WeatherModel { 
    void getWeather(String cityNumber, OnWeatherListener listener); 
} 
................ 
public class WeatherModelImpl implements WeatherModel { 
        @Override 
    public void getWeather(String cityNumber, final OnWeatherListener listener) { 
        /*数据层操作*/ 
        VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html, 
                Weather.class, new Response.Listener<weather>() { 
                    @Override 
                    public void onResponse(Weather weather) { 
                        if (weather != null) { 
                            listener.onSuccess(weather); 
                        } else { 
                            listener.onError(); 
                        } 
                    } 
                }, new Response.ErrorListener() { 
                    @Override 
                    public void onErrorResponse(VolleyError error) { 
                        listener.onError(); 
                    } 
                }); 
    } 
}
public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener { 
    private WeatherModel weatherModel; 
    private EditText cityNOInput; 
    private TextView city; 
    ... 
    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
        weatherModel = new WeatherModelImpl(); 
        initView(); 
    } 
    //初始化View 
    private void initView() { 
        cityNOInput = findView(R.id.et_city_no); 
        city = findView(R.id.tv_city); 
        ... 
        findView(R.id.btn_go).setOnClickListener(this); 
    } 
    //显示结果 
    public void displayResult(Weather weather) { 
        WeatherInfo weatherInfo = weather.getWeatherinfo(); 
        city.setText(weatherInfo.getCity()); 
        ... 
    } 
    @Override 
    public void onClick(View v) { 
        switch (v.getId()) { 
            case R.id.btn_go: 
                weatherModel.getWeather(cityNOInput.getText().toString().trim(), this); 
                break; 
        } 
    } 
    @Override 
    public void onSuccess(Weather weather) { 
        displayResult(weather); 
    } 
    @Override 
    public void onError() { 
        Toast.makeText(this, 获取天气信息失败, Toast.LENGTH_SHORT).show(); 
    } 
    private T findView(int id) { 
        return (T) findViewById(id); 
    } 
}

简单分析下这个例子:

完美的体现了MVC的两大缺点,下面看看MVP怎么解决第一个缺点的

MVP

看上图可以看出,从MVC中View被拆成了Presenter和View,真正实现了逻辑处理和View的分离。下面写一个实例:模拟一个登录界面,输入用户名和密码,可以登录以及清除密码

/** 
定义业务接口 
*/ public interface IUserBiz { 
    public void login(String username, String password, OnLoginListener loginListener); 
} 
/** 
结果回调接口 
*/ public interface OnLoginListener { 
    void loginSuccess(User user); 
    void loginFailed(); 
} 
/** 
具体Model的实现 
*/ public class UserBiz implements IUserBiz { 
    @Override 
    public void login(final String username, final String password, final OnLoginListener loginListener) 
    { 
        //模拟子线程耗时操作 
        new Thread() 
        { 
            @Override 
            public void run() 
            { 
                try 
                { 
                    Thread.sleep(2000); 
                } catch (InterruptedException e) 
                { 
                    e.printStackTrace(); 
                } 
                //模拟登录成功 
                if ("zhy".equals(username) && "123".equals(password)) 
                { 
                    User user = new User(); 
                    user.setUsername(username); 
                    user.setPassword(password); 
                    loginListener.loginSuccess(user); 
                } else 
                { 
                    loginListener.loginFailed(); 
                } 
            } 
        }.start(); 
    } 
}

上面说到View层是以接口的形式定义,我们不关心数据,不关心逻辑处理!只关心和用户的交互,那么这个登录界面应该有的操作就是(把这个界面想成一个容器,有输入和输出)。

获取用户名,获取密码,现实进度条,隐藏进度条,跳转到其他界面,展示失败dialog,清除用户名,清除密码。接下来定义接口:

public interface IUserLoginView  {  
    String getUserName();  
    String getPassword();  
    void clearUserName();  
    void clearPassword();  
    void showLoading();  
    void hideLoading();  
    void toMainActivity(User user);  
    void showFailedError();  
}

然后Activity实现这个这个接口:

public class UserLoginActivity extends ActionBarActivity implements IUserLoginView { 
    private EditText mEtUsername, mEtPassword; 
    private Button mBtnLogin, mBtnClear; 
    private ProgressBar mPbLoading; 
    private UserLoginPresenter mUserLoginPresenter = new UserLoginPresenter(this); 
    @Override 
    protected void onCreate(Bundle savedInstanceState) 
    { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_user_login); 
        initViews(); 
    } 
    private void initViews() 
    { 
        mEtUsername = (EditText) findViewById(R.id.id_et_username); 
        mEtPassword = (EditText) findViewById(R.id.id_et_password); 
        mBtnClear = (Button) findViewById(R.id.id_btn_clear); 
        mBtnLogin = (Button) findViewById(R.id.id_btn_login); 
        mPbLoading = (ProgressBar) findViewById(R.id.id_pb_loading); 
        mBtnLogin.setOnClickListener(new View.OnClickListener() 
        { 
            @Override 
            public void onClick(View v) 
            { 
                mUserLoginPresenter.login(); 
            } 
        }); 
        mBtnClear.setOnClickListener(new View.OnClickListener() 
        { 
            @Override 
            public void onClick(View v) 
            { 
                mUserLoginPresenter.clear(); 
            } 
        }); 
    } 
    @Override 
    public String getUserName() 
    { 
        return mEtUsername.getText().toString(); 
    } 
    @Override 
    public String getPassword() 
    { 
        return mEtPassword.getText().toString(); 
    } 
    @Override 
    public void clearUserName() 
    { 
        mEtUsername.setText(""); 
    } 
    @Override 
    public void clearPassword() 
    { 
        mEtPassword.setText(""); 
    } 
    @Override 
    public void showLoading() 
    { 
        mPbLoading.setVisibility(View.VISIBLE); 
    } 
    @Override 
    public void hideLoading() 
    { 
        mPbLoading.setVisibility(View.GONE); 
    } 
    @Override 
    public void toMainActivity(User user) 
    { 
        Toast.makeText(this, user.getUsername() + 
                " login success , to MainActivity", Toast.LENGTH_SHORT).show(); 
    } 
    @Override 
    public void showFailedError() 
    { 
        Toast.makeText(this, 
                "login failed", Toast.LENGTH_SHORT).show(); 
    } 
}

Presenter的作用就是从View层获取用户的输入,传递到Model层进行处理,然后回调给View层,输出给用户!

public class UserLoginPresenter { 
    private IUserBiz userBiz; 
    private IUserLoginView userLoginView; 
    private Handler mHandler = new Handler(); 
//Presenter必须要能拿到View和Model的实现类 
    public UserLoginPresenter(IUserLoginView userLoginView) 
    { 
        this.userLoginView = userLoginView; 
        this.userBiz = new UserBiz(); 
    } 
    public void login() 
    { 
        userLoginView.showLoading(); 
        userBiz.login(userLoginView.getUserName(), userLoginView.getPassword(), new OnLoginListener() 
        { 
            @Override 
            public void loginSuccess(final User user) 
            { 
                //需要在UI线程执行 
                mHandler.post(new Runnable() 
                { 
                    @Override 
                    public void run() 
                    { 
                        userLoginView.toMainActivity(user); 
                        userLoginView.hideLoading(); 
                    } 
                }); 
            } 
            @Override 
            public void loginFailed() 
            { 
                //需要在UI线程执行 
                mHandler.post(new Runnable() 
                { 
                    @Override 
                    public void run() 
                    { 
                        userLoginView.showFailedError(); 
                        userLoginView.hideLoading(); 
                    } 
                }); 
            } 
        }); 
    } 
    public void clear() 
    { 
        userLoginView.clearUserName(); 
        userLoginView.clearPassword(); 
    } 
}

分析下这个例子:

MVC到MVP简单说,就是增加了一个接口降低一层耦合。那么,用样的MVP到MVVM就是再加一个接口呗。实际项目我建议用MVP模式,MVVM还是复杂了对于中小型项目有点过度设计,这里就不展开讲。

模块化

上图是一个项目常见的架构方式

新建一个app,我们往往有两种模块划分方法:

每一个包都是一个业务模块,每个模块下再按照类型来分。

我建议中小型的新项目按照类型比较好,因为开始代码量不多按照业务来分不切实际,一个包只放几个文件?? 况且前期业务不稳定,等到开发中期业务定型了,再进行重构难度也不大。

上面讲的模块划分既不属于模块化也不属于插件化,仅仅是一个简单package结构不同而已,app还是一个app并没有产生什么变化。通常讲的模块化,是指把业务划分为不同的moduler(类型是library),每个moduler之间都不依赖,app(类型是application)只是一个空壳依赖所有的moduler。

image

image.png

每个红色箭头都是一个业务模块,红色框是我们的app里面只包含简单的业务:自定义Application,入口Activity,build.gradle编译打包配置。看下项目的依赖关系:

这样架构后,带来最大的不同就是:不同业务模块完全分离,好处就是不同模块的开发绝对不会互相耦合了,因为你在模块A 根本访问不到模块B的API。此时模块间通信急需解决,Intent隐式跳转可以处理部分Activity的跳转,但真正的业务场景远不止两个界面跳一跳。你之前封装的业务通用方法,工具类,数据缓存现在其他模块都拿不到了,本本来可以复用的控件,fragment都不能共享,而这些都是和业务耦合没办法拿到底层基础库。

模块间通信

针对上面问题有两个解决办法,根据自己项目实际情况,如果项目的前期搭建已经很优秀,有完善的基础库,不同模块间的通信不是很多,可以自己实现。如果项目比较庞大,不同业务间频繁调用建议使用阿里巴巴的开源库。

自己实现

首先每个moduler有个目录叫include,里面有三个类,此处以一个bbs论坛模块为例说明,

每个模块方法和回调都有了,in和out都具备了,别的模块怎么使用呢?就该app该上场了,app不能只是一个壳里面要定义一个ModulerManager implements 所有模块的对外interface,作为每个模块的中转站,A模块告诉ModulerManager我想跳转到论坛模块,接着ModulerManager调用IBBService.enterBbsActivity,IBBSServiceImpl是IBBService的具体实现(多态)然后调用IBBSServiceImpl.enterBbsActivity跳转到BBS界面。

通信是解决了,其实踩坑才刚刚开始:

apply plugin: 'com.android.library'

app的build.gradle配置:

apply plugin: 'com.android.application'

性质发生巨大变化。里面的自定义application,build.gradle,代码混淆配置等全部移到app

结语

插件化其实最后发布的产品也是一个apk,只不过大小可以控制(可以随意去掉某些模块),支持用户动态加载子apk。因此,插件化就是动态加载apk。有人说我用intent隐式可以直接跳转到另一个apk啊,干嘛还要插件化。

其实是两码事,intent只是指定一个Activity跳过去,后面的交互完成不受你控制,2个apk也是运行在独立的进程数据无法共享。而插件化可以让两个apk运行在一个进程,可以完全像同一个apk一样开发。不过,我觉得插件化只适合需要多部门并行开发的那种,比如支付宝这种超级app,一般的app开发除非特殊需要,否则用不到。

插件化也有成熟的框架,在此不详细说了。另外,每个人的习惯不一样,组件化,模块化在我看来差不多,没必要纠结两个名词。

上一篇 下一篇

猜你喜欢

热点阅读