Talk about the Android application architecture I understand

Talk about the Android application architecture I understand

This article has been authorized to publish exclusively on the WeChat public account guolin_blog ( )

Preface

Android architecture may be the most discussed topic in the forum, mvc mvp and mvvm are endless, followed by modularization and plug-inization. In this regard, the debate about which architecture is better has never stopped. My point of view: It is meaningless to compare the advantages and disadvantages of these models without actual projects. Each model has advantages and disadvantages, and there is no difference between good and bad. The more advanced the architecture is, the more complicated it is to implement, and it requires more learning costs and more manpower. Therefore, the key to the selection of technology is the characteristics of your own project, the level of the team, the allocation of resources, and the constraints of development time. Focus! But many teams turned the cart before the horse and set mvvm to their own projects.

Now I will talk about the Android architecture I understand from two major areas: the code level, mainly the understanding of MVC and MVP. At the project level, it is mainly how to build the entire project and how to divide the modules.

Popular understanding of MVC and MVP

First conclusion:

  • MVC: Model-View-Controller, classic mode, easy to understand, there are two main disadvantages:
  1. View's dependence on Model will cause the View to also contain business logic;
  2. Controller will become very thick and complicated.
  • MVP: Model-View-Presenter, an evolution mode of MVC, replacing Controller with Presenter, mainly to solve the first shortcoming mentioned above, decoupling View and Model, but the second shortcoming is still not resolved.
  • MVVM: Model-View-ViewModel, is an optimized mode of MVP, using two-way binding: View changes are automatically reflected in ViewModel, and vice versa.
MVC

To put it simply: the Demos we usually write are all MVC, the controller is our activity, the model (data provider) is to read the database, and we generally have special classes for network requests, and the View generally uses custom controls.

But all this just looks beautiful.

Imagine that in actual development, our activity code is actually more and more, model and controller are not separated at all, and controls also require relational data and business.

Therefore, the real existence of MVC is MC (V), Model and Controller cannot be separated at all, and data and View are severely coupled. This is its problem. A simple example: Get weather data and display it on the interface

  • Model layer
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();
                    }
                });
    }
}
 

Controllor (View) layer

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);
    }
}
 

Simply analyze this example: 1. The controls in the activity must be concerned with business and data in order to know how to display it. In other words, it is difficult for two people to obtain the data and the other to display the UI without communicating with each other, and then complete this function. 2. All the logic is in the activity. Perfectly reflects the two major shortcomings of MVC, let's see how MVP solves the first shortcoming

MVP

Looking at the figure above, you can see that View is disassembled into Presenter and View from MVC, which truly realizes the separation of logic processing and View. Write an example below: simulate a login interface, enter the user name and password, you can log in and clear the password

  • Model layer
/**
 
*/
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 As mentioned above, the View layer is defined in the form of an interface. We don't care about data or logic processing! Only care about the interaction with the user, then the operation that this login interface should have is (think of this interface as a container with input and output):

Obtain user name, obtain password, display the progress bar, hide the progress bar, jump to other interfaces, display the failure dialog, clear the user name, and clear the password. Next define the interface:

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

Then Activity implements this interface:

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 Presenter's role is to get user input from the View layer, pass it to the Model layer for processing, and then call back to the View layer and output to the user!
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();
    }
}
 

Analyze this example: 1. We have the IUserLoginView interface (protocol), and the controls in the activity do not need to care about the data at all, just implement this interface and display the UI in each method "step by step". In other words, we let two people develop this function together. One person needs to process data and make an interface (protocol), and the other person directly implements this interface with activity. You can display the UI in each callback with your eyes closed. The cooperation is very pleasant. 2. MVP successfully solved the first shortcoming of MVC, but the logic processing is still mixed in Activity.

Simply put, MVC to MVP is to add an interface to reduce a layer of coupling. So, to use the same MVP to MVVM is to add another interface. For actual projects, I suggest using MVP mode. MVVM is still complicated. For small and medium-sized projects, it is a bit over-designed, so I won t go into it here.

Modular

The figure above is a common architectural approach for a project. 1. The bottom layer is the basic library, where modules that are not related to business are placed: such as basic network requests, image compression, etc., which can be divided into logic modules, general UI modules and third-party libraries as needed. (It is recommended to use a separate svn branch) 2. The middle layer is the general business layer, where the general business modules (business-related) of the company's multiple android projects are placed, such as the login process, file upload/download, etc. 3. The top layer is the application layer. For example, the company has three android projects: LbBoss, BV and BVHD. We can also extract the common layer for similar projects (for example, the BV and BV PAD version here, the common layer is BVCommon).

When creating a new app, we often have two module division methods:

  • Divided by type:
  • Divided by business: Each package is a business module, and each module is classified according to its type.
  • How to choose? I suggest that small and medium-sized new projects are better according to the type, because the initial amount of code is not too much and it is not practical to divide it according to the business. Only a few files in a package? ? Moreover, the early-stage business is unstable, and it is not difficult to reconstruct the business after the mid-development business is finalized.

The module division mentioned above is neither modular nor plug-in. It is just a simple package structure that is different, and there is no change in app or app. Generally speaking, modularity refers to dividing the business into different modules (type is library), and each module is not dependent on each other. App (type is application) is just an empty shell that depends on all modules.

Each red arrow is a business module, the red box is our app contains only simple business: custom Application, entrance Activity, build.gradle compilation and packaging configuration. Look at the dependencies of the project:

After such an architecture, the biggest difference is that the different business modules are completely separated. The advantage is that the development of different modules will never be coupled with each other, because you cannot access the API of module B in module A. At this time, the communication between modules needs to be solved urgently. Intent implicit jumps can handle the jumps of some activities, but the real business scenario is far more than the jump of two interfaces. The business general methods, tool classes, and data caches you previously encapsulated are now out of reach of other modules. Controls and fragments that could have been reused cannot be shared, and these are coupled with the business and cannot get the underlying basic library.

Communication between modules

There are two solutions to the above problems. According to the actual situation of your own project, if the pre-construction of the project is already excellent, there is a complete basic library, and there are not many communications between different modules, you can implement it yourself. If the project is relatively large, it is recommended to use Alibaba's open source library for frequent calls between different businesses.

  • Realize it yourself 1. 1. each moduler has a directory called include, which contains three classes. Here, a bbs forum module is used as an example. IBBSNotify: There are a bunch of interfaces inside. The function is that the module's external callback can only be triggered passively. . IBBService: Inside is a bunch of interfaces, which are used to expose methods to other modules, such as enterBbsActivity. IBBSServiceImpl: Obviously, it is the realization of IBBService. For example, enterBbsActivity is how to jump to the forum interface and transfer what data.

2. Every module has methods and callbacks, both in and out. How to use other modules? It s time for the app to play. The app can t just be a shell. It must define a ModuleManager implements the external interface of all modules. As a transfer station for each module, Module A tells the ModuleManager that I want to jump to the forum module, and then ModuleManager calls IBBService.enterBbsActivity , IBBSServiceImpl is the specific implementation (polymorphism) of IBBService and then calls IBBSServiceImpl.enterBbsActivity to jump to the BBS interface. 3. The communication has been solved, but actually stepping on the pit has just started: a. The app here is a new one we created, so the app module of the previous project should be reduced to library:

apply plugin: 'com.android.library'
 

The build.gradle configuration of the shell app:

apply plugin: 'com.android.application'
 

The nature has changed dramatically. The custom application, build.gradle, code obfuscation configuration, etc. inside are all moved to the app bRjava is not final in the Lib type of module. All switch case statements are replaced with if else c. Be sure to build another common module and place common data , Caching, etc. d. There are many common functions, such as sharing, push, try to strip business and put it into common e. Other project-related details

  • The open source library ARouter is specially used to connect the communication between modules. It is very simple to use without going into it here.

other

Plug-inization: In fact, the final product released by plug-inization is also an apk, but the size can be controlled (some modules can be removed at will), and users can dynamically load sub-apk. Therefore, plug-in is to dynamically load the apk. Some people say that I can jump directly to another apk with intent implicitly, why do I need to plug-in.

In fact, they are two different things. The intent only specifies an Activity to jump over, and the subsequent interaction is not under your control. The two apks are also running in independent processes and cannot share data. The plug-in allows two apks to run in one process and can be developed exactly like the same apk. However, I think that plug-inization is only suitable for those that need to be developed in parallel by multiple departments, such as super apps such as Alipay. General app development is not needed unless there are special needs.

There are also mature frameworks for plug-inization, so I won't go into details here. In addition, everyone's habits are different. In my opinion, componentization and modularization are similar. There is no need to entangle two terms.