Android开发MVP模式实践
kusajin
10年前
<p>现在用一个基于MVP模式的APP项目进一步分析MVP的实际应用。</p> <p> </p> <p><a href="/misc/goto?guid=4958987558570479788"><span style="color:#3366ff">原项目</span></a>应该使用的是Android studio开发,笔者对项目进行了整理,广大Eclipser请猛点<a href="/misc/goto?guid=4959671473499252396"><span style="color:#3366ff">Github链接</span></a>。</p> <p><strong>一、项目功能说明</strong></p> <p>APP获取好友列表后将数据展示在一个ListView中,点击Item会打开一个新页面展示好友详细信息。</p> <p><img alt="" src="https://simg.open-open.com/show/27d53469cc649591d1276c8b3fc61d38.png"><img alt="" src="https://simg.open-open.com/show/8d19b41393c1bc532a85804a545d1248.png"></p> <p><strong>二、项目结构</strong></p> <p><img alt="" src="https://simg.open-open.com/show/e51fc19939407781df5cf343750a3d1d.png"></p> <p>示例将代码分为四层,对应到MVP模式中:</p> <p> </p> <ul> <li><span style="color:#009900"><strong>Mode:</strong></span>Entities</li> <li><span style="color:#009900"><strong>Presenter:</strong></span>Use Cases+Presenters</li> <li><strong><span style="color:#009900">View:</span></strong>UI</li> </ul> <p>为了保证每个层都能方便的进行单元测试并和其它层相对独立,将项目分为三个模块,Presentation、Domain和Data。</p> <p><img alt="" src="https://simg.open-open.com/show/1b945727457d92681597600b63a1224b.png"></p> <p> </p> <p><span style="color:#009900"><strong>1)Presentation</strong></span></p> <p>Presenters和UI被划分到这一层,但Presenters在这里只是负责将Domain逻辑处理后的数据进行组装并调度UI显示,没有业务处理逻辑。这样将数据逻辑处理划分给Domain可以让Presenter更关注于UI显示的调度,从而避免Present逻辑的冗余。这也是我选择这个工程作为示例的原因。</p> <p><span style="color:#009900"><strong>2)Domain</strong></span></p> <p>这里对Data中的数据进行逻辑处理,为Present提供业务逻辑和数据支持。</p> <p><span style="color:#009900"><strong>3)Data</strong></span></p> <p>数据仓库。例如,当通过id获取用户数据时,首先会检测用户信息是否已经存储在本地,否则的话就会从服务器获取后在本地缓存。根据上篇博客提到的设计原则,外圆代码逻辑无需关心用户数据是从存储介质、内存还是服务器获取,只需拿到Domain处理好的最终数据进行展示和交互即可。</p> <p><img alt="" src="https://simg.open-open.com/show/10b7025a00bdfdc1294b5611c0a4b152.png"></p> <p>根据以上说明,笔者将APP分为三个工程</p> <p><img alt="" src="https://simg.open-open.com/show/08548176abc317ab39afc61b6a2bea70.png"></p> <p>其中Domain和Data作为Library的形式供Presentation引入。</p> <p><strong>三、代码详解</strong></p> <p>通过获取用户详细信息这个功能分析各个层级之间的调度及数据传递方向和方式。</p> <p>首先入口View层的UserDetaisFragment。</p> <p> </p> <pre> <code class="language-java">public class UserDetailsFragment extends BaseFragment implements UserDetailsView</code></pre> <pre> <code class="language-java">private UserDetailsPresenter userDetailsPresenter;</code></pre> <pre> <code class="language-java">@Override void initializePresenter() { // All these dependency initialization could have been avoided using a // dependency injection framework. But in this case are used this way for // LEARNING EXAMPLE PURPOSE. ThreadExecutor threadExecutor = JobExecutor.getInstance(); PostExecutionThread postExecutionThread = UIThread.getInstance(); JsonSerializer userCacheSerializer = new JsonSerializer(); UserCache userCache = UserCacheImpl.getInstance(getActivity(), userCacheSerializer, FileManager.getInstance(), threadExecutor); UserDataStoreFactory userDataStoreFactory = new UserDataStoreFactory(this.getContext(), userCache); UserEntityDataMapper userEntityDataMapper = new UserEntityDataMapper(); UserRepository userRepository = UserDataRepository.getInstance(userDataStoreFactory, userEntityDataMapper); GetUserDetailsUseCase getUserDetailsUseCase = new GetUserDetailsUseCaseImpl(userRepository, threadExecutor, postExecutionThread); UserModelDataMapper userModelDataMapper = new UserModelDataMapper(); this.userDetailsPresenter = new UserDetailsPresenter(this, getUserDetailsUseCase, userModelDataMapper); }</code></pre> <p>UserDetailsFragment内部有个UserDetailsPresenter引用,下面我们看UserDetailsPresenter代码。</p> <p> </p> <p> </p> <pre> <code class="language-java">public class UserDetailsPresenter implements Presenter { /** id used to retrieve user details */ private int userId; private final UserDetailsView viewDetailsView; private final GetUserDetailsUseCase getUserDetailsUseCase; private final UserModelDataMapper userModelDataMapper; public UserDetailsPresenter(UserDetailsView userDetailsView, GetUserDetailsUseCase getUserDetailsUseCase, UserModelDataMapper userModelDataMapper) { if (userDetailsView == null || getUserDetailsUseCase == null || userModelDataMapper == null) { throw new IllegalArgumentException("Constructor parameters cannot be null!!!"); } this.viewDetailsView = userDetailsView; this.getUserDetailsUseCase = getUserDetailsUseCase; this.userModelDataMapper = userModelDataMapper; }</code></pre> <pre> <code class="language-java">private void showViewLoading() { this.viewDetailsView.showLoading(); } private void getUserDetails() { this.getUserDetailsUseCase.execute(this.userId, this.userDetailsCallback); } private final GetUserDetailsUseCase.Callback userDetailsCallback = new GetUserDetailsUseCase.Callback() { @Override public void onUserDataLoaded(User user) { UserDetailsPresenter.this.showUserDetailsInView(user); UserDetailsPresenter.this.hideViewLoading(); } @Override public void onError(ErrorBundle errorBundle) { UserDetailsPresenter.this.hideViewLoading(); UserDetailsPresenter.this.showErrorMessage(errorBundle); UserDetailsPresenter.this.showViewRetry(); } }; } private void hideViewLoading() { this.viewDetailsView.hideLoading(); } private void showViewRetry() { this.viewDetailsView.showRetry(); } private void hideViewRetry() { this.viewDetailsView.hideRetry(); }</code></pre> <p>Presenter包涵一个GetUserDetailsUseCase引用,并在调用getUserDetails()方法时传入一个callBack作为数据处理结果的回调。可以看到Presenter中并没有复杂的逻辑处理,反而更多的是hideViewRetry(),showViewLoading()等对Fragemnt的显示调度方法。Fragemt实现了UserDetailsView接口协议并在实例化Presenter时当做参数传入,这样就搭建好了符合MVP模式的Presenter和View的交互方式。</p> <p> </p> <p> </p> <pre> <code class="language-java">public interface UserDetailsView extends LoadDataView { /** * Render a user in the UI. * * @param user The {@link UserModel} that will be shown. */ void renderUser(UserModel user); }</code></pre> <pre> <code class="language-java">public interface LoadDataView { /** * Show a view with a progress bar indicating a loading process. */ void showLoading(); /** * Hide a loading view. */ void hideLoading(); /** * Show a retry view in case of an error when retrieving data. */ void showRetry(); /** * Hide a retry view shown if there was an error when retrieving data. */ void hideRetry(); /** * Show an error message * * @param message A string representing an error. */ void showError(String message); /** * Get a {@link android.content.Context}. */ Context getContext(); }</code></pre> <p>下面我们看GetUserDetailUseCase代码。GetUserDetailUseCase在Domain模块,负责加工处理Data模块的数据。</p> <p> </p> <p> </p> <pre> <code class="language-java">public class GetUserDetailsUseCaseImpl implements GetUserDetailsUseCase {</code></pre> <pre> <code class="language-java">private final UserRepository userRepository;</code></pre> <pre> <code class="language-java"> @Override public void execute(int userId, Callback callback) { if (userId < 0 || callback == null) { throw new IllegalArgumentException("Invalid parameter!!!"); } this.userId = userId; this.callback = callback; this.threadExecutor.execute(this); }</code></pre> <pre> <code class="language-java">@Override public void run() { this.userRepository.getUserById(this.userId, this.repositoryCallback); }</code></pre> <pre> <code class="language-java">private final UserRepository.UserDetailsCallback repositoryCallback = new UserRepository.UserDetailsCallback() { @Override public void onUserLoaded(User user) { notifyGetUserDetailsSuccessfully(user); } @Override public void onError(ErrorBundle errorBundle) { notifyError(errorBundle); } }; private void notifyGetUserDetailsSuccessfully(final User user) { this.postExecutionThread.post(new Runnable() { @Override public void run() { callback.onUserDataLoaded(user); } }); }</code></pre> <pre> <code class="language-java">}</code></pre> <p>GetUserDetailUseCaseImpl又会调用Data模块的UserRepository获取用户数据。UserRepository会从本地存储或服务器获取用户数据,这里就不再跟进UserRepository的源码。</p> <p> </p> <p>当GetUserDetailUseCaseImpl从UserRepository获取用户数据后通过UserRepository.UserDetailCallback回调给UserDetailPresenter,然后Presenter就会根据数据调度Fragment的显示。<br> 下面总结下从用户点击Item到打开用户详情页的调用流程及数据流动方向</p> <p><img alt="" src="https://simg.open-open.com/show/21ffac79211b3a72fa50e58bc8a2da2d.png"></p> <p>用户点击Fragment中ListView的Item,Fragment向Presentor询问用户详情信息,Presentor将命令传递给UseCase,UseCase从Data获取数据并加工后通过callBack将数据传递回Presentor,Presentor最终告诉Fragment打开新页面并展示数据。</p> <p>大家下载源码后会发现甭管是Presentor和UI,还是UseCase和Data,都是通过接口协议进行交互,这也是MVP模式的特点之一。</p> <p>MVP就分析到这里,我认识的也很有限,欢迎大家讨论指正。<br> <br> <strong>参考资料:</strong><br> <br> <a href="/misc/goto?guid=4959671469250617399" style="font-family:'Microsoft YaHei'; font-size:18px"><span style="color:#3366ff">MVC or MVP Pattern - Whats the difference?</span></a></p> <p>来自: <a href="/misc/goto?guid=4959671473619912690" rel="nofollow">http://blog.csdn.net/guxiao1201/article/details/40151457</a></p>