谈谈 Android MVP 架构

JewelSecres 4年前
   <h3>MVP 架构简介</h3>    <p>说起 MVP 架构,相信很多朋友都看过,网上也有很多这方面的资料。博主使用 MVP 架构搭建项目也有一段时间了。简单谈一谈心得。说到 MVP 架构,很多人都拿它跟 MVC 去对比。这里我就不过多重复说了,单刀直入。</p>    <p>什么是 MVP 架构</p>    <p>MVP 架构由 Model(模型)、View(视图)、Presenter(主持者)构成,下面我们一起来了解它们:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2049c60abbe2668525591dea7a55dd6d.png"></p>    <p>MVP 架构图</p>    <ul>     <li>Model 负责业务逻辑以及数据的处理,主要通过接口实现</li>     <li>View 负责 UI 显示以及与用户之间的交互</li>     <li>Presenter 起到一个衔接桥梁的作用,负责 Model 跟 View 之间的交互</li>    </ul>    <h3>MVP 架构的利弊</h3>    <p>优点</p>    <ol>     <li> <p>耦合度低</p> <p>View 跟 Model 之间由 Presenter 负责两者之间的交互,低了其耦合度,使其更加关注自身逻辑,结构清晰</p> </li>     <li> <p>可维护性高</p> <p>每个 View 都有其对应的 Presenter,容易进行区分,哪个模块出现了问题,或者接口出现了问题,可以迅速的确定。模型与视图之间完全分离,修改视图不影响模型</p> </li>     <li> <p>方便单元测试</p> <p>因其业务逻辑都在 Presenter 里,进行单元测试的时候,可以直接写个测试接口,由 Presenter 去继承</p> </li>    </ol>    <p>缺点</p>    <ol>     <li> <p>类数量暴涨</p> <p>每个 View 都有 Presenter ,跟其对应的接口,类的数量会明显变多,在某些场景下 Presenter 的复用会产生接口冗余。</p> </li>     <li> <p>额外的学习曲线</p> <p>需要花费额外的时间去学习,学习理解成本高,开始编写代码之前需要时间成本(项目的架构)</p> </li>    </ol>    <h3>实战演练</h3>    <p>前面讲述了一堆的理论知识,下面一步步解剖 MVP 架构,下图是 Demo 的目录结构(看起来比较复杂,勿怪),实现模拟网络获取图书数据并将其显示的功能</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/4e7d1dea022d6327b6659f2807b38a6b.png"></p>    <p>MVP Demo 目录图</p>    <p>下面我们来看项目实现效果,功能比较简单,gif图就不弄了</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/07ee25acfbd7589962a17e0cbe2af48c.png"></p>    <p>demo 运行图1</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/0ebab9923294c70de4893ff494e207b2.png"></p>    <p>demo 运行图2</p>    <p>Model</p>    <p>创建实体类 Book</p>    <pre>  <code class="language-java">public class Book {        private int book_id;      private String book_name;      private String book_author;      private String book_tag;        public Book() {      }        public Book(int book_id, String book_name, String book_author, String book_tag) {          this.book_id = book_id;          this.book_name = book_name;          this.book_author = book_author;          this.book_tag = book_tag;      }        public int getBook_id() {          return book_id;      }        public void setBook_id(int book_id) {          this.book_id = book_id;      }        public String getBook_name() {          return book_name;      }        public void setBook_name(String book_name) {          this.book_name = book_name;      }        public String getBook_author() {          return book_author;      }        public void setBook_author(String book_author) {          this.book_author = book_author;      }        public String getBook_tag() {          return book_tag;      }        public void setBook_tag(String book_tag) {          this.book_tag = book_tag;      }  }</code></pre>    <p>创建一个接口,用于获取回调实体类 Book 携带的数据</p>    <pre>  <code class="language-java">public interface BooksDataSource {        interface LoadBooksCallback{          void loadBooks(List<Book> bookList);          void dataNotAvailable();      }        void getBooks(@NonNull LoadBooksCallback loadBooksCallback);  }</code></pre>    <p>继承 BooksDataSource 接口,实现模拟数据获取</p>    <pre>  <code class="language-java">public class BooksLocalDataSource implements BooksDataSource{        private static BooksLocalDataSource INSTANCE;          public static BooksLocalDataSource getInstance() {          if (INSTANCE == null) {              INSTANCE = new BooksLocalDataSource();          }          return INSTANCE;      }        private BooksLocalDataSource(){}        @Override      public void getBooks(@NonNull LoadBooksCallback loadBooksCallback) {          //模拟数据          List<Book> bookList = new ArrayList<>();          Book book1 = new Book(1,"《第一行代码:Android (第2版) 》","郭霖","编程");          Book book2 = new Book(2,"《Android开发艺术探索》","任玉刚","编程");          Book book3 = new Book(3,"《Android群英传》","徐宜生","编程");          bookList.add(book1);          bookList.add(book2);          bookList.add(book3);          loadBooksCallback.loadBooks(bookList);        }  }</code></pre>    <p>数据回调业务处理,网络数据和本地数据(这里仅模拟本地数据)</p>    <pre>  <code class="language-java">public class BooksRepository implements BooksDataSource{        private static BooksRepository INSTANCE = null;        private final BooksDataSource mBooksRemoteDataSource;        private final BooksDataSource mBooksLocalDataSource;        private BooksRepository(@NonNull BooksDataSource booksRemoteDataSource,                              @NonNull BooksDataSource booksLocalDataSource) {          mBooksRemoteDataSource = booksRemoteDataSource;          mBooksLocalDataSource = booksLocalDataSource;      }        public static void destroyInstance() {          INSTANCE = null;      }          public static BooksRepository getInstance() {          if (INSTANCE == null) {              INSTANCE = new BooksRepository(BooksRemoteDataSource.getInstance(), BooksLocalDataSource.getInstance());          }          return INSTANCE;      }          @Override      public void getBooks(@NonNull final LoadBooksCallback loadBooksCallback) {          //数据回调          mBooksLocalDataSource.getBooks(new LoadBooksCallback() {              @Override              public void loadBooks(List<Book> bookList) {                  loadBooksCallback.loadBooks(bookList);              }                @Override              public void dataNotAvailable() {                  loadBooksCallback.dataNotAvailable();              }          });      }    }</code></pre>    <p>到这里,Model的任务算是结束了,得到了所需要的数据源。</p>    <p>View</p>    <p>Presenter 与 View 是通过接口进行交互,所以这里可以定义一个接口,用于进行交互,此处的难点在于,要清楚需要哪些方法。</p>    <p>这里建立两个Base基类,用于初始化</p>    <pre>  <code class="language-java">public interface BaseView<T> {      void setPresenter(T presenter);  }</code></pre>    <pre>  <code class="language-java">public interface BasePresenter {      void start();  }</code></pre>    <p>从上面的demo运行演示图看,这里 View 的工作主要有2个,一个是显示无数据时的状态,第二个是显示书籍的列表</p>    <pre>  <code class="language-java">void showBookList(List<Book> bookList);  void showNoBooks();</code></pre>    <p>本demo演示的是本地模拟数据,关于网络数据方面,可以模拟开启一个线程,通过 Thread.sleep( long ) 充当耗时操作,使用 ProgressBar,给用户一个友好提示,同时需要在 View 的接口中定义相关方法</p>    <p>小结:在 View 的方法定义上,需要观察功能上的操作,接着考虑:</p>    <ul>     <li>该操作需要做什么?( loadBooks )</li>     <li>操作后的结果反馈?(showBookList,showNoBooks)</li>     <li>操作中的友好交互?(显示正在加载,加载完成)</li>    </ul>    <p>经过上面的思考后,接下来就是 View 的实现,也就是 Activity ,MVP 中的 View 主要对应的是 Activity</p>    <pre>  <code class="language-java">public class MainActivity extends AppCompatActivity implements BooksContract.View {        private BooksContract.Presenter mPresenter;      private Button showBooksBtn;      private TextView noDataText;      private ListView bookListView;      private BooksAdapter booksAdapter;        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);          initView();          mPresenter = new BooksPresenter(BooksRepository.getInstance(),this);        }        private void initView() {          showBooksBtn = (Button) findViewById(R.id.show_books_btn);          noDataText = (TextView) findViewById(R.id.no_data_text);          bookListView = (ListView) findViewById(R.id.books_list_view);            showBooksBtn.setOnClickListener(new View.OnClickListener() {              @Override              public void onClick(View v) {                  mPresenter.loadBooks();              }          });      }        @Override      protected void onResume() {          super.onResume();      }        @Override      public void setPresenter(BooksContract.Presenter presenter) {          mPresenter = presenter;      }        @Override      public void showBookList(List<Book> bookList) {          if (!bookList.isEmpty()) {              noDataText.setVisibility(View.INVISIBLE);          }            booksAdapter = new BooksAdapter(getApplicationContext(), bookList);          bookListView.setAdapter(booksAdapter);      }        @Override      public void showNoBooks() {          noDataText.setVisibility(View.VISIBLE);        }  }</code></pre>    <p>从上面的代码看 Activity 实现还是比较简单的,接口引导我们去实现对应的功能,下面是 BooksAdapter 用于显示书籍列表</p>    <pre>  <code class="language-java">public class BooksAdapter extends BaseAdapter {      private List<Book> mBookList;      private Context mContext;      private LayoutInflater inflater;        public BooksAdapter(Context context, List<Book> bookList) {          inflater = LayoutInflater.from(context);          mBookList = bookList;          mContext = context;      }        @Override      public int getCount() {          return mBookList.isEmpty() ? 0 : mBookList.size();      }        @Override      public Object getItem(int position) {          return position;      }        @Override      public long getItemId(int position) {          return mBookList.get(position).getBook_id();      }        @Override      public View getView(int position, View convertView, ViewGroup parent) {            BookViewHolder bookViewHolder;          if (convertView == null) {              convertView = inflater.inflate(R.layout.book_item, parent, false);              bookViewHolder = new BookViewHolder();              bookViewHolder.itemBookName = (TextView) convertView.findViewById(R.id.item_book_name);              bookViewHolder.itemBookAuthor = (TextView) convertView.findViewById(R.id.item_book_author);              bookViewHolder.itemBookTag = (TextView) convertView.findViewById(R.id.item_book_tag);              convertView.setTag(bookViewHolder);          } else {              bookViewHolder = (BookViewHolder) convertView.getTag();          }            bookViewHolder.itemBookName.setText(mBookList.get(position).getBook_name());          bookViewHolder.itemBookAuthor.setText(mBookList.get(position).getBook_author());          bookViewHolder.itemBookTag.setText(mBookList.get(position).getBook_tag());            return convertView;      }        public class BookViewHolder {          private TextView itemBookName;          private TextView itemBookAuthor;          private TextView itemBookTag;      }  }</code></pre>    <p>到这里,View 所需要的工作我们都已经实现了,下面我们来看 Presenter</p>    <p>Presenter</p>    <p>上面讲过,Presenter 是 View 跟 Model 之间的桥梁,那它要做的工作是什么,需要有哪些方法呢?</p>    <p>这里主要看功能有什么操作,比如,上面的运行图是通过按钮去显示书籍列表,那么这里我们需要一个方法用于 Presenter 去跟 Model 拿数据</p>    <pre>  <code class="language-java">interface Presenter extends BasePresenter{          void loadBooks();      }</code></pre>    <pre>  <code class="language-java">public class BooksPresenter implements BooksContract.Presenter {        private BooksRepository mBooksRepository;      private BooksContract.View mBookView;        public BooksPresenter(@NonNull BooksRepository booksRepository, @NonNull BooksContract.View bookView) {          mBooksRepository = booksRepository;          mBookView = bookView;          mBookView.setPresenter(this);      }        @Override      public void start() {        }        @Override      public void loadBooks() {          mBooksRepository.getBooks(new BooksDataSource.LoadBooksCallback() {              @Override              public void loadBooks(List<Book> bookList) {                  mBookView.showBookList(bookList);              }                @Override              public void dataNotAvailable() {                  mBookView.showNoBooks();              }          });      }    }</code></pre>    <p>Presenter 要完成二者之间的交互,必须实现它们,从上面的代码看,得到按钮点击的通知,也就是 loadBooks() 方法,去跟 Model 拿数据,交由 BooksRepository 去处理数据的业务逻辑,最后通过 mBookView 去通知,View 进行对应的视图显示,交互。</p>    <p>源码的解析到这一步,就比较清晰了,更多 MVP 相关的 demo 可以去看官方的 demo</p>    <p> </p>    <p>来自:https://juejin.im/post/590065a0570c350058fb8d4a</p>    <p> </p>