Ideas de arquitectura de Android y encapsulación del marco MVVM

Acerca de la arquitectura del proyecto de Android también es un tema cliché. Hay demasiados artículos sobre la arquitectura de Android en Internet, pero a través de las palabras clave de búsqueda de Google, la mayoría de los artículos populares en la página de inicio son introducciones a los conceptos de MVC, MVP y MVVM. arquitecturas Artículos conceptuales Puede que no sea de mucha ayuda para los estudiantes que no entienden la arquitectura de Android. Este artículo en realidad se originó a partir del intercambio de tecnología interna del autor en la empresa, y se publicó como artículo después de una ligera modificación. El contenido del artículo involucra la evolución de
MVC, MVP a MVVM, al mismo tiempo, en aras de la comprensión, se demuestra cada arquitectura en código, y finalmente
se encapsula la arquitectura MVVM en base a los componentes proporcionados por Jetpack. El contenido del artículo es relativamente básico y casi no hay conocimientos oscuros, lo que será de gran ayuda para los estudiantes que quieran comprender la arquitectura de Android.

1. Evolución de la arquitectura del proyecto Android

En primer lugar, debemos entender que no existe una plataforma para la arquitectura. No importa que MVC, MVP o MVVM
no sean exclusivos de la plataforma Android, incluso porque la plataforma Android comenzó tarde, la arquitectura del proyecto Android se refiere más o menos a la implementación de la arquitectura front-end.

Para proyectos front-end o del lado de Android, el código se puede dividir en tres partes, a saber, la parte de la interfaz de usuario, la parte de la lógica empresarial y la parte de control de datos. El punto de partida de estas tres partes de circulación proviene de la entrada del usuario, es decir, el usuario invoca la lógica comercial correspondiente para obtener datos al operar la interfaz de usuario y, finalmente, devuelve los datos a la interfaz de interfaz de usuario. la siguiente figura.

inserte la descripción de la imagen aquí

De acuerdo con estas tres partes, podemos dividir el código en tres capas, a saber, la estratificación inicial de la arquitectura MVC. Sin embargo, a medida que el negocio del proyecto se vuelve más y más complejo, se revelan las desventajas de la arquitectura MVC y no puede soportar el negocio existente. Entonces, en este contexto, la arquitectura MVP se derivó para compensar las deficiencias de MVC. Incluso más tarde, Google lanzó oficialmente algunos componentes de Jetpack para resolver específicamente el problema de la arquitectura de Android, desde el impulso principal de MVVM hasta el impulso principal actual de la arquitectura MVI. Pero no importa cómo evolucione la arquitectura, no se puede separar de la lógica de tres capas mencionada anteriormente, pero la nueva arquitectura compensa las deficiencias de la arquitectura anterior sobre la base de las existentes, lo que hace que el código del proyecto sea más fácil de mantener. .

En el siguiente contenido, discutimos principalmente la evolución de la arquitectura del proyecto Android de MVC a MVVM.

  1. Introducción a MVC

Basado en la lógica de tres niveles mencionada anteriormente, el proyecto inicial de Android adoptó la arquitectura MVC. MVC es la abreviatura de Model-View-Controller
. En pocas palabras, MVC es que el usuario opera la Vista, y la Vista llama al Controlador para operar la capa Modelo, y luego la capa Modelo devuelve los datos a la capa Vista para su visualización.

  • La capa modelo (Modelo) se encarga de comunicarse con la capa de base de datos y de red, y obtener y almacenar los datos de la aplicación;
  • La capa de vista (View) se encarga de visualizar los datos de la capa Modelo y manejar la interacción con los usuarios;
  • La capa de control (Controlador) se utiliza para establecer la conexión entre la capa Modelo y la capa Vista, es responsable de procesar la lógica comercial principal y actualiza los datos de la capa Modelo de acuerdo con las operaciones del usuario.

El diagrama de estructura de MVC se muestra en la siguiente figura.

inserte la descripción de la imagen aquí

En la arquitectura MVC del proyecto de Android, dado que la actividad actúa como la capa de vista y la capa de controlador al mismo tiempo, el MVC en Android se parece más a la siguiente estructura:

inserte la descripción de la imagen aquí

Con base en la figura anterior, podemos tomar el proceso de inicio de sesión de la aplicación como ejemplo para implementar el código de la arquitectura MVC en Android.

(1) Implementación de código de capa de modelo

La capa Modelo se usa para procesar la solicitud de inicio de sesión y obtener datos relacionados con el inicio de sesión del servidor, y notificar a la capa Vista para actualizar la interfaz después del éxito. el código se muestra a continuación:

public class LoginModel {
    
    
    public void login(String username, String password, LoginListener listener) {
    
    
        RetrofitManager.getApiService()
                .login(username, password)
                .enqueue(new Callback<User>() {
    
    
                    @Override
                    public void onResponse(Call<User> call, Response<User> response) {
    
    
                        listener.onSuccess(response.data);
                    }

                    @Override
                    public void onFailed(Exception e) {
    
    
                        listener.onFailed(e.getMessage());
                    }
                });
        
    }
}

El código anterior notifica a la capa de vista que actualice la interfaz de usuario a través de LoginListener.LoginListener es una interfaz, de la siguiente manera:

    public interface LoginListener {
    
    
    void onSuccess(User data);

    void onFailed(String msg);
}

(2) Implementación de código de vista/controlador

Dado que las capas de controlador y vista en la arquitectura MVC de Android están a cargo de la actividad, la actividad debe implementar LoginListener para actualizar
la interfaz de usuario. Su código es el siguiente:

public class MainActivity extends AppCompactActivity implements LoginListener {
    
    

    private LoginModel loginModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        model = new LoginModel();
        findViewbyId(R.id.btn_fetch_data).setOnClickListener(view -> {
    
    
                    String username = findViewById(R.id.et_username).getText().toString();
                    String password = findViewById(R.id.et_password).getText().toString();
                    loginModel.login(username, password, this);
                }
        );
    }

    @Override
    public void onSuccess(User data) {
    
    
        // Update UI
    }

    @Override
    public void onFailed(String msg) {
    
    
        // Update UI
    }
}

Como se puede ver en el código anterior, el código MVC en Android no es claro acerca de las capas, lo que resulta en la confusión de la capa Controlador y la capa Vista. Al mismo tiempo, cuando escribe código de Android, generalmente no extrae deliberadamente una capa de Modelo, sino que coloca
el código de la capa de Modelo en la Actividad para implementarlo.

public class MainActivity extends AppCompactActivity implements LoginListener, View.OnClickListener {
    
    

    private LoginModel loginModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    
    
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        model = new LoginModel();

        findViewbyId(R.id.btn_fetch_data).setOnClickListener(view -> {
    
    
                    showLoading();
                    String username = findViewById(R.id.et_username).getText().toString();
                    String password = findViewById(R.id.et_password).getText().toString();
                    RetrofitManager.getApiService()
                            .login(username, password)
                            .enqueue(new Callback<User>() {
    
    
                                @Override
                                public void onResponse(Call<User> call, Response<User> response) {
    
    
                                    onSuccess(response.data);
                                }

                                @Override
                                public void onFailed(Exception e) {
    
    
                                    listener.onFailed(e.getMessage());
                                }
                            });
                }
        );
    }

    @Override
    public void onSuccess(User data) {
    
    
        dismissLoading();
        // Update UI
    }

    @Override
    public void onFailed(String msg) {
    
    
        dismissLoading();
        // Update UI
    }

    public void showLoading() {
    
    
        // ...
    }

    public void dismissLoading() {
    
    
        // ...
    }
}

Esas capas de código se vuelven más borrosas y también hacen que la jerarquía estructural del código sea más confusa. El código en la actividad de algunas páginas que complican algunos negocios puede llegar a tener miles de líneas. Se puede ver que la arquitectura MVC en el proyecto Android
tiene muchos problemas. El resumen tiene principalmente los siguientes puntos:

  • Actividad/Fragmento realiza el trabajo de la capa de Vista y la capa de Controlador al mismo tiempo, lo que viola la responsabilidad única;
  • La capa Modelo y la capa Vista están acopladas y son interdependientes;
  • No se presta atención a las capas durante el desarrollo, y el código de la capa del modelo también se incluye en Actividad/Fragmento, lo que hace que la capa de código sea más confusa.
  1. Introducción a MVP

Para los problemas existentes en la arquitectura MVC anterior, podemos optimizarlos y resolverlos sobre la base de MVC.
Es decir, la lógica de la capa de control se elimina de la Actividad y se bloquea el acoplamiento entre la capa Modelo y la capa Vista. La capa Modelo no se comunica directamente con la Vista, pero permite que el Modelo notifique a la capa de control cuando el los datos cambian, y la capa de control luego notifica a la capa de Vista. Actualizar la interfaz, esta es la idea arquitectónica de MVP. MVP es la abreviatura de Model-View-Presenter . En pocas palabras, MVP es cambiar
el Controlador de MVC a Presentador, es decir, extraer el código de la capa lógica de la Actividad al Presentador, para que el nivel de código se vuelva más claro. En segundo lugar, la capa Modelo ya no se mantiene. la capa Vista
, y el código está más desacoplado. .

  • La capa modelo (Model) es consistente con la de MVC. También es responsable de comunicarse con la base de datos y la capa de red, y obtener y almacenar datos de la aplicación. La diferencia es que la capa Modelo ya no se comunica directamente con la capa Vista, sino que se comunica con la capa Presentador.
  • La capa de vista (View) se encarga de visualizar los datos de la capa Modelo e interactuar con la capa Presentador al mismo tiempo. En comparación con MVC, la capa de vista y la capa de modelo de MVP ya no están acopladas.
  • La capa de control (Presentador) es principalmente responsable de procesar la lógica comercial, monitorear los cambios de datos en la capa Modelo y luego llamar a la capa Ver para actualizar la interfaz de usuario.

El diagrama de estructura de MVP se muestra en la siguiente figura.
inserte la descripción de la imagen aquí

Como se puede ver en la figura anterior, la Vista se comunica directamente con la capa Presentador, y cuando la capa Vista recibe operaciones del usuario, llamará a la
capa Presentador para procesar la lógica empresarial. Luego, la capa del presentador obtiene datos a través del modelo, y la capa del modelo envía los datos más recientes al presentador después de obtener los datos.
Dado que la capa Presentador también contiene una referencia a la capa Vista, los datos se pasan a Viewla capa para su visualización.

A continuación, todavía usamos el inicio de sesión como ejemplo para demostrar la implementación de la arquitectura MVP a través del código.

(1) Implementación de código de capa de modelo

La capa Modelo en MVP es consistente con la capa Modelo en MVC, el código es el siguiente:

public class LoginModel {
    
    
    public void login(String username, String password, LoginListener listener) {
    
    
        RetrofitManager.getApiService()
                .login(username, password)
                .enqueue(new Callback<User>() {
    
    
                    @Override
                    public void onResponse(Call<User> call, Response<User> response) {
    
    
                        listener.onSuccess(response.data);
                    }

                    @Override
                    public void onFailed(Exception e) {
    
    
                        listener.onFailed(e.getMessage());
                    }
                });
    }
}

Después de que la interfaz de inicio de sesión devuelva el resultado, el resultado se vuelve a llamar a través de LoginListener.

public interface LoginListener {
    
    
    void onSuccess(User user);

    void onFailed(String errorInfo);
}

(2) Implementación del código de la capa del presentador

Dado que el presentador debe notificar a la capa de vista para actualizar la interfaz de usuario, debe mantener la vista, y aquí se puede abstraer una interfaz de vista. como sigue:

public interface ILoginView {
    
    
    void showLoading();

    void dismissLoading();

    void loginSuccess(User data);

    void loginFailer(String errorMsg);
}

Además, el presentador también necesita comunicarse con la capa del modelo, por lo que la capa del presentador también contendrá la capa del modelo. Después de que el usuario activa la operación de inicio de sesión, se invoca la lógica de inicio de sesión del presentador. El presentador realiza la operación de inicio de sesión a través del Modelo Después de que el inicio de sesión sea exitoso, el resultado se retroalimenta a la capa Ver. Interfaz de actualización. El código se implementa de la siguiente manera:

public class LoginPresenter {
    
    

    // Model 层 
    private LoginModel model;

    // View 层 
    private ILoginView view;

    public LoginPresenter(LoginView view) {
    
    
        this.model = new LoginModel();
        this.view = view;
    }

    public void login(String username, String password) {
    
    
        view.showLoading();
        model.login(username, password, new LoginListener() {
    
    
            @Override
            public void onSuccess(User user) {
    
    
                view.loginSuccess(user);
                view.dismissLoading();
            }

            @Override
            public void onFailed(String msg) {
    
    
                view.loginFailer(msg);
                view.dismissLoading();
            }
        });
    }

    @Override
    public void onDestory() {
    
    
        // recycle instance.
    }
}

(3) Ver implementación de código de capa

La actividad como capa de vista debe implementar la interfaz ILoginView anterior, y la capa de vista debe contener el presentador para procesar la lógica comercial. La implementación del código de la capa Vista se vuelve muy simple.

public class MainActivity extends AppCompatActivity implements ILoginView {
    
    

    private LoginPresenter presenter;

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

        presenter = new LoginPresenter(this);
        findViewById(R.id.button).setOnClickListener(v -> {
    
    
            String username = findViewById(R.id.et_username).getText().toString();
            String password = findViewById(R.id.et_password).getText().toString();
            presenter.login(username, password);
        });
    }

    @Override
    public void loginSuccess(User user) {
    
    
        // Update UI.
    }

    @Override
    public void loginFailer(String msg) {
    
    
        // Update UI.
    }

    @Override
    protected void onDestroy() {
    
    
        super.onDestroy();
        presenter.onDestroy();
    }

    @Override
    public void showLoading() {
    
    

    }

    @Override
    public void dismissLoading() {
    
    
    }
}

La esencia de MVP es la programación orientada a la interfaz para realizar el principio de inversión de dependencia. Se puede ver que la arquitectura MVP es cada vez más clara que MVC en términos de capas, y desacopla la capa Modelo y la capa Vista. Pero MVP también tiene algunas desventajas, enumeradas a continuación:

  • Se introduce una gran cantidad de clases de implementación de interfaz IView e IPresenter para aumentar la complejidad del proyecto.
  • La capa Vista y el Presentador se sostienen entre sí y hay un acoplamiento.
  1. Introducción a MVVM

Comparado con MVC, MVP sin duda tiene muchas ventajas, pero no es invulnerable.Además, MVP no es la arquitectura recomendada oficialmente por Google, por lo que solo puede considerarse como una forma salvaje para que los programadores optimicen la arquitectura de los proyectos de Android. A partir de 2015, Google comenzó oficialmente a guiar la arquitectura del proyecto Android y luego lanzó el componente DataBinding y una serie de componentes Jetpack para ayudar a los desarrolladores a optimizar la arquitectura del proyecto. El conjunto de marco de orientación proporcionado oficialmente por Google es MVVM. MVVM es la abreviatura de **
Model-View-ViewModel**. Esta arquitectura resuelve los problemas de la arquitectura MVP hasta cierto punto. Aunque el marco rector oficial ha cambiado recientemente de MVVM a MVI, MVVM sigue siendo el marco principal para los proyectos de Android.

  • La capa modelo (Model) es consistente con la capa Modelo en MVP, y es responsable de comunicarse con la base de datos y la capa de red, obteniendo y almacenando datos. La diferencia con MVP es que la capa de modelo ya no notifica a la capa de lógica empresarial los cambios de datos a través de devoluciones de llamada, sino a través del modo de observador.
  • View (View) se encarga de visualizar los datos de la capa Model e interactuar con la capa ViewModel al mismo tiempo.
  • El modelo de vista (ViewModel) es el principal responsable del procesamiento de la lógica comercial e interactúa con la capa Modelo y la capa Vista al mismo tiempo. En comparación con Presenter de MVP, ViewModel ya no depende de View, lo que hace que el desacoplamiento sea más completo.

El diagrama de estructura de la arquitectura MVVM es el siguiente.

inserte la descripción de la imagen aquí

La esencia de la arquitectura MVVM está basada en datos y su principal característica es la dependencia unidireccional. La arquitectura MVVM desacopla ViewModel de View a través del modo de observador, dándose cuenta de la dependencia unidireccional de que View depende de ViewModel y ViewModel depende de Model.

A continuación, aún tomamos el inicio de sesión como ejemplo para implementar el código de arquitectura MVVM a través del modo observador.

(1) Modo observador

Porque la arquitectura MVVM necesita desacoplar ViewModel de View. Por lo tanto, el patrón del observador se puede usar aquí para lograrlo. A continuación se muestra el código para implementar el patrón Observer.

  • Implementar la interfaz de observador
public interface Observer<T> {
    
    
    // 成功的回调
    void onSuccess(T data);

    // 失败的回调
    void onFailed(String msg);
}
  • observador abstracto
public abstract class Observable<T> {
    
    
    // 注册观察者
    void register(Observer observer);

    // 取消注册
    void unregister(Observer observer);

    // 数据改变(设置成功数据)
    protected void setValue(T data) {
    
    

    }

    // 失败,设置失败原因
    void onFailed(String msg);
}

Aquí debemos tener en cuenta que el método setValue en el Observable observable está configurado para proteger. Significa que si la capa Vista obtiene una instancia Observable, no puede llamar a setValue para configurar los datos, a fin de garantizar la seguridad del código.

  • Implementación concreta observada
public class LoginObservable implements Observable<User> {
    
    

    private final List<Observer<User>> list = new ArrayList<>();

    private User user;

    @Override
    public void register(Observer<User> observer) {
    
    
        list.add(observer);
    }

    @Override
    public void unregister(Observer<User> observer) {
    
    
        liset.remove(observer);
    }

    @Override
    public void setValue(User user) {
    
    
        this.user = user;
        for (Observer observer : list) {
    
    
            observer.onSuccess(user);
        }
    }

    @Override
    public void onFailed(String msg) {
    
    
        for (Observer observer : list) {
    
    
            observer.onFailed(msg);
        }
    }
}

En la clase de implementación del observador, el método setValue se establece en public, lo que significa que si es LoginObservable, los datos se pueden establecer a través de setValue.

(2) Implementación de código de capa de modelo

El código de la capa Modelo es básicamente consistente con la implementación de MVP/MVC, de la siguiente manera:

public class LoginModel {
    
    
    public void login(String username, String password, LoginObservable observable) {
    
    
        RetrofitManager.getApiService()
                .login(username, password)
                .enqueue(new Callback<User>() {
    
    
                    @Override
                    public void onResponse(Call<User> call, Response<User> response) {
    
    
                        observable.setValue(response.data);
                    }

                    @Override
                    public void onFailed(Exception e) {
    
    
                        observable.onFailed(e.getMessage());
                    }
                });
    }
}

Cabe señalar que el método de inicio de sesión de LoginModel recibe el tipo de LoginObservable, por lo que los datos se pueden configurar a través de setValue aquí.

(3) Implementación de la capa ViewModel

La capa ViewModel necesita contener la capa Model, y la capa ViewModel contiene un LoginObservable y abre un método getObservable para que lo use la capa View. el código se muestra a continuación:

public class LoginViewModel {
    
    

    private LoginObservable observable;

    private LoginModel loginModel;

    public LoginViewModel() {
    
    
        loginModel = new LoginModel();
        observable = new LoginObservable();
    }

    public void login(String username, String password) {
    
    
        loginModel.login(username, password, observable);
    }

    // 这里返回值是父类Observable,即View层得到的Observable无法调用setValue
    public Observable getObservable() {
    
    
        return observable;
    }
}

Cabe señalar aquí que el valor de retorno del método getObservable es Observable, lo que significa que la capa Vista solo puede llamar al método de registro para observar los cambios de datos, pero no puede establecer los datos a través de setValue.

(4) Ver implementación de código de capa

La capa de vista contiene el modelo de vista, y el usuario activa un evento de inicio de sesión y lo pasa al modelo para que lo procese a través de la capa de vista, y la capa del modelo pasa los datos al observador en el modelo de vista después de iniciar sesión. Por lo tanto, la capa View solo necesita obtener el observador de la capa ViewModel y actualizar la interfaz de usuario después de observar el cambio de datos. el código se muestra a continuación:

public class MainActivity extends AppCompatActivity implements Observer<User> {
    
    

    private LoginViewModel viewModel;

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

        viewModel = new LoginViewModel();
        viewModel.getObservable().register(this);
        findViewById(R.id.button).setOnClickListener(v -> {
    
    
            String username = findViewById(R.id.et_username).getText().toString();
            String password = findViewById(R.id.et_password).getText().toString();
            viewModel.login(username, password);
        });
    }

    @Override
    public void onDestroy() {
    
    
        super.onDestory();
        viewModel.getObservable().unregister(this);
    }

    @Override
    public void onSuccess(User user) {
    
    
        // fetch data success,update UI.
    }

    @Override
    public void onFailed(String message) {
    
    
        // fetch data failed,update UI.
    }
}

Se puede ver que nos hemos dado cuenta
de la dependencia unidireccional de View->ViewModel->Model a través del modo de observador. En comparación con MVP, el desacoplamiento de MVVM es más puro. Sin embargo, el modo observador anterior lo implementamos nosotros mismos y definitivamente no es seguro usarlo directamente en el proyecto. Mencionamos anteriormente que Google nos ha proporcionado un conjunto de componentes Jetpack para ayudar a los desarrolladores a implementar la arquitectura MVVM. Luego, echemos un vistazo a la arquitectura MVVM implementada por Jetpack.

2. Use los componentes de Jetpack para encapsular la arquitectura MVVM

Si ha visto esto, creo que ya tiene una cierta comprensión de la arquitectura del proyecto. Es necesario enfatizar aquí que la arquitectura es una idea y no tiene nada que ver con las herramientas que usamos para lograrla. Al igual que en el capítulo anterior, implementamos MVVM a través del modo observador que escribimos nosotros mismos, siempre que sigamos esta idea arquitectónica, pertenece a esta arquitectura. Estos componentes lanzados oficialmente por Google pueden ayudarnos a realizar MVVM de manera más eficiente.

1. Introducción a Jetpack MVVM

MVVM es un marco recomendado oficialmente por Google. Para ello, Google proporciona una serie de herramientas para implementar MVVM, que se incluyen en los componentes de Jetpack, como LiveData, ViewModel y
DataBinding. El siguiente es un diagrama de arquitectura MVVM oficial implementado por los componentes de Jetpack.

inserte la descripción de la imagen aquí

Este diagrama es consistente con el diagrama de estructura de MVVM en nuestro capítulo anterior, excepto que los componentes de Jetpack se incorporan aquí. Se puede ver que las flechas en la figura son todas unidireccionales y que la capa View apunta a la capa ViewModel, lo que indica que la capa View contiene una referencia a la capa ViewModel, pero la capa ViewModel no contiene la capa View. La capa ViewModel contiene la capa Repository, pero la capa Repository no contiene ViewModel. La correspondencia entre esta imagen y MVVM es la siguiente:

  • Ver (View) corresponde a la Actividad/Fragmento en la figura, incluidos los archivos de diseño y las cosas relacionadas con la interfaz;
  • El modelo de vista (ViewModel) corresponde a Jetpack ViewModel y LiveData en la figura
    , que se usa para almacenar datos relacionados con la interfaz de usuario y puede garantizar que los datos no se pierdan después de girar la pantalla. Además, también proporciona una interfaz para llamar a la capa Vista y una interfaz para comunicarse con el Repositorio;
  • La capa del modelo (Modelo) corresponde al Repositorio de la figura, incluidos los datos locales y los datos del servidor.

2. Encapsulación de código de marco MVVM

Después de comprender Jetpack MVVM, generalmente hacemos algunos paquetes básicos para un desarrollo más eficiente. Por ejemplo, cómo combinar solicitudes de red y bases de datos para obtener datos, lógica de visualización de carga, etc.
El contenido de este capítulo requiere que los lectores tengan cierta comprensión de Jetpack ViewModel, LiveData y otros componentes.

2.1 Encapsulación de la capa de red

Para encapsular los datos devueltos por el servidor, se puede extraer una clase base de respuesta genérica. como sigue:

class BaseResponse<T>(
    var errorCode: Int = -1,
    var errorMsg: String? = null,
    var data: T? = null,
    var dataState: DataState? = null,
    var exception: Throwable? = null,
) {
    
    
    companion object {
    
    
        const val ERROR_CODE_SUCCESS = 0
    }
    
    val success: Boolean
        get() = errorCode == ERROR_CODE_SUCCESS
}

/**
 * 网络请求状态
 */
enum class DataState {
    
    
    STATE_LOADING, // 开始请求
    STATE_SUCCESS, // 服务器请求成功
    STATE_EMPTY, // 服务器返回数据为null
    STATE_FAILED, // 接口请求成功但是服务器返回error
    STATE_ERROR, // 请求失败
    STATE_FINISH, // 请求结束
}

En general, trataremos las situaciones anómalas correspondientes a solicitudes de red de manera unificada. Aquí podemos hacerlo en Observer. Encapsule el observador de LiveData, para realizar el procesamiento unificado de respuesta y excepción.

abstract class ResponseObserver<T> : Observer<BaseResponse<T>> {
    
    
    
    final override fun onChanged(response: BaseResponse<T>?) {
    
    
        response?.let {
    
    
            when (response.dataState) {
    
    
                DataState.STATE_SUCCESS, DataState.STATE_EMPTY -> {
    
    
                    onSuccess(response.data)
                }
                DataState.STATE_FAILED -> {
    
    
                    onFailure(response.errorMsg, response.errorCode)
                }
                DataState.STATE_ERROR -> {
    
    
                    onException(response.exception)
                }
                else -> {
    
    
                }
            }
        }
    }
    
    private fun onException(exception: Throwable?) {
    
    
        ToastUtils.showToast(exception.toString())
    }
    
    /**
     * 请求成功
     *  @param  data 请求数据
     */
    abstract fun onSuccess(data: T?)
    
    /**
     * 请求失败
     *  @param  errorCode 错误码
     *  @param  errorMsg 错误信息
     */
    open fun onFailure(errorMsg: String?, errorCode: Int) {
    
    
        ToastUtils.showToast("Login Failed,errorCode:$errorCode,errorMsg:$errorMsg")
    }
}

Encapsule RetrofitCreator para crear un servicio de API.

object RetrofitCreator {
    
    
    
    private val mOkClient = OkHttpClient.Builder()
        .callTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
        .connectTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
        .readTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
        .writeTimeout(Config.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
        .retryOnConnectionFailure(true)
        .followRedirects(false)
        .addInterceptor(HttpHeaderInterceptor())
        .addInterceptor(LogInterceptor())
        .build()
    
    private fun getRetrofitBuilder(baseUrl: String): Retrofit.Builder {
    
    
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(mOkClient)
            .addConverterFactory(GsonConverterFactory.create())
    }
    
    /**
     * Create Api service
     *  @param  cls Api Service
     *  @param  baseUrl Base Url
     */
    fun <T> getApiService(cls: Class<T>, baseUrl: String): T {
    
    
        val retrofit = getRetrofitBuilder(
            baseUrl
        ).build()
        return retrofit.create(cls)
    }
}

2.2 Encapsulación de la capa modelo

La capa de modelo en el código oficial se implementa a través del repositorio. Para reducir el código de plantilla, podemos encapsular BaseRepository para manejar las solicitudes de red.

open class BaseRepository {
    
    
    
    // Loading 状态的 LiveData
    val loadingStateLiveData: MutableLiveData<LoadingState> by lazy {
    
    
        MutableLiveData<LoadingState>()
    }
    
    /**
     * 发起请求
     *  @param  block 真正执行的函数回调
     *  @param responseLiveData 观察请求结果的LiveData
     */
    suspend fun <T : Any> executeRequest(
        block: suspend () -> BaseResponse<T>,
        responseLiveData: ResponseMutableLiveData<T>,
        showLoading: Boolean = true,
        loadingMsg: String? = null,
    ) {
    
    
        var response = BaseResponse<T>()
        try {
    
    
            if (showLoading) {
    
    
                loadingStateLiveData.postValue(LoadingState(loadingMsg, DataState.STATE_LOADING))
            }
            response = block.invoke()
            if (response.errorCode == BaseResponse.ERROR_CODE_SUCCESS) {
    
    
                if (isEmptyData(response.data)) {
    
    
                    response.dataState = DataState.STATE_EMPTY
                } else {
    
    
                    response.dataState = DataState.STATE_SUCCESS
                }
            } else {
    
    
                response.dataState = DataState.STATE_FAILED
// throw ServerResponseException(response.errorCode, response.errorMsg)
            }
        } catch (e: Exception) {
    
    
            response.dataState = DataState.STATE_ERROR
            response.exception = e
        } finally {
    
    
            responseLiveData.postValue(response)
            if (showLoading) {
    
    
                loadingStateLiveData.postValue(LoadingState(loadingMsg, DataState.STATE_FINISH))
            }
        }
    }
    
    private fun <T> isEmptyData(data: T?): Boolean {
    
    
        return data == null || data is List<*> && (data as List<*>).isEmpty()
    }
}

2.3 Encapsulación de la capa ViewModel

Si no hay encapsulación, la capa ViewModel también tendrá código de plantilla. Aquí se completa el código común en BaseViewModel. BaseViewModel necesita contener el Repositorio.

abstract class BaseViewModel<T : BaseRepository> : ViewModel() {
    
    
    val repository: T by lazy {
    
    
        createRepository()
    }
    
    val loadingDataState: LiveData<LoadingState> by lazy {
    
    
        repository.loadingStateLiveData
    }
    
    /**
     * 创建Repository
     */
    @Suppress("UNCHECKED_CAST")
    open fun createRepository(): T {
    
    
        val baseRepository = findActualGenericsClass<T>(BaseRepository::class.java)
            ?: throw NullPointerException("Can not find a BaseRepository Generics in ${
      
      javaClass.simpleName}")
        return baseRepository.newInstance()
    }
}

2.4 Encapsulación de la capa de vista

ViewModel se mantiene en BaseActivity/BaseFragment, y Loading se muestra y oculta según LoadingState.

abstract class BaseActivity<VM : BaseViewModel<*>, VB : ViewBinding> : AppCompatActivity() {
    
    
    
    protected val viewModel by lazy {
    
    
        createViewModel()
    }
    
    protected val binding by lazy {
    
    
        createViewBinding()
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(binding.root)
        viewModel.loadingDataState.observe(this) {
    
    
            when (it.state) {
    
    
                DataState.STATE_LOADING ->
                    showLoading(it.msg)
                else ->
                    dismissLoading()
            }
        }
        onActivityCreated(savedInstanceState)
    }
    
    /**
     * Activity content view created.
     *  @param  savedInstanceState savedInstanceState
     */
    abstract fun onActivityCreated(savedInstanceState: Bundle?)
    
    /**
     * 显示Loading
     */
    open fun showLoading(msg: String? = null) {
    
    
        ToastUtils.showToast("showLoading")
    }
    
    /**
     * 隐藏Loading
     */
    open fun dismissLoading() {
    
    
        ToastUtils.showToast("hideLoading")
    }
    
    /**
     * Create ViewBinding
     */
    @Suppress("UNCHECKED_CAST")
    open fun createViewBinding(): VB {
    
    
        val actualGenericsClass = findActualGenericsClass<VB>(ViewBinding::class.java)
            ?: throw NullPointerException("Can not find a ViewBinding Generics in ${
      
      javaClass.simpleName}")
        try {
    
    
            val inflate = actualGenericsClass.getDeclaredMethod("inflate", LayoutInflater::class.java)
            return inflate.invoke(null, layoutInflater) as VB
        } catch (e: NoSuchMethodException) {
    
    
            e.printStackTrace()
        } catch (e: IllegalAccessException) {
    
    
            e.printStackTrace()
        } catch (e: InvocationTargetException) {
    
    
            e.printStackTrace()
        }
    }
    
    /**
     * Create ViewModel
     *  @return  ViewModel
     */
    @Suppress("UNCHECKED_CAST")
    open fun createViewModel(): VM {
    
    
        val actualGenericsClass = findActualGenericsClass<VM>(BaseViewModel::class.java)
            ?: throw NullPointerException("Can not find a ViewModel Generics in ${
      
      javaClass.simpleName}")
        if (Modifier.isAbstract(actualGenericsClass.modifiers)) {
    
    
            throw IllegalStateException("$actualGenericsClass is an abstract class,abstract ViewModel class can not create a instance!")
        }
        // 判断如果是 BaseAndroidViewModel,则使用 AppViewModelFactory 来生成
        if (BaseAndroidViewModel::class.java.isAssignableFrom(actualGenericsClass)) {
    
    
            return ViewModelProvider(this, AppViewModelFactory(application))[actualGenericsClass]
        }
        return ViewModelProvider(this)[actualGenericsClass]
    }
}

El proceso de creación de ViewModel se genera automáticamente en BaseActivity según el tipo genérico de la subclase. Esto se hace usando la reflexión. Si cree que afecta el rendimiento, puede reescribir la función en la subclase createViewModelpara generar ViewModel usted mismo
.

Además, si necesita usar ViewModel con la aplicación, puede heredar BaseAndroidViewModel y su implementación se refiere a AndroidViewModel.

abstract class BaseAndroidViewModel<T : BaseRepository>(@field:SuppressLint("StaticFieldLeak") var application: Application) : BaseViewModel<T>()

Crear BaseAndroidViewModel aquí requiere el uso de AppViewModelFactory.

class AppViewModelFactory(private val application: Application) : ViewModelProvider.NewInstanceFactory() {
    
    
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
    
    
        return if (BaseAndroidViewModel::class.java.isAssignableFrom(modelClass)) {
    
    
            try {
    
    
                modelClass.getConstructor(Application::class.java).newInstance(application)
            } catch (e: NoSuchMethodException) {
    
    
                throw IllegalStateException("Cannot create an instance of $modelClass", e)
            } catch (e: IllegalAccessException) {
    
    
                throw IllegalStateException("Cannot create an instance of $modelClass", e)
            } catch (e: InstantiationException) {
    
    
                throw IllegalStateException("Cannot create an instance of $modelClass", e)
            } catch (e: InvocationTargetException) {
    
    
                throw IllegalStateException("Cannot create an instance of $modelClass", e)
            }
        } else super.create(modelClass)
    }
}

Si LoginViewModel implementa BaseAndroidViewModel, cuando use ViewModelProvider para crear LoginViewModel
, debe pasar el parámetro AppViewModelFactory en lugar de AndroidViewModelFactory en la biblioteca Jetpack ViewModel.

ViewModelProvider(this, AppViewModelFactory(application))[LoginViewModel::class.java]

3. Aplicación de ejemplo después de la encapsulación de MVVM

Después de completar la encapsulación anterior, veamos cómo implementar la lógica de inicio de sesión.

3.1 Implementar Repositorio

class LoginRepository : BaseRepository() {
    
    
    /**
     * Login
     *  @param  username 用户名
     *  @param  password 密码
     */
    suspend fun login(username: String, password: String, responseLiveData: ResponseMutableLiveData<LoginResponse>, showLoading: Boolean = true) {
    
    
        executeRequest({
    
    
            RetrofitManager.apiService.login(username, password)
        }, responseLiveData, showLoading)
    }
}

RetrofitManager se utiliza para crear el servicio API de gestión:

object RetrofitManager {
    
    
    private const val BASE_URL = "https://www.wanandroid.com/"
    
    val apiService: ApiService by lazy {
    
    
        RetrofitCreator.createApiService(
            ApiService::class.java, BASE_URL
        )
    }
}

3.2 Instancia de modelo de vista

class LoginViewModel : BaseViewModel<LoginRepository>() {
    
    
    // 提供给Model层设置数据
    private val _loginLiveData: ResponseMutableLiveData<LoginResponse> = ResponseMutableLiveData()
    
    // 提供给View层观察数据
    val loginLiveData: ResponseLiveData<LoginResponse> = _loginLiveData
    
    /**
     * Login
     *  @param  username 用户名
     *  @param  password 密码
     */
    fun login(username: String, password: String) {
    
    
        viewModelScope.launch {
    
    
            repository.login(username, password, _loginLiveData)
        }
    }
}

3.3 Instancia de Actividad/Fragmento

class LoginActivity : BaseActivity<LoginViewModel, ActivityBizAMainBinding>() {
    
    
    
    override fun onActivityCreated(savedInstanceState: Bundle?) {
    
    
        binding.tvLogin.setOnClickListener {
    
    
            viewModel.login("110120", "123456")
        }
        
        viewModel.loginLiveData.observe(this, object : ResponseObserver<LoginResponse>() {
    
    
            override fun onSuccess(data: LoginResponse?) {
    
    
                // Update UI
            }
        })
    }
}

Lo que debe tenerse en cuenta aquí es que se debe usar LiveData inmutable en la actividad para evitar
setValue/postValue a través de LiveData en la capa de vista durante el desarrollo, lo que causa problemas de actualización de la interfaz de usuario incorrecta. Por lo tanto, se han realizado algunos cambios en LiveData, de la siguiente manera:

abstract class ResponseLiveData<T> : LiveData<BaseResponse<T>> {
    
    
    /**
     * Creates a MutableLiveData initialized with the given `value`.
     *
     *  @param  value initial value
     */
    constructor(value: BaseResponse<T>?) : super(value)
    
    /**
     * Creates a MutableLiveData with no value assigned to it.
     */
    constructor() : super()
    
    /**
     * Adds the given observer to the observers list within the lifespan of the given owner.
     * The events are dispatched on the main thread. If LiveData already has data set, it
     * will be delivered to the observer.
     */
    fun observe(owner: LifecycleOwner, observer: ResponseObserver<T>) {
    
    
        super.observe(owner, observer)
    }
}

ResponseLiveData hereda de LiveData, por lo que ResponseLiveData es inmutable. Además se define un ResponseMutableLiveData que es un
LiveData mutable, el código es el siguiente:

class ResponseMutableLiveData<T> : ResponseLiveData<T> {
    
    
    
    /**
     * Creates a MutableLiveData initialized with the given `value`.
     *
     *  @param  value initial value
     */
    constructor(value: BaseResponse<T>?) : super(value)
    
    /**
     * Creates a MutableLiveData with no value assigned to it.
     */
    constructor() : super()
    
    public override fun postValue(value: BaseResponse<T>?) {
    
    
        super.postValue(value)
    }
    
    public override fun setValue(value: BaseResponse<T>?) {
    
    
        super.setValue(value)
    }
}

Hasta ahora, la encapsulación y el uso de Jetpack MVVM han terminado.

Resumir

Este artículo explica en detalle la evolución de la arquitectura del proyecto Android de MVC, MVP a MVVM y enumera ejemplos de implementación detallados para las tres arquitecturas.
Al mismo tiempo , encapsula la arquitectura principal actual de Jetpack MVVM. Por supuesto, dado que la comprensión de la arquitectura de todos no es necesariamente la misma, inevitablemente habrá inconsistencias en todos los artículos con la comprensión de los lectores. Bienvenido a dejar un mensaje para la discusión.

Supongo que te gusta

Origin blog.csdn.net/qq_20521573/article/details/127038987
Recomendado
Clasificación