MVC architecture design and three-tier model & the essence and decoupling of MVP

Keywords: Android network Java Fragment

Blog Homepage

1. MVC architecture design and classic three-tier model

MVC: model view controller, classic mode, easy to manage.

  • Model: business layer and model layer, entity model and business related code
  • View: view layer, which corresponds to layout layout file in android
  • Controller: control layer, UI operation in android, corresponding to Activity

But in the actual development of Android, this View layer corresponds to the layout file, and there are very few things that can be done. In fact, the data binding operation and event processing code in the layout file are all in the Activity. We often put the specific business related code into the Activity. In addition, the Activity itself bears the responsibility of the control layer, which leads to contrlll The ER layer is becoming more and more bloated.

Therefore, the real existence of MVC is MC (V). Controller and Model cannot be separated at all. View and Model are seriously coupled.

As can be seen from the figure, Controller is used as a medium between Model and View. There is a close relationship between Model and View, and the coupling is strong.

MVC has two main disadvantages:

  • Strong coupling between Model layer and View layer makes it difficult to maintain
  • The Controller layer will become complex and code bloated

Advantage:

  • The Controller layer and View layer operate in the Activity, and the data operation is convenient
  • Module responsibilities are clearly divided, mainly divided into three modules of layer M-V-C

Take a simple example: get network pictures and show them on the interface

  • Model layer: get network pictures
public interface ImageModel {
    // Load pictures from the network
    void loadImage(String imagePath, OnImageListener listener);

    interface OnImageListener {
        void shopImage(Bitmap bitmap);
    }
}

public class ImageModelImpl implements ImageModel {

    @Override
    public void loadImage(String imagePath, OnImageListener listener) {
        if (listener != null && !TextUtils.isEmpty(imagePath)) {
            // Simulate network to get pictures
            if (!TextUtils.isEmpty(imagePath)) {
                listener.shopImage(BitmapFactory.decodeFile(imagePath));
            } else {
                listener.shopImage(null);
            }

        }
    }
}
  • Controller layer
public class MainActivity extends AppCompatActivity implements ImageModel.OnImageListener {

    private static final String TAG = "===>";
    // View level
    private ImageView mShowImageView;
    // Model level
    private ImageModel mImageModel;

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

        mShowImageView = findViewById(R.id.showImageView);

        findViewById(R.id.loadImageBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Loading pictures
                mImageModel.loadImage("/mnt/sdcard/Pictures/2.png", MainActivity.this);
            }
        });

        mImageModel = new ImageModelImpl();
    }

    @Override
    public void shopImage(Bitmap bitmap) {
        if (bitmap != null) {
            // Show pictures
            mShowImageView.setImageBitmap(bitmap);
        }
    }
}

2. The essence of MVP and ingenious decoupling of View and Model

MVP: Model View Presenter, an evolution mode of MVC, changes Controller to presenter, mainly to solve the first defect above, decoupling view and model, but the second defect is still not solved.

  • View: corresponding to Activity, responsible for drawing view and user interaction
  • Model: business logic and entity model
  • Person: responsible for the interaction between View and Model

shortcoming

1. Too many MVP interfaces
2. For each function, write more files than MVC
3. If multiple server interfaces need to be requested in an interface, many callback interfaces will be implemented in this interface file, resulting in complex code
4. If the data source and parameters in the request are changed, more code will be modified
5. Extra code complexity and learning cost

Advantages of MVP over MVC

1. It reduces the responsibility of the Activity, simplifies the code in the Activity, and extracts the complex logic code to the Presenter for processing. The corresponding advantage is lower coupling

2. The Activity code becomes more concise: after using MVP, the Activity can lose a lot of weight, basically only the code of findView, setListener and init. The other is the call to Presenter and the implementation of View interface. In this case, it's much easier to read the code, and if you look at the Presenter interface, you can understand what the business of this module is, and you can quickly locate the specific code. Activities become easy to understand and maintain, and businesses need to be adjusted in the future. The pruning function is much easier.

3. Convenient for unit test

  • Generally, unit testing is used to test whether there is any problem in some newly added business logic. If we adopt the traditional code style (conventionally called MV mode, without P), we may first write a test code in the Activity, and then delete the test code and replace it with formal code after the test. If there is any problem in the business, we have to replace the test code, eh, test generation The code has been deleted! OK, rewrite
  • In MVP, because the business logic is in Presenter, we can write an implementation class of PresenterTest to inherit the interface of Presenter. Now, as long as we change the creation of Presenter into PresenterTest in Activity, we can carry out unit test. After the test, we can change it back. If you find that you still need to test, change to Presenter test.

4. Avoid memory leakage of Activity

  • Cause of OOM exception:
    Now the memory leak causes the APP's memory to be insufficient, and one of the two reasons for memory leak is Activity Leak (the other is Bitmap Leak);

    A powerful function of Java is the memory recycling mechanism of its virtual machine. This function makes Java users do not need to consider the recycling of objects like C + + users when designing code. However, Java users always like to write a large number of objects casually, and then imagine that virtual machine can help them deal with the memory recycling. However, when the virtual machine reclaims memory, it will only reclaim the unreferenced objects. The referenced objects cannot be recycled because they may be called

    Activity has a life cycle. Users can switch activities at any time. When the APP memory is not enough, the system will recycle the resources of the activity in the background to avoid OOM.

  • Abnormal analysis of memory leakage caused by MVC
    Using the traditional MVC mode, a lot of asynchronous tasks and UI operations are placed in the Activity. For example, you may download a picture from the network, and load the picture into the Activity's ImageView in the successful callback, so asynchronous tasks retain the reference to the Activity. In this way, even if the Activity has been switched to the background (onDestroy has been executed), these asynchronous tasks still retain the reference to the Activity instance, so the system cannot recycle the Activity instance, and the result is the Activity Leak. In Android components, Activity objects usually occupy the most memory in the Java Heap, so the system will recycle Activity objects first. If there is an Activity Leak, the APP will be OOM easily because of insufficient memory.
  • How to avoid memory leak in MVC mode
    As long as the reference of asynchronous task to Activity is separated in the current Activity onDestroy, the Activity Leak can be avoided

5. Clear division of module responsibilities, clear levels and clear interface functions
6. The Model layer and View layer are separated and decoupled; the View is modified without affecting the Model
7. High function reuse and convenience; one Presenter can be used for multiple views without changing the Presenter's logic
8. If the background interface has not been written, but the return data type is known, the complete function of this interface can be written out

MVP role in Android

1. View: responsible for drawing UI elements and interacting with users (embodied as Activity in Android)

  • Provide UI interaction
  • Modify UI under the control of presenter
  • Leave business events to presenter
  • View layer does not store data and does not interact with Model layer
  • In Android, the View layer is generally Activity, Fragment, View (control), ViewGroup (layout), etc

2. Activity interface: the interface to be implemented by view. View interacts with Presenter through View interface to reduce coupling and facilitate unit test

3. Model: responsible for storing, retrieving and manipulating data (sometimes a Model interface is also implemented to reduce coupling)

  • Read data from network, database, file, third party and other data sources
  • Analyze and convert the external data type to the internal data of APP and submit it to the upper level for processing
  • Temporary storage, management and coordination of data request of upper layer

4. Presenter: as the intermediate link between View and Model, it deals with the responsible logic of user interaction

3. MVP case implementation

Pull down the latest 20 articles from the server side, and then display the introduction of each article on the list. When the user clicks a certain data to enter another page, the page loads the detailed content of this article.

1. ArticleModel is the Model layer interface

public interface ArticleModel {
    // Load article data
    void loadArticles(OnArticleListener listener);

    interface OnArticleListener {
        void onLoadComplete(List<Article> data);
    }

}

2. ArticleModelImpl implements the ArticleModel interface, which is used to load network data. For the sake of simple code, here we sleep for 2 seconds to simulate obtaining data from the network


public class ArticleModelImpl implements ArticleModel {
    @Override
    public void loadArticles(OnArticleListener listener) {
        new LoadArticleTask(listener).execute();
    }

    private static class LoadArticleTask extends AsyncTask<Void, Void, List<Article>> {

        private final OnArticleListener listener;

        LoadArticleTask(OnArticleListener listener) {
            this.listener = listener;
        }

        @Override
        protected List<Article> doInBackground(Void... params) {
            // Simulate network request
            SystemClock.sleep(2000);

            final List<Article> data = new ArrayList<>();
            for (int i = 0; i < 40; i++) {
                data.add(new Article("title-" + i, "message:" + i));
            }
            return data;
        }

        @Override
        protected void onPostExecute(List<Article> data) {
            if (listener != null) {
                listener.onLoadComplete(data);
            }
        }
    }
}

3. The ArticleViewInterface is the logical interface of the main interface, which represents the View interface role and is used for the Presenter's callback to View

public interface ArticleViewInterface {
    void showProgressBar(); // Show progress bar
    void hideProgressBar(); // Hide progress bar
    void showArticles(List<Article> data); // Display data
}

4. The Presenter layer serves as the intermediary between View and Model.

public interface ArticlePresenter {
    void loadArticles();
}

public class ArticlePresenterImpl implements ArticlePresenter {
    // The interface of ArticleView, representing the View role
    private ArticleViewInterface mView;
    // Model of article data, that is, model role 
    private ArticleModel mArticleModel;
    public ArticlePresenterImpl(ArticleViewInterface view) {
        this.mView = view;
        mArticleModel = new ArticleModelImpl();
    }

    // Get articles, our business logic
    @Override
    public void loadArticles() {
        mView.showProgressBar();
        mArticleModel.loadArticles(new ArticleModel.OnArticleListener() {
            @Override
            public void onLoadComplete(List<Article> data) {
                // After the data is loaded, notify the View layer to update the UI
                mView.hideProgressBar();
                mView.showArticles(data);
            }
        });
    }
}

5. The ArticleActivity needs to implement the ArticleViewInterface interface, and establish a contact with the Presenter. The business logic of the ArticleActivity is handed over to the Presenter for processing, and the processing result is called back to the ArticleActivity class through the ArticleViewInterface interface

public class ArticleActivity extends AppCompatActivity implements ArticleViewInterface {

    private ProgressBar mProgressBar;
    private ArrayAdapter<Article> mAdapter;
    private ArticlePresenter mArticlePresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        // Build the ArticlePresenter and associate with the ArticleActivity
        mArticlePresenter = new ArticlePresenterImpl(this);
    }

    private void initView() {
        mProgressBar = findViewById(R.id.load_progress_bar);

        ListView listView = findViewById(R.id.list_view);

        mAdapter = new ArrayAdapter<>(this, R.layout.item_list, R.id.item_title);

        listView.setAdapter(mAdapter);
    }

    @Override
    protected void onResume() {
        super.onResume();
        // Request article data
        mArticlePresenter.loadArticles();
    }

    @Override
    public void showArticles(List<Article> data) {
        mAdapter.setNotifyOnChange(true); 
        mAdapter.addAll(data); // Update UI
    }

   // ....
}

4. Life cycle of MVP, Activity and Fragment

Because presenters often need to perform some time-consuming operations, such as requesting network data, and presenters hold strong references to ArticleActivity. If the Activity is destroyed before the end of the request, then because the network request has not returned, presenters always hold the ArticleActivity object, which makes the ArticleActivity object cannot be recycled, and memory leak occurs at this time.

How to solve such a problem?
We can solve this problem through weak reference and life cycle of Activity and Fragment.

1. First, create a Presenter abstraction, BasePresenter, which is a generic class. The generic type is the interface type to be implemented by the View role

public abstract class BasePresenter<V> {
    private Reference<V> mViewRef; // Weak reference of View interface type

    public void attachView(V view) {
        mViewRef = new WeakReference<>(view); // Establishing correlation
    }

    public void detachView() {
        if (mViewRef != null) {
            mViewRef.clear();
            mViewRef = null;
        }
    }

    public boolean isViewAttached() {
        return mViewRef != null && mViewRef.get() != null;
    }

    protected V getView() {
        return mViewRef.get();
    }

}

2. Create an MVPBaseActivity base class, and control its relationship with the Presenter through its life cycle function. MVPBaseActivity has two generic parameters, the first is the interface type of View, and the second is the specific type of Presenter

public abstract class MVPBaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity{

    protected T mPresenter; // Presenter object
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mPresenter = createPresenter(); // Create Presenter

        mPresenter.attachView((V) this);
    }

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

    protected abstract T createPresenter();
}

If my article is helpful to you, please give me a compliment

Posted by rdimaggio on Fri, 06 Dec 2019 08:26:53 -0800