MVP形式在Android开发中的最好实习-Android-优质IT资源分享社区

admin
管理员
管理员
  • UID1
  • 粉丝27
  • 关注4
  • 发帖数581
  • 社区居民
  • 忠实会员
  • 原创写手
阅读:192回复:0

  MVP形式在Android开发中的最好实习

楼主#
更多 发布于:2016-06-02 20:51

这篇文章拖了好久了,一向存在草稿箱里没有持续写,趁今日有空,撸撸完。

回想一下,你刚刚学习Android的时候,总会看到一些书上写着,Android运用的是MVC形式,Activity即是一个Controller,或许那个时候,你没有啥深化的领会。随着经验的堆集。你发现,Activity既是Controller,掌管着许许多多的事务逻辑,一起它也作为View的一部分,操控着视图层的显现。久而久之,这个Controller便显得过于重,责任不再那么单一。

所以,再后来,为了使Activity的责任愈加单一,便呈现了MVP,MVVM等形式,只能说各有各的长处,没有谁对谁错,一个形式有另一个形式不具有的特色,一起也不具备另一个形式具有的特色,架构的挑选永远是依据事务的杂乱程度来进行的。MVC有其特色,即是写代码简略啊,可是其缺陷也很明显,事务杂乱起来后,Activity显得过于巨大不是格外好维护。至于MVVM,自己是非常排斥这种形式的,为何呢,在XML中写数据绑定的代码显得有点蛋疼,然后使得xml的责任不是那么单一,在我看来,xml用来作为View再好不过了,不用掺和别的任何元素进来,这样显得“不洁净”。而MVP呢,我觉得在Android开发中,MVP是一个值得思考的形式,它既没有MVVM那样,在xml中写数据绑定的代码,xml仍然仍是原来的配方,也没有MVC那样,拥有一个臃肿的Controller,取而代之的是愈加明晰的分层,责任愈加单一,当然,长处背面必定有缺陷,信任用过MVP的都知道有啥缺陷,那即是接口的界说会暴增。

那么啥是MVP形式呢?

M即Model,what to show?

也即是显如今UI上的数据,至于数据怎样来,数据库,网络等等途径,都是归于这一层

V即View,how to

show?也即是怎样显现数据,在Android中,通常是运用xml界说这个view,通常View中会持有Presenter的引证。

P即Presenter,Presenter扮演着中间联络人的作用,就比方MVC中的Controller,通常来说,Presenetr中通常会持有View和Model的引证。

这三者的联络如下图所示:

那么疑问来了,该怎么完成MVP形式呢?这儿介绍一个开源库Mosby,github地址https://github.com/sockeqwe/mosby

本篇文章不对该库的详细完成作剖析,假如对完成感兴趣的能够阅览源码,究竟源码之前,了无隐秘。在运用前,先参加对该库的依赖

dependencies {    compile

'com.hannesdorfmann.mosby:mvp:2.0.1'    compile

'com.hannesdorfmann.mosby:viewstate:2.0.1'}

如今假定咱们完成一个登入功用,原来的MVC办法即是先界说好xml,然后直接在Activity中书写各种事务逻辑,致使Activity越来越巨大,而运用了MVP以后,Activity会显得非常洁净。

XML的界说这儿就不再贴了,两个输入框(账号和暗码),一个登入按钮。

首要,咱们需求一个与服务器交互的接口,为了简略起见,咱们在本地进行模仿,假如账号暗码都是admin,则登入成功,假如账号暗码都是server,别的状况都回来账号或暗码过错。理论上,这个需求在子线程中建议恳求,再经过UI线程回调,这一步也省掉,直接在主线程中判别并回调,由所以本地模仿,不会发生任何卡顿,实际运用时需严厉依照子线程恳求主线程回调。

public interface Listener<T> {    void

onSuccess(T t);    void onFailure(int code);}public class LoginApi {    public

static void login(String username, String password, Listener<String>

listener) {        if (username.equals("admin") &&

password.equals("admin")) {            listener.onSuccess(null);        } else

if (username.equals("server") && password.equals("server")) {          

 listener.onFailure(LoginView.SERVER_ERROR);        } else {          

 listener.onFailure(LoginView.USERNAME_OR_PASSWORD_ERROR);        }    }}

事务逻辑的接口界说好了,这个LoginApi能够认为是Model层,接下来咱们需求界说和Login有关的View,Presenter。

首要界说一个LoginView接口承继MvpView接口,因为登录的接口有两种状况,一种是登录成功,一种是登录失利,而登录失利的状况又有多种,所以需求经过一个状态码进行区分,所以LoginView中的接口就发生了。这儿咱们直接将各种过错状态界说在了LoginView中,实际运用时建议界说在一个常量类中进行统一管理。

public interface LoginView extends MvpView {  

 public static final int USERNAME_OR_PASSWORD_EMPTY = 0x01;    public static

final int USERNAME_OR_PASSWORD_ERROR = 0x02;    public static final int

SERVER_ERROR = 0x03;    void onLoginSuccess();    void onLoginFailure(int

code);}

然后界说一个LoginPresenter类承继MvpBasePresenter,泛型参数是LoginView,在里边调用LoginApi的接口并将接口回来。

public class LoginPresenter extends

MvpBasePresenter<LoginView> {    public void login(final String username,

final String password) {        if (username == null || username.equals("")) {  

         LoginView view = getView();            if (view != null) {            

   view.onLoginFailure(LoginView.USERNAME_OR_PASSWORD_EMPTY);              

 return;            }        } else if (password == null || password.equals(""))

{            LoginView view = getView();            if (view != null) {        

       view.onLoginFailure(LoginView.USERNAME_OR_PASSWORD_EMPTY);              

 return;            }        }        Listener<String> listener = new

Listener<String>() {            @Override            public void

onSuccess(String str) {                LoginView view = getView();              

 if (view != null) {                    view.onLoginSuccess();                }

           }            @Override            public void onFailure(int code) {  

             if (code == LoginView.USERNAME_OR_PASSWORD_ERROR) {                

   LoginView view = getView();                    if (view != null) {          

             view.onLoginFailure(LoginView.USERNAME_OR_PASSWORD_ERROR);        

           }                } else {                    LoginView view =

getView();                    if (view != null) {                      

 view.onLoginFailure(LoginView.SERVER_ERROR);                    }              

 }            }        };        LoginApi.login(username, password, listener);  

 }}

最终即是让Activity完成LoginView接口,完成LoginView中界说的接口,此外,还需求承继MvpActivity,泛型参数是LoginView和LoginPresenter,并完成笼统办法createPresenter()回来LoginPresenter,而在LoginView中界说的两个接口onLoginSuccess和onLoginFailure中,全都是UI有关的代码,悉数Activity中不再有事务逻辑的代码,责任也就单一了。

public class LoginActivity extends

MvpActivity<LoginView, LoginPresenter> implements View.OnClickListener,

LoginView {    private EditText etAccount;    private EditText etPassword;  

 private Button btnLogin;    @Override    protected void onCreate(Bundle

savedInstanceState) {        super.onCreate(savedInstanceState);      

 setContentView(R.layout.activity_main);        etAccount = (EditText)

findViewById(R.id.accout);        etPassword = (EditText)

findViewById(R.id.password);        btnLogin = (Button)

findViewById(R.id.login);        btnLogin.setOnClickListener(this);    }  

 @NonNull    @Override    public LoginPresenter createPresenter() {      

 return new LoginPresenter();    }    @Override    public void onClick(View v) {

       switch (v.getId()) {            case R.id.login:              

 onLogin();                break;        }    }    private void onLogin() {    

   String username = etAccount.getText().toString();        String passowrd =

etPassword.getText().toString();        getPresenter().login(username,

passowrd);    }    @Override    public void onLoginSuccess() {      

 Toast.makeText(this, "登入成功", Toast.LENGTH_SHORT).show();    }    @Override  

 public void onLoginFailure(int code) {        switch (code) {            case

LoginView.USERNAME_OR_PASSWORD_EMPTY:                Toast.makeText(this,

"账号或暗码不能为空", Toast.LENGTH_SHORT).show();                break;            case

LoginView.USERNAME_OR_PASSWORD_ERROR:                Toast.makeText(this,

"账号或暗码过错", Toast.LENGTH_SHORT).show();                break;            case

LoginView.SERVER_ERROR:                Toast.makeText(this, "服务器过错",

Toast.LENGTH_SHORT).show();                break;        }    }}

格外需求留意的是,在Presenter中引证View时,一定要判别是不是非空,因为这个View是WeakReference弱引证,不进行判别的话会发生空指针反常。这是这个结构欠好的当地,需求屡次重复判空。

以上是这个结构最根底的用法,实际运用时咱们通常不会这么直接运用它的类,通常来说,咱们会界说各种Base类,比方BaseView,BasePresenter,BaseActivity,BaseFragment;然后将各种公共的办法都放着里边,削减冗余。假如你要引证这个结构,实际运用时略微留意一下这个疑问就能够了。

此外,Mosby还有一个LCE模块,啥是LCE模块呢,本来即是Loading-Content-Error的全称,首要用于数据的加载,显现灯作用,它体如今一个MvpLceView这个接口上以及详细的完成MvpLceActivity和MvpLceFragment上,该接口的界说如下。

public interface MvpLceViewextends MvpView {

 /**   * Display a loading view while loading data in background.   * The loading view must have the id = R.id.loadingView   *   *

@param pullToRefresh true, if pull-to-refresh has been invoked loading.   */

 public void showLoading(boolean pullToRefresh);  /**   * Show the content view.

  *   * The content view must have the id = R.id.contentView  

*/  public void showContent();  /**   * Show the error view.   * The

error view must be a TextView with the id = R.id.errorView   *   *

@param e The Throwable that has caused this error   * @param pullToRefresh true,

if the exception was thrown during pull-to-refresh, otherwise   * false.   */

 public void showError(Throwable e, boolean pullToRefresh);  /**   * The data

that should be displayed with {@link #showContent()}   */  public void setData(M

data);  /**   * Load the data. Typically invokes the presenter method to load

the desired data.   *

* Should not be called from

presenter to prevent infinity loops. The method is declared   * in   *

the views interface to add support for view state easily.   *

*   * @param pullToRefresh true, if triggered by a

pull to refresh. Otherwise false.   */  public void loadData(boolean

pullToRefresh);}

该接口中界说了5个办法,

showLoading 用于显现加载数据时的动画,比方进度条

showError 用于显现加载数据失利的内容

setData 当数据加载成功时,将数据进行赋值,在调用showContent之前进行调用

loadData

加载数据,这个办法通常是放着Activity或许Fragment中进行调用的

showContent 数据加载成功时显现

除此之外,咱们还要运用MvpLceActivity或许MvpLceFragment,还要在xml中界说有关的View,比方errorView,contenView等等。

如今咱们来实习一下,以显现一个新闻列表为例。

首要界说规划,在规划中需求声明errorView,loadingView,contentView这几个id

界说实体类,并增加结构函数和getter,setter办法

public class News {    private String title;  

 private String desprition;    public News(String title, String desprition) {  

     this.title = title;        this.desprition = desprition;    }    public

String getTitle() {        return title;    }    public void setTitle(String

title) {        this.title = title;    }    public String getDesprition() {    

   return desprition;    }    public void setDesprition(String desprition) {    

   this.desprition = desprition;    }    @Override    public String toString() {

       return "News{" +                "title='" + title + '\'' +              

 ", desprition='" + desprition + '\'' +                '}';    }}

界说View层接口,空接口,承继MvpLceView即可

public interface NewsView extends

MvpLceView<List>{}

界说Presenter层,调用Model层办法获取数据源,在运用getView之前,一定要调用isViewAttached()办法或许运用getView!=null进行判空。不然极有也许发生空指针反常,在onSuccess中,调用view层的setData和showContent进行数据的显现,在onFaliure中则调用showError显现数据加载失利。

public class NewsPresenter extends

MvpBasePresenter{    public void loadNews(final boolean pullToRefresh)

{        if (isViewAttached()) {          

 getView().showLoading(pullToRefresh);        }      

 Listener<List> listener=new Listener<List>() {        

   @Override            public void onSuccess(Listnews) {              

 if (isViewAttached()) {                    getView().setData(news);            

       getView().showContent();                }            }          

 @Override            public void onFailure(int code) {                if

(isViewAttached()) {                    getView().showError(new

Exception("msg:"+code), pullToRefresh);                }            }        };

       NewsApi.loadNews(pullToRefresh,listener);  

 }}

编写接口办法,这儿相同选用模仿,不过为了有加载动画等作用的显现,这儿在子线程中进行模仿,以后切回主线程,而且,为了达到服务器过错的模仿作用,运用了一个随机数,当随机数为奇数时则回来获取数据失利的场景

public class NewsApi {    private static Handler

handler = new Handler(Looper.getMainLooper());    private static Random random =

new Random();    public static void loadNews(final boolean pullToRefresh, final

Listener<List> listener) {        new Thread(new Runnable() {      

     @Override            public void run() {                final

Listlist = new ArrayList();                News news1 = new

News("标题1", "描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘1");                News news2 = new

News("标题2", "描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘2");                News news3 = new

News("标题3", "描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘3");                News news4 = new

News("标题4", "描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘4");                News news5 = new

News("标题5", "描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘5");                News news6 = new

News("标题6", "描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘描绘6");                list.add(news1);      

         list.add(news2);                list.add(news3);              

 list.add(news4);                list.add(news5);                if

(pullToRefresh) {                    list.add(news6);                }          

     try {                    Thread.sleep(3000);                } catch

(InterruptedException e) {                    e.printStackTrace();              

 }                handler.post(new Runnable() {                    @Override    

               public void run() {                        if (listener != null)

{                            listener.onFailure(1);                          

 int i = random.nextInt(100);                            if (i % 2 == 0) {      

                         listener.onSuccess(list);                            }

else {                                listener.onFailure(1000);                

           }                        }                    }                });  

         }        }).start();    }}

对应的Activity则是承继了MvpLceActivity,重写笼统办法,理论上来说showContent和showError是不需求重写的,可是这儿运用了SwipeRefreshLayout,需求将加载的那个圆圈给躲藏掉,需求重写这两个办法,调用setRefreshing设为false;getErrorMessage办法回来的字符串类型即是用来显如今errorView上的,当不是下拉改写时,则直接显如今errorView上,不然,运用Toast进行弹出。setData办法即是数据获取成功后对数据源进行运用,比方设置到adapter并告诉数据源改动。loadData办规律调用presenter中的办法进行加载即可

public class NewsActivity extends

MvpLceActivity<SwipeRefreshLayout, List, NewsView, NewsPresenter>

implements NewsView, SwipeRefreshLayout.OnRefreshListener {    private

RecyclerView recyclerView;    private NewsAdapter adapter;    @Override  

 protected void onCreate(Bundle savedInstanceState) {      

 super.onCreate(savedInstanceState);      

 setContentView(R.layout.activity_news);        adapter = new NewsAdapter();    

   contentView.setOnRefreshListener(this);        recyclerView = (RecyclerView)

findViewById(R.id.recyclerView);        recyclerView.setLayoutManager(new

LinearLayoutManager(this));        recyclerView.setAdapter(adapter);      

 loadData(false);    }    @NonNull    @Override    public NewsPresenter

createPresenter() {        return new NewsPresenter();    }    @Override  

 public void showContent() {        super.showContent();      

 contentView.setRefreshing(false);    }    @Override    public void

showError(Throwable e, boolean pullToRefresh) {        super.showError(e,

pullToRefresh);        contentView.setRefreshing(false);    }    @Override  

 protected String getErrorMessage(Throwable e, boolean pullToRefresh) {      

 return "发生了过错";    }    @Override    public void setData(Listdata) {    

   adapter.setNews(data);        adapter.notifyDataSetChanged();    }  

 @Override    public void loadData(boolean pullToRefresh) {      

 presenter.loadNews(pullToRefresh);    }    @Override    public void onRefresh()

{        contentView.setRefreshing(true);        loadData(true);  

 }}

adapter就不贴了,比较简略。

最终的作用如下

能够看到,最开始是加载数据失利的状况,显现了一个TextView,假如此时点击这个TextView,就会进行加载重试,然后加载出了数据,再下拉改写,数据加载失利,运用Toast进行弹出提示,假如成功,则会显现数据,这一切都变得简便了。

能够看到,运用了Mosby以后,完成Mvp显得非常简略,只需求承继自该库中对应的类,就能够轻轻松松的完成Mvp形式,该库中还有一些别的模块,比方ViewState,用于页面UI数据的恢复与存储,有兴趣的能够自行查看官网,并深化学习。处理Activity和Fragment能够做View层外,View以及ViewGroup的子类都能够作为View层,自行研讨。该库的内部很多运用了托付,有兴趣的能够学习一下源码。

最终,贴上悉数代码。

下载地址:http://download.csdn.net/detail/sbsujjbcy/9438195

优质IT资源分享社区为你提供此文。

站有大量优质android教程视频,资料等资源,包含android基础教程,高级进阶教程等等,教程视频资源涵盖传智播客,极客学院,达内,北大青鸟,猎豹网校等等IT职业培训机构的培训教学视频,价值巨大。欢迎点击下方链接查看。

android教程视频

优质IT资源分享社区(www.itziyuan.top)
一个免费,自由,开放,共享,平等,互助的优质IT资源分享网站。
专注免费分享各大IT培训机构最新培训教学视频,为你的IT学习助力!

!!!回帖受限制请看点击这里!!!
!!!资源失效请在此版块发帖说明!!!

[PS:按 CTRL+D收藏本站网址~]

——“优质IT资源分享社区”管理员专用签名~

本版相似帖子

游客