西部猎手 發表於 2020-11-18 22:10:00

Android开发中的MVP模式详解

<div>
<p>在Android开发中,我们通常会去将项目分成一个个的模块文件夹,来进行管理维护,有的人是直接按照功能来分模块,这也是最常见的,有的人则会按照一定的设计模式,再结合功能来进行项目模式设计,比如MVP、MVVM这两种目前比较流行的项目设计模式,本文主要讲解MVP模式。</p>
<h2>MVC、MVP、MVVM</h2>
<h3>MVC</h3>
<p>对于MVC我想大家应该都不陌生,最典型的MVC就是JSP + servlet + javabean的模式了。</p>
<br>
<div class="image-package">
<div class="image-container">
<div class="image-container-fill">&nbsp;</div>
<div class="image-view" data-width="579" data-height="427"><img src="//upload-images.jianshu.io/upload_images/7017252-4d65fe9058b319d7?imageMogr2/auto-orient/strip|imageView2/2/w/579/format/webp" data-original-src="//upload-images.jianshu.io/upload_images/7017252-4d65fe9058b319d7" data-original-width="579" data-original-height="427" data-original-format="image/png" data-original-filesize="156311" data-image-index="0"></div>


</div>
<div class="image-caption">MVC1</div>


</div>
<div class="image-package">
<div class="image-container">
<div class="image-container-fill">&nbsp;</div>
<div class="image-view" data-width="452" data-height="300"><img src="//upload-images.jianshu.io/upload_images/7017252-3aa52e574f8fbefb?imageMogr2/auto-orient/strip|imageView2/2/w/452/format/webp" data-original-src="//upload-images.jianshu.io/upload_images/7017252-3aa52e574f8fbefb" data-original-width="452" data-original-height="300" data-original-format="image/jpeg" data-original-filesize="27255" data-image-index="1"></div>


</div>
<div class="image-caption">MVC2</div>


</div>
<p>这两张是我从百度中截来的图(原谅我太懒,不想画图),从中很容易看出<code>MVC</code>的大致流程,用户通过操作<code>View</code>来发送用户请求;<code>Controller</code>接收到 用户请求 后,负责决定应该调用哪个<code>Model</code>来进行处理;然后<code>Model</code>根据用户请求进行相应的业务逻辑处理,并返回数据;最后<code>Controller</code>调用相应的<code>视图View</code>来显示<code>Model</code>返回的数据。</p>
<h3>MVP</h3>
<div class="image-package">
<div class="image-container">
<div class="image-container-fill">&nbsp;</div>
<div class="image-view" data-width="537" data-height="323"><img src="//upload-images.jianshu.io/upload_images/7017252-966b4f0641835c6f?imageMogr2/auto-orient/strip|imageView2/2/w/537/format/webp" data-original-src="//upload-images.jianshu.io/upload_images/7017252-966b4f0641835c6f" data-original-width="537" data-original-height="323" data-original-format="image/png" data-original-filesize="14495" data-image-index="2"></div>


</div>
<div class="image-caption">MVP</div>


</div>
<p>首先,我们来看一下上图(没错,又是从百度上抠下来的?_?),<code>View</code>发送指令给<code>Presenter</code>,<code>Presenter</code>获取指令后,调用响应的<code>Model</code>进行业务逻辑处理,然后返回数据给<code>Presenter</code>,<code>Presenter</code>根据<code>Model</code>返回的数据再来调用相应的<code>View</code>。</p>
<h3>MVVM</h3>
<div class="image-package">
<div class="image-container">
<div class="image-container-fill">&nbsp;</div>
<div class="image-view" data-width="664" data-height="214"><img src="//upload-images.jianshu.io/upload_images/7017252-6cc29e0af2fbaeb3?imageMogr2/auto-orient/strip|imageView2/2/w/664/format/webp" data-original-src="//upload-images.jianshu.io/upload_images/7017252-6cc29e0af2fbaeb3" data-original-width="664" data-original-height="214" data-original-format="image/jpeg" data-original-filesize="22461" data-image-index="3"></div>


</div>
<div class="image-caption">MVVM</div>


</div>
<p>看上图(盗图狂魔就是我@_@),在<code>MVVM</code>中有个<code>ViewModel</code>,它的作用就是与<code>View</code>进行双向绑定,当<code>View</code>或者<code>ViewModel</code>有一方变动时,另一方也会跟着改变,其实就是观察者模式,同时<code>ViewModel</code>也会处理一些轻量的业务逻辑,具有和<code>MVP</code>中的<code>Presenter</code>的一些类似功能。当用户对<code>View</code>进行操作时,<code>ViewModel</code>就会直接收到指令,然后调用<code>Model</code>处理业务逻辑,当<code>Model</code>返回数据给<code>ViewModel</code>时,因为<code>ViewModel</code>与<code>View</code>双向绑定的缘故,<code>ViewModel</code>接收到数据后,<code>View</code>也会跟着改变,省去了<code>ViewModel</code>特意调用<code>View</code>来改变<code>View</code>的状态这一步骤!有兴趣的同学,可以去体验一下<code>Android Studio</code>中的<code>databinding</code>简单感受一下。</p>
<h2>为什么用MVP</h2>
<p>在Android中,对于Activity并没有明确的说它是属于View还是Controller的范畴,Activity既有View的性质,也具有Controller的性质,所以导致MVC在Android中很难明确分工使用,导致Activity很重。而且MVC中View会与Model直接交互,所以Activity与Model的耦合性很高,当后期维护时,稍有变动,可能Model、Activity、XML都会跟着改变,工作量很大,成本太高。</p>
<p>而MVP与MVC最大的不同之处是,MVP将M与V分隔开来,通过P交互,这样在Android中,就可以明确的把Activity当作View处理,虽然可能还有一点逻辑在其中,但是已经无伤大雅;View和Model不直接交互,当View有变动或者Model有变动时,不会相互影响,有太大变动,,耦合性低,对于后期维护来说,特别是项目越来越庞大时,可以很快的理清项目结构,找到需要修改的地方,大大的缩短了工作量。而且,因为View与Model分离的缘故,Model可以单独进行单元测试。</p>
<p>对于MVVM,其实很多框架中都使用到了它,比如说Vue.js、AngularJs都使用到了这种模式,在Android中也有DataBinding这个原生插件可以使用,来达到双向绑定的作用,但只是使用了DataBinding并不是完全的MVVM模式,个人认为还必须有个中间层类似与Presenter一样的层,来处理一些交互;说实话,在Android中使用了MVVM后,的确大大的提高了开发效率,省去了很多代码,但是如果只是使用纯粹的MVVM,当项目变得庞大后,还是有些吃不消的,各种改动的工作量不是闹着玩的,所以个人认为MVVM只是适合中小型项目,对于大项目还是有点吃紧的。</p>
<p>所以,最后个人认为如果你的项目会越来越庞大,但是又想体验MVVM那种便利,不妨试试MVP+DataBinding,其实这就有点类似于MVPVM模式了,方便快捷,即使项目庞大,改动时也不需要太多重构。</p>
<h2>MVPLoader</h2>
<p>好了,上面说了那么多,我们还是来点实际的吧,下面是本人在项目中对MVP的处理方式,有不同见解的,欢迎大家提出。</p>
<div class="cnblogs_code">
<pre>==================================View=======================================<span style="color: rgba(0, 0, 0, 1)">

所有的view(Activity、FragmentActivity、Fragment...)都必须实现这个接口

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> IView {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 此方法是为了当Presenter中需要获取上下文对象时,传递上下文对象,而不是让Presenter直接持有上下文对象</span>
<span style="color: rgba(0, 0, 0, 1)">    Activity getSelfActivity();
}

这是Activity的基类:

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">abstract</span> <span style="color: rgba(0, 0, 255, 1)">class</span> BaseActivity&lt;P extends IPresenter&gt;<span style="color: rgba(0, 0, 0, 1)"> extends Activity implements IView {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Presenter对象</span>
    <span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> P MvpPre;

    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onCreate(@Nullable Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      MvpPre </span>=<span style="color: rgba(0, 0, 0, 1)"> bindPresenter();
    }
   
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 绑定Presenter</span>
    <span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">abstract</span><span style="color: rgba(0, 0, 0, 1)"> P bindPresenter();

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> &lt;T&gt; T $(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> resId) {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (T) findViewById(resId);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> &lt;T&gt; T $(<span style="color: rgba(0, 0, 255, 1)">int</span><span style="color: rgba(0, 0, 0, 1)"> resId, View parent) {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (T) parent.findViewById(resId);
    }

    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> Activity getSelfActivity() {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">;
    }

    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onDestroy() {
      super.onDestroy();
      </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
         * 在生命周期结束时,将presenter与view之间的联系断开,防止出现内存泄露
         </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span> (MvpPre != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            MvpPre.detachView();
      }
    }
}

</span>==================================Presenter=======================================
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> IPresenter {
    </span><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> detachView();
}

Presenter的基类:

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">abstract</span> <span style="color: rgba(0, 0, 255, 1)">class</span> BasePresenter&lt;V extends IView&gt;<span style="color: rgba(0, 0, 0, 1)"> implements IPresenter {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 此处使用弱引用是因为,有时Activity关闭不一定会走onDestroy,所以这时使用弱引用可以及时回收IView</span>
    <span style="color: rgba(0, 0, 255, 1)">protected</span> Reference&lt;V&gt;<span style="color: rgba(0, 0, 0, 1)"> MvpRef;

    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> BasePresenter(V view) {
      attachView(view);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> attachView(V view) {
      MvpRef </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> WeakReference&lt;V&gt;<span style="color: rgba(0, 0, 0, 1)">(view);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> V getView() {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (MvpRef != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> MvpRef.<span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)">();
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
   * 主要用于判断IView的生命周期是否结束,防止出现内存泄露状况
   *
   * @return
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span>
    <span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> boolean isViewAttach() {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> MvpRef != <span style="color: rgba(0, 0, 255, 1)">null</span> &amp;&amp; MvpRef.<span style="color: rgba(0, 0, 255, 1)">get</span>() != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
    }

    </span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
   * Activity生命周期结束时,Presenter也清除IView对象,不在持有
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> detachView() {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (MvpRef != <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">) {
            MvpRef.clear();
            MvpRef </span>= <span style="color: rgba(0, 0, 255, 1)">null</span><span style="color: rgba(0, 0, 0, 1)">;
      }
    }
}

</span>===================================demo========================================<span style="color: rgba(0, 0, 0, 1)">
接口:
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 创建一个类作为纽带,将view、presenter、model的接口方法都串联在一起,更加便于管理
</span><span style="color: rgba(0, 128, 0, 1)">*/</span>
<span style="color: rgba(0, 0, 255, 1)">public</span> final <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MainContacts {
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> IMain extends IView {
      </span><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> showTips(boolean isSucceess);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> IMainPre extends IPresenter {
      </span><span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> login(String username, String password);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">interface</span><span style="color: rgba(0, 0, 0, 1)"> IMainLgc {
      boolean login(String username, String password);
    }
}


Model部分:

</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> MainLogic implements MainContacts.IMainLgc {

    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> boolean login(String username, String password) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (TextUtils.isEmpty(username) ||<span style="color: rgba(0, 0, 0, 1)"> TextUtils.isEmpty(password)) {
            </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
      }
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
    }
}


View部分:
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span> MainActivity extends BaseActivity&lt;MainPresnter&gt;<span style="color: rgba(0, 0, 0, 1)"> implements MainContacts.IMain {
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> EditText editT_username, editT_password;
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> Button btn_login;

    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);
      initUI();
      addListeners();
    }

    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">protected</span><span style="color: rgba(0, 0, 0, 1)"> MainPresnter bindPresenter() {
      </span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">new</span> MainPresnter(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> initUI() {
      editT_username </span>=<span style="color: rgba(0, 0, 0, 1)"> $(R.id.editT_username);
      editT_password </span>=<span style="color: rgba(0, 0, 0, 1)"> $(R.id.editT_password);
      btn_login </span>=<span style="color: rgba(0, 0, 0, 1)"> $(R.id.btn_login);
    }

    </span><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> addListeners() {
      btn_login.setOnClickListener(</span><span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> View.OnClickListener() {
            @Override
            </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> onClick(View v) {
                MvpPre.login(editT_username.getText().toString(), editT_password.getText().toString());
            }
      });
    }

    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> showTips(boolean isSucceess) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isSucceess) {
            Toast.makeText(</span><span style="color: rgba(0, 0, 255, 1)">this</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">登录成功!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, Toast.LENGTH_SHORT).show();
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            Toast.makeText(</span><span style="color: rgba(0, 0, 255, 1)">this</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">登录失败!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, Toast.LENGTH_SHORT).show();
      }
    }
}


Presenter部分:
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span> MainPresnter extends BasePresenter&lt;MainContacts.IMain&gt;<span style="color: rgba(0, 0, 0, 1)"> implements MainContacts.IMainPre {
    </span><span style="color: rgba(0, 0, 255, 1)">private</span><span style="color: rgba(0, 0, 0, 1)"> MainLogic mMainLogic;

    </span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> MainPresnter(MainContacts.IMain view) {
      super(view);
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.mMainLogic = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> MainLogic();
    }

    @Override
    </span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> login(String username, String password) {
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 判断activity的生命周期是否结束,不判断的话在极端情况下可能会出现内存泄露</span>
      <span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (isViewAttach()) {
            MvpRef.</span><span style="color: rgba(0, 0, 255, 1)">get</span><span style="color: rgba(0, 0, 0, 1)">().showTips(mMainLogic.login(username, password));
      }
    }
}</span></pre>
</div>
<p>&nbsp;</p>
<p>好了,以上便是本人对MVP模式的一些理解,如果你不想自己在重新搭建MVP框架,可以直接使用MVPLoader项目做依赖:</p>
<div class="_2Uzcx_">&nbsp;</div>
<div class="_2Uzcx_">
<div class="cnblogs_code">
<pre>Step <span style="color: rgba(128, 0, 128, 1)">1</span><span style="color: rgba(0, 0, 0, 1)">:

      allprojects {
          repositories {
            ...
            maven { url </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">https://jitpack.io</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)"> }
          }
      }
      
Step </span><span style="color: rgba(128, 0, 128, 1)">2</span><span style="color: rgba(0, 0, 0, 1)">:

      dependencies {
          compile </span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(128, 0, 0, 1)">com.github.albert-lii:MVPLoader:1.0.6</span><span style="color: rgba(128, 0, 0, 1)">'</span><span style="color: rgba(0, 0, 0, 1)">
      }</span></pre>
</div>
<p>&nbsp;</p>
</div>
<p>如果你有不同的理解,也欢迎提出,大家一起进步。</p>
<h2>Github地址</h2>
<blockquote>
<h3><strong>https://github.com/albert-lii/MVPLoader</strong></h3>
<p><strong>如果觉得不错,给个赞吧</strong></p>
</blockquote>
</div>
<p><br><br>作者:albertlii<br>链接:https://www.jianshu.com/p/479aca31d993<br>来源:简书<br>著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。</p><br><br>
来源:https://www.cnblogs.com/sheseido/p/14002528.html
頁: [1]
查看完整版本: Android开发中的MVP模式详解