登录 立即注册
安币:

安卓巴士 - 安卓开发 - Android开发 - 安卓 - 移动互联网门户

重构!将Google MVP应用于已有项目 [复制链接]

2017-1-4 16:30
MrlLee 阅读:720 评论:0 赞:0
Tag:  AndroidGoogle开发者项目而且

将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代码:

public interface BasePresenter {
    void start();
}


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

[代码]java代码:

public interface BaseView {
    void setPresenter(T presenter);
    void initViews(View view);
}


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

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

[代码]java代码:

public interface ZhihuDailyContract {
    interface View extends BaseView {
        void showError();
        void showLoading();
        void stopLoading();
        void showResults(ArrayList 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();
    }
}


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

[代码]java代码:

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代码:

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 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();
    }
}


创建Presenter。

[代码]java代码:

public class ZhihuDailyPresenter implements ZhihuDailyContract.Presenter, OnStringListener {
    private ZhihuDailyContract.View view;
    private Context context;
    private StringModelImpl model;
    private ArrayList list = new ArrayList();
    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();
    }
}


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

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

[代码]java代码:

public interface OnStringListener {
    /**
     * 请求成功时回调
     * @param result
     */
    void onSuccess(String result);
    /**
     * 请求失败时回调
     * @param error
     */
    void onError(VolleyError error);
}


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

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

[代码]java代码:

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() {
            
@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);
    }
}


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

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

本项目地址

纸飞机-采用MVP架构,一款集合了知乎日报、果壳精选和豆瓣一刻的综合性阅读客户端

转自:https://marktony.github.io/

分享到:
我来说两句
您需要登录后才可以评论 登录 | 立即注册
所有评论(0)

站长推荐

通过邮件订阅最新安卓weekly信息
上一条 /5 下一条

广告投放| 申请友链|手机版|站点统计|安卓巴士

返回顶部