手记

重构!将Google MVP应用于已有项目

将Google官方的Android MVP架构引入到已有的项目中。

前言

在本次项目重构之前,我的项目采用的是什么架构呢?

额,没有架构…或者说,不那么标准的MVC,一个页面就是一个Activity或者Fragment,各种数据,网络请求,响应都写在Activity或者Fragment–这两个不怎么标准的Controller中,代码混乱,而且长长长长长长,写代码的时候一时爽,到了维护阶段,那酸爽,不敢相信。采用新的架构迫在眉睫。

事实上,已经有很多的App项目采用了MVP或者MVVM等架构。由于并没有那么权威的实现,很多开发者也陷入了选择困难症,在各种架构直接摇摆不定,找不到适合自己项目架构。而Google也适时的推出了一系列官方示例用于参考。

Google示例项目

Android Architecture Blueprints [beta]

示例项目以一个TODO APP为例,目前仍然在进行中。本次项目重构采用的是基础版 todo-mvp

示例项目的代码组织方式和我之前以adapter, activity, fragment等组织方式不同,采用的是按照功能划分,一种功能就是一个包,包内文件以xxxactivity, xxxfragment, xxxcontract, xxxpresenter命名,xxx代表着所要实现的功能。本次项目重构两种方式均有采用。

重构

首先是仿照TODO MVP,建立两个Base接口,BaseView和BasePresenter,这两个基本接口是所有的View和Presenter的基类。

[代码]java代码:

?

1

2

3

public interface BasePresenter {

    void start();

}

 

BasePresenter中有方法start(),作用是Presenter开始获取数据并改变界面显示,调用时机为Fragment的onResume()方法中。

[代码]java代码:

?

1

2

3

4

public interface BaseView<t> {

    void setPresenter(T presenter);

    void initViews(View view);

}</t>

 

BaseView中有方法setPresenter(),将Presenter示例传入view,调用时机为Presenter实现类的构造方法中,initViews()传入View实例,用于初始化界面元素,调用时机为Fragment的onCreate()方法中。

接着创建契约类,用于统一管理View和Presenter的所有接口。这里以知乎日报的部分为例。

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

public interface ZhihuDailyContract {

    interface View extends BaseView<presenter> {

        void showError();

        void showLoading();

        void stopLoading();

        void showResults(ArrayList<zhihudailynews.question>   list);

        void showNetworkError();

    }

    interface Presenter extends BasePresenter {

        void loadPosts(long date, boolean clearing);

        void refresh();

        void loadMore(long date);

        void startReading(int position);

        void goToSettings();

    }

}</zhihudailynews.question></presenter>

 

然后创建相应的Activity,在官方的示例项目中,Activity是作为View和Presenter的桥梁使用,用于创建View和Presenter实例。本项目由于涉及了TabLayout和ViewPager的使用,所以创建View和Presenter的部分我放到了ViewPager的Adapter中实现。

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

public class MainPagerAdapter   extends FragmentPagerAdapter   {

    private String[] titles;

    private final Context context;

    public MainPagerAdapter(FragmentManager fm,   Context context) {

        super(fm);

        this.context   = context;

        titles   = context.getResources().getStringArray(R.array.page_titles);

    }

     

@Override

    public Fragment getItem(int position) {

        if (position == 1){

            GuokrFragment   fragment = GuokrFragment.newInstance();

            new GuokrPresenter(context, fragment);

            return fragment;

        }   else if (position == 2){

            DoubanMomentFragment   fragment = DoubanMomentFragment.newInstance();

            new DoubanMomentPresenter(context, fragment);

            return fragment;

        }

        ZhihuDailyFragment   fragment = ZhihuDailyFragment.newInstance();

        new ZhihuDailyPresenter(context, fragment);

        return fragment;

    }

     

@Override

    public int getCount() {

        return titles.length;

    }

     

@Override

    public CharSequence getPageTitle(int position) {

        return titles[position];

    }

}

 

Fragment在示例项目中的角色为View的具体实现类。

[代码]java代码:

?

001

002

003

004

005

006

007

008

009

010

011

012

013

014

015

016

017

018

019

020

021

022

023

024

025

026

027

028

029

030

031

032

033

034

035

036

037

038

039

040

041

042

043

044

045

046

047

048

049

050

051

052

053

054

055

056

057

058

059

060

061

062

063

064

065

066

067

068

069

070

071

072

073

074

075

076

077

078

079

080

081

082

083

084

085

086

087

088

089

090

091

092

093

094

095

096

097

098

099

100

101

102

103

104

105

106

107

108

109

110

111

112

113

public class ZhihuDailyFragment   extends Fragment

        implements ZhihuDailyContract.View{

    private RecyclerView recyclerView;

    private SwipeRefreshLayout refresh;

    private FloatingActionButton fab;

    private ZhihuDailyNewsAdapter adapter;

    private ZhihuDailyContract.Presenter presenter;

    public ZhihuDailyFragment() {

    }

    public static ZhihuDailyFragment newInstance() {

        return new ZhihuDailyFragment();

    }

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

    }

     

@Nullable

     

@Override

    public View onCreateView(LayoutInflater   inflater, ViewGroup container, Bundle savedInstanceState) {

        View   view = inflater.inflate(R.layout.fragment_douban_zhihu_daily,container,false);

        initViews(view);

        presenter.loadPosts(Calendar.getInstance().getTimeInMillis(),   false);

        refresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {

             

@Override

            public void onRefresh() {

                presenter.refresh();

            }

        });

        fab.setOnClickListener(new View.OnClickListener() {

             

@Override

            public void onClick(View v) {

                ...

            }

        });

        return view;

    }

     

@Override

    public void setPresenter(ZhihuDailyContract.Presenter presenter) {

        if (presenter != null) {

            this.presenter   = presenter;

        }

    }

     

@Override

    public void initViews(View view) {

        recyclerView   = (RecyclerView) view.findViewById(R.id.rv_main);

        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));

        recyclerView.addItemDecoration(new DividerItemDecoration(getActivity(),LinearLayoutManager.VERTICAL));

        refresh   = (SwipeRefreshLayout) view.findViewById(R.id.refresh);

        fab   = (FloatingActionButton) view.findViewById(R.id.fab);

        fab.setRippleColor(getResources().getColor(R.color.colorPrimaryDark));

    }

     

@Override

    public void showError() {

        Snackbar.make(fab,   R.string.loaded_failed,Snackbar.LENGTH_SHORT).show();

    }

     

@Override

    public void showLoading() {

        refresh.post(new Runnable() {

             

@Override

            public void run() {

                refresh.setRefreshing(true);

            }

        });

    }

     

@Override

    public void stopLoading() {

        refresh.post(new Runnable() {

             

@Override

            public void run() {

                refresh.setRefreshing(false);

            }

        });

    }

     

@Override

    public void showResults(ArrayList<zhihudailynews.question> list) {

        if (adapter == null) {

            adapter   = new ZhihuDailyNewsAdapter(getContext(),   list);

            adapter.setItemClickListener(new OnRecyclerViewOnClickListener() {

                 

@Override

                public void OnItemClick(View v, int position) {

                    presenter.startReading(position);

                }

            });

            recyclerView.setAdapter(adapter);

        }   else {

            adapter.notifyDataSetChanged();

        }

    }

     

@Override

    public void showNetworkError() {

        Snackbar.make(fab,R.string.no_network_connected,Snackbar.LENGTH_INDEFINITE)

                .setAction(R.string.go_to_set,   new View.OnClickListener()   {

                     

@Override

                    public void onClick(View v) {

                        presenter.goToSettings();

                    }

                }).show();

    }

}</zhihudailynews.question>

 

创建Presenter。

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

public class ZhihuDailyPresenter   implements ZhihuDailyContract.Presenter,   OnStringListener {

    private ZhihuDailyContract.View view;

    private Context context;

    private StringModelImpl model;

    private ArrayList<zhihudailynews.question>   list = new ArrayList<zhihudailynews.question>();

    public ZhihuDailyPresenter(Context context,   ZhihuDailyContract.View view) {

        this.context   = context;

        this.view   = view;

        this.view.setPresenter(this);

        model   = new StringModelImpl(context);

    }

     

@Override

    public void loadPosts(long date, boolean clearing) {

        view.showLoading();

        if (clearing) {

            list.clear();

        }

        model.load(Api.ZHIHU_HISTORY   + formatter.ZhihuDailyDateFormat(date), this);

    }

     

@Override

    public void refresh() {

        list.clear();

        loadPosts(Calendar.getInstance().getTimeInMillis(),   true);

    }

     

@Override

    public void loadMore(long date) {

        if (NetworkState.networkConnected(context))   {

            model.load(Api.ZHIHU_HISTORY   + formatter.ZhihuDailyDateFormat(date), this);

        }   else {

            view.showNetworkError();

        }

    }

     

@Override

    public void startReading(int position) {

        context.startActivity(new Intent(context, ZhihuDetailActivity.class)

                .putExtra("id",list.get(position).getId())

        );

    }

     

@Override

    public void goToSettings() {

        context.startActivity(new Intent(Settings.ACTION_SETTINGS));

    }

     

@Override

    public void start() {

    }

     

@Override

    public void onSuccess(String result) {

        Gson   gson = new Gson();

        ZhihuDailyNews   post = gson.fromJson(result, ZhihuDailyNews.class);

        for (ZhihuDailyNews.Question item :   post.getStories()) {

            list.add(item);

        }

        view.showResults(list);

        view.stopLoading();

    }

     

@Override

    public void onError(VolleyError error) {

        view.stopLoading();

        view.showError();

    }

}</zhihudailynews.question></zhihudailynews.question>

 

Presenter获取到了View,并通过调用setPresenter()方法将自身传入,如果需要对改变界面显示,直接调用View层的方法即可。这样Presenter就于View层实现了分离。

最后是Model层的实现。由于使用了Gson,数据的返回类型只需要为String类型即可。

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

public interface OnStringListener {

    /**

     * 请求成功时回调

     * @param   result

     */

    void onSuccess(String result);

    /**

     * 请求失败时回调

     * @param   error

     */

    void onError(VolleyError error);

}

 

定义了两个方法,分别为请求成功时和请求失败时的回调。

然后需要一个StringModel的实现类–StringModelImpl。

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

public class StringModelImpl   {

    private Context context;

    public StringModelImpl(Context context) {

        this.context   = context;

    }

    public void load(String url, final OnStringListener listener) {

        StringRequest   request = new StringRequest(url,   new Response.Listener<string>()   {

             

@Override

            public void onResponse(String s) {

                listener.onSuccess(s);

            }

        },   new Response.ErrorListener()   {

             

@Override

            public void onErrorResponse(VolleyError volleyError) {

                listener.onError(volleyError);

            }

        });

        VolleySingleton.getVolleySingleton(context).addToRequestQueue(request);

    }

}</string>

 

这样,Model, View, Presenter均已实现,实现了各个层次的分离。

采用MVP架构进行重构,代码量上相对于原项目时有所增加的,但这种数量的增加相对于MVP架构带来的好处是显而易见的。当然这是对于代码量比较大的项目而言,平时用于练手的小项目就没有必要强项目所难,勉强的实现MVP了,这样只会增加代码量而已。

原文链接:http://www.apkbus.com/blog-705730-62886.html

0人推荐
随时随地看视频
慕课网APP