Android框架之MVC模式(以登录为例)

    Android框架现在常见的有MVC模式、MVP模式、MVVM模式。我们首先先明确一个概念:模式是指组织代码的结构方式,模式并不能提高代码的执行效率。模式是为了后续功能的扩展方便和代码的结构清晰而使用的。

    刚开始做Android开发时我们把代码都写在Activity里,这样代码的扩展性和结构清晰并不好。由此演化出Android的MVC模式,本篇文章也主要描述怎么将原来的写法抽取成MVC模式。

    MVC模式即将软件分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。

    模型(Model)- 用来产生数据(访问网络、数据库等)

    视图(View)- 视图层相关(界面布局控件等相关的代码)

    控制器(Controller)- 逻辑控制层(业务逻辑相关的代码)

一、Activity是万能的

首先我们以登录功能为例看一下原来把代码都写到Activity的情况:

布局文件activity_login.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    tools:context=".LoginActivity">

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="40dp"
        android:layout_marginTop="40dp"
        android:src="@mipmap/ic_launcher_round"/>

    <EditText
        android:id="@+id/et_phone_number"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginEnd="40dp"
        android:layout_marginStart="40dp"
        android:hint="请输入手机号"
        android:inputType="number"
        android:textColor="#333333"
        android:textSize="14sp"/>

    <EditText
        android:id="@+id/et_password"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginEnd="40dp"
        android:layout_marginStart="40dp"
        android:layout_marginTop="40dp"
        android:hint="请输入密码"
        android:inputType="textPassword"
        android:textColor="#333333"
        android:textSize="14sp"/>

    <Button
        android:id="@+id/btn_login"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginEnd="40dp"
        android:layout_marginStart="40dp"
        android:layout_marginTop="40dp"
        android:text="登 录"
        android:textColor="#FFFFFF"
        android:textSize="20sp"/>

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:visibility="gone"/>
</LinearLayout>
Activity代码LoginActivity:
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "LoginActivity";

    private EditText    etPhoneNumber;
    private EditText    etPassword;
    private Button      btnLogin;
    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        etPhoneNumber = (EditText) findViewById(R.id.et_phone_number);
        etPassword = (EditText) findViewById(R.id.et_password);
        btnLogin = (Button) findViewById(R.id.btn_login);
        progressBar = (ProgressBar) findViewById(R.id.progressBar);

        btnLogin.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_login:    //登录
                String phoneNumber = etPhoneNumber.getText().toString().trim();
                String password = etPassword.getText().toString().trim();
                gotoLogin(phoneNumber, password);
                break;
            default:
                break;
        }
    }

    private void gotoLogin(String phoneNumber, String password) {
        //本地对输入情况做校验
        boolean validateOk = validateInput(phoneNumber, password);
        if (validateOk) {
            progressBar.setVisibility(View.VISIBLE);
            String md5Password = MD5Utils.getMd5(password);
            OkHttpUtils
                    .post()
                    .url(Constants.URL_LOGIN)
                    .addParams("phoneNumber", phoneNumber)
                    .addParams("password", md5Password)
                    .build()
                    .execute(new StringCallback() {
                        @Override
                        public void onError(okhttp3.Call call, Exception e, int id) {
                            progressBar.setVisibility(View.GONE);
                            Log.i(TAG, "onError: ---登录访问异常---" + e.getMessage());
                            e.printStackTrace();
                            Toast.makeText(LoginActivity.this, "网络访问出现异常", Toast.LENGTH_SHORT).show();
                        }

                        @Override
                        public void onResponse(String response, int id) {
                            progressBar.setVisibility(View.GONE);
                            Log.i(TAG, "onResponse: 登录成功 response = " + response + " ---");
                            Gson gson = new Gson();
                            LoginData loginData = gson.fromJson(response, LoginData.class);

                            switch (loginData.status) {
                                case 200: //用户名未注册
                                case 201: //密码有误
                                case 203: //登录失败
                                    Toast.makeText(LoginActivity.this, loginData.message, Toast.LENGTH_SHORT).show();
                                    Log.i(TAG, "onResponse: = " + loginData.message);
                                    break;
                                case 202:   //登录成功
                                    Toast.makeText(LoginActivity.this, loginData.message, Toast.LENGTH_SHORT).show();
                                    Log.i(TAG, "onResponse: = " + loginData.message);

                                    //本地保存必要的用户信息
                                    //......

                                    Intent intent = new Intent(LoginActivity.this, LoginSuccessActivity.class);
                                    startActivity(intent);
                                    //登录页面直接消失
                                    finish();
                                    break;
                                default:
                                    Toast.makeText(LoginActivity.this, "登录出现未知异常", Toast.LENGTH_SHORT).show();
                                    break;
                            }
                        }
                    });
        }
    }

    private boolean validateInput(String phoneNumber, String password) {
        if (TextUtils.isEmpty(phoneNumber)) {
            Toast.makeText(this, "手机号不能为空", Toast.LENGTH_SHORT).show();
            return false;
        }
        if (TextUtils.isEmpty(password)) {
            Toast.makeText(this, "密码不能为空", Toast.LENGTH_SHORT).show();
            return false;
        }
        if (!phoneNumber.matches(Constants.STR_PHONE_REGEX2)) {  //匹配正则表达式
            Toast.makeText(this, "请输入正确的手机号", Toast.LENGTH_SHORT).show();
            return false;
        }
        return true;
    }

}

二、实现MVC模式

    要使用MVC模式同样实现上面的登录功能,需要我们将数据相关的代码抽取出来,成为单独的Model层,最终Model层能直接提供登录的用户数据LoginData。

    在项目中先创建一个model包,然后创建LoginModel.java类,即登录的Model层:

//Model层只用来产生数据
public class LoginModel {
    private static final String TAG = "LoginModel";

    public void gotoLogin(String phoneNumber, String md5Password, final OnNetResponseListener listener) {
        OkHttpUtils
                .post()
                .url(Constants.URL_LOGIN)
                .addParams("phoneNumber", phoneNumber)
                .addParams("password", md5Password)
                .build()
                .execute(new StringCallback() {
                    @Override
                    public void onError(okhttp3.Call call, Exception e, int id) {
                        Log.i(TAG, "onError: ---网络访问出现异常---" + e.getMessage());
                        e.printStackTrace();
                        listener.onNetResposeError("网络访问出现异常");
                    }

                    @Override
                    public void onResponse(String response, int id) {
                        Log.i(TAG, "onResponse: 登录成功 response = " + response + " ---");
                        Gson gson = new Gson();
                        LoginData loginData = gson.fromJson(response, LoginData.class);
                        listener.onNetReponseSuccess(loginData);
                    }
                });
    }
    
    public interface OnNetResponseListener {

        void onNetResposeError(String msg);

        void onNetReponseSuccess(LoginData loginData);
    }
}

    其中,由于访问网络是异步操作,所以获取到的数据需要使用回调(即观察者设计模式)的方式返回回去,抽取LoginModel之后原LoginActivity访问网络的代码做相应调整,如下:

public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "LoginActivity";

    private EditText    etPhoneNumber;
    private EditText    etPassword;
    private Button      btnLogin;
    private ProgressBar progressBar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);

        etPhoneNumber = (EditText) findViewById(R.id.et_phone_number);
        etPassword = (EditText) findViewById(R.id.et_password);
        btnLogin = (Button) findViewById(R.id.btn_login);
        progressBar = (ProgressBar) findViewById(R.id.progressBar);

        btnLogin.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_login:    //登录
                String phoneNumber = etPhoneNumber.getText().toString().trim();
                String password = etPassword.getText().toString().trim();
                login(phoneNumber, password);
                break;
            default:
                break;
        }
    }

    private void login(String phoneNumber, String password) {
        //本地对输入情况做校验
        boolean validateOk = validateInput(phoneNumber, password);
        if (validateOk) {
            progressBar.setVisibility(View.VISIBLE);
            String md5Password = MD5Utils.getMd5(password);

            LoginModel loginModel = new LoginModel();
            loginModel.gotoLogin(phoneNumber, md5Password, new LoginModel.OnNetResponseListener() {
                @Override
                public void onNetResposeError(String msg) {
                    progressBar.setVisibility(View.GONE);
                    Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onNetReponseSuccess(LoginData loginData) {
                    progressBar.setVisibility(View.GONE);
                    switch (loginData.status) {
                        case 200: //用户名未注册
                        case 201: //密码有误
                        case 203: //登录失败
                            Toast.makeText(LoginActivity.this, loginData.message, Toast.LENGTH_SHORT).show();
                            Log.i(TAG, "onResponse: = " + loginData.message);
                            break;
                        case 202:   //登录成功
                            Toast.makeText(LoginActivity.this, loginData.message, Toast.LENGTH_SHORT).show();
                            Log.i(TAG, "onResponse: = " + loginData.message);

                            //本地保存必要的用户信息
                            //......

                            Intent intent = new Intent(LoginActivity.this, LoginSuccessActivity.class);
                            startActivity(intent);
                            //登录页面直接消失
                            finish();
                            break;
                        default:
                            Toast.makeText(LoginActivity.this, "登录出现未知异常", Toast.LENGTH_SHORT).show();
                            break;
                    }
                }
            });

        }
    }

    private boolean validateInput(String phoneNumber, String password) {
        if (TextUtils.isEmpty(phoneNumber)) {
            Toast.makeText(this, "手机号不能为空", Toast.LENGTH_SHORT).show();
            return false;
        }
        if (TextUtils.isEmpty(password)) {
            Toast.makeText(this, "密码不能为空", Toast.LENGTH_SHORT).show();
            return false;
        }
        if (!phoneNumber.matches(Constants.STR_PHONE_REGEX2)) {  //匹配正则表达式
            Toast.makeText(this, "请输入正确的手机号", Toast.LENGTH_SHORT).show();
            return false;
        }
        return true;
    }
}

    这样我们就使用MVC模式同样实现上面的登录功能

三、总结

    我的理解:所谓的MVC模式就是把我们最初代码的数据获取功能的那部分代码抽取成model层。其实在各个端的开发中都有MVC模式,Android作为移动端来说MVC模式和后端并不完全一致。在Android中我更愿意这样来划分:M层是独立出来的,V层和C层的角色都由Activity来承担。有一些人会认为:将XML布局视为V层,将Activity视为控制器,这样Activity读取V视图层的数据。如果这样理解的话,MVP模式中V又是Activity,个人感觉有点混乱

    通过以上的代码,或许我们还是感觉Activity中的代码有些繁杂,因为V层和C层的角色都由Activity来承担,要想更进一步的话,就需要我们再进一步了解MVP模式。

猜你喜欢

转载自blog.csdn.net/beita08/article/details/81055854