安卓中MVC模式的深度思索和实践(二)

来源:互联网 发布:什么蛋糕品牌最好 知乎 编辑:程序博客网 时间:2024/06/05 07:22

这是一个有关安卓MVC框架模式的短系列,目的是思索和分析安卓中MVC模式更为真实的一面。

系列:

-安卓中MVC模式的深度思索和实践(一)
-安卓中MVC模式的深度思索和实践(三)

转载请标明出处: http://blog.csdn.net/cysion1989/article/details/71486875

在上一篇中,主要从一个比较传统但又精致的角度重新审视了一下安卓中的MVC模式。首先回顾一下上篇中最后有关MVC的观点,核心是分层,重点是职责如何单一和清晰化:
- 视图V,具备展示职责,职责的划分是通过与控制者的改变无关这条原则来进行的;比如在安卓中,以LinearLayout为例,不管使用者怎么改变,它要么纵向布局,要么横向布局,其本身就是这样,而与使用的场景无关。整体来说,V层,就是要好好的担起展示的职责,不要把属于展示的职责,交给其它层去做。
- 数据M,具备数据存取和操作职责,职责的划分是通过是否要与V发生联系来进行的。比如对于C来说,M应该就是一个最终且最有目的性的需求品,C不该关心这个目标数据怎么来的。这使得一般的校验和缓存等操作,应该存在于M层。
- 控制器C,或者叫做调度器,主要用来委托操作和调度,它不应该着眼于业务逻辑或者视图逻辑;而要将业务逻辑和视图逻辑,都还给对应的V层和M层。而视图和业务耦合的视图业务逻辑,可视情况委托给业务工具类(Helper)。对于调度职责来说,要构建一个调度体系,上级调度管理下层调度,下层调度管理下下层调度,整体就是采用分而治之的思想,实现页面级别的模块化。

本文主要来分析C的优化方式,理论难免枯燥,所以本篇就以一个完整的简单demo来聊聊页面的模块化,本文主要说明怎么分模块的问题,很多细节问题没有指出,这些细节问题很多并不是demo中方案带来的,而是项目实践中都会遇到的。

demo的仓库地址

下面是demo项目的大体介绍。

  • 首先是demo的页面原型图,见附图所示。
    页面粗略原型图

  • 需求分析:这个页面是个整体可滚动的视图,单从原型图来看在实际项目中应该至少有一个网络请求接口,甚至4个;顶部UI需要填充多个数据;WebView应该有其对应的配置甚至js交互;纵向list可能还能上拉加载等。滑动冲突的问题不作考虑,因为无论什么布局都要考虑。

  • 方案一,ScrollView嵌套LinearLayout,内部纵向嵌套4种布局,分别对应原型图。这种方案应该是80%以上的童鞋都会采用的方案;但是本文并不推荐采用这种方式。这里先列三条原因:1. 除了List里面的UI,所有的UI都将会出现在ScrollView所在的Controller中,少说也要超过10个ui要find和设置数据吧,容易造成C的臃肿;2. 这种页面肯定有网络请求,假设以比较多的一种方式,4个接口来说,所有的数据请求以及给View设置都出现在C中,也会臃肿;3. 增加和删除布局,会比较麻烦,因为C中代码臃肿耦合较多。

  • 方案二,ScrollView嵌套LinearLayout,内部纵向嵌套4种布局,在C中分别替换成Fragment。这种方案相对方案一来说,有了一些进步,至少一些View的find和数据请求被分散了。但本文仍不建议这样,原因有三:1. Fragment的生命周期真的有点难以把控,尤其是当上述布局出现在二级嵌套的Fragment中时;2. Fragment的替换本身有一定的复杂度;3. 扩展性有不足,若横向List和纵向List交替实现多次,这种替换方式显得被动。

  • 方案三,当然就是本文推荐的方式:RecyclerView作为大C中的唯一的UI存在,那四种布局,分别对应四种item,即对应四种holder。这么做的优势有:1. 大C几乎只处理一个View的find和设置,网络请求也只处理部分接口,C更多的作用是调度;2.RecyclerView的adapter负责分发布局,职责比较清晰;3. 对应的几个Holder都相当于小C,分别处理View的find和set,也能处理自身的网络请求;4. 布局增删,复用都很容易–by CysionLiu。

  • 代码实现,还是上代码比较容易理解,本demo省略了网络部分。

  • 大C的主要代码

public class MainActivity extends AppCompatActivity {    private RecyclerView mGlobalList;    private GlobalAdapter mGlobalAdapter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mGlobalList = (RecyclerView) findViewById(R.id.list_main_acty);        mGlobalList.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));        mGlobalAdapter = new GlobalAdapter(this, Provider.create());        mGlobalList.setAdapter(mGlobalAdapter);    }}
  • adapter的主要代码
public class GlobalAdapter extends RecyclerView.Adapter {    private Activity mActivity;    private Context mContext;    private List<BaseBean> mDataList;    public static final int TOP = 900;    public static final int WEB = 901;    public static final int GALLERY = 902;    public static final int SHOW = 903;    public GlobalAdapter(Activity aActivity, List<BaseBean> aDataList) {        mActivity = aActivity;        mContext = aActivity.getApplicationContext();        mDataList = aDataList;        if (mDataList == null) {            mDataList = new ArrayList<>();        }    }    @Override    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {        switch (viewType) {            case TOP:                return new TopViewHolder(LayoutInflater.from(mContext).inflate(R.layout.holder_top, parent, false));            case WEB:                return new WebHolder(LayoutInflater.from(mContext).inflate(R.layout.holder_web, parent, false));            case GALLERY:                return new GalleryHolder(LayoutInflater.from(mContext).inflate(R.layout.holder_gallery, parent, false));            case SHOW:                return new ShowHolder(LayoutInflater.from(mContext).inflate(R.layout.holder_show, parent, false));            default:                break;        }        return null;    }    @Override    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {        ((BaseViewHolder) holder).bindData(mActivity, mDataList, position);    }    @Override    public int getItemCount() {        return mDataList.size();    }    @Override    public int getItemViewType(int position) {        return mDataList.get(position).getItemType();    }}
  • 重点来了,数据和布局的分发是关键,毕竟这四种布局差别很大,数据也没什么直接关系。但是本系列第一篇最后也提到,利用组合模式的思想,可实现数据的组合。还是上代码好理解。

  • 先定义一个基础数据

public class BaseBean {    private int itemType;    public int getItemType() {        return itemType;    }    public void setItemType(int aItemType) {        itemType = aItemType;    }}
  • 顶部ui的数据比较好解释,权且贴出;
public class TopBean extends BaseBean {    private String name;    private String imgHead;    private int resImgHead;    private int countNum;    private int commentNum;    private String description;.......省略}
  • 重点是子列表型数据的处理,以横向列表数据为例,这里看ContainerBean和GalleryBean;这里的两个bean类就使用了组合思想,让ContainerBean可当做一个元素,也可被用作列表元素,如此带来的额外的好处是adapter也可被内部list复用–by CysionLiu。
public class ContainerBean extends BaseBean {    private List<BaseBean> dataList;    public List<BaseBean> getDataList() {        return dataList;    }    public void setDataList(List<BaseBean> aDataList) {        dataList = aDataList;    }}
public class GalleryBean extends BaseBean {    private String imgUrl;    private int resId;        ....省略}
  • Holder的处理也是关键,为了复用和减少很多代码,一个父Holder是很重要的。里面有三个抽象方法,都有其作用。其中,数据接收或者网络请求便可发生在bindData方法中。
public abstract class BaseViewHolder extends RecyclerView.ViewHolder implements IBindData, View.OnAttachStateChangeListener {    protected Activity mActivity;    public BaseViewHolder(View itemView) {        super(itemView);        itemView.addOnAttachStateChangeListener(this);    }    @Override    public abstract void bindData(Activity aActivity, List<BaseBean> dataList, int position);    @Override    public void onViewAttachedToWindow(View v) {        onAttached();    }    @Override    public void onViewDetachedFromWindow(View v) {        onDetached();    }    public abstract void onAttached();    public abstract void onDetached();}
  • 最后,模拟一下数据,搭建一下这个列表。
public class Provider {    public static List<BaseBean> create() {        List<BaseBean> baseBeanList = new ArrayList<>();        //top        BaseBean top = new TopBean();        top.setItemType(GlobalAdapter.TOP);        baseBeanList.add(top);        //web        WebBean web = new WebBean();        web.setItemType(GlobalAdapter.WEB);        web.setUrl("https://m.baidu.com/");        baseBeanList.add(web);        //ga        List<BaseBean> innerGallery = new ArrayList<>();        innerGallery.add(new GalleryBean());        innerGallery.add(new GalleryBean());           .......        ContainerBean containerBean1 = new ContainerBean();        containerBean1.setDataList(innerGallery);        containerBean1.setItemType(GlobalAdapter.GALLERY);        baseBeanList.add(containerBean1);        //show        List<BaseBean> shows = new ArrayList<>();        shows.add(new ShowBean());        shows.add(new ShowBean());               .....        ContainerBean containerBean2 = new ContainerBean();        containerBean2.setDataList(shows);        containerBean2.setItemType(GlobalAdapter.SHOW);        baseBeanList.add(containerBean2);        return baseBeanList;    }}

以上就是将页面模块的代码实现,整体还是比较简单的,通过这些设置,方案一中唯一的C将会变成5个C,顶级C是MainActivity,它和adapter共同来实现布局的调度和分发;子布局分别对应一个小C-holder,每个holder可实现其自己的逻辑。增加布局也很容易,只需增加一个新type数据,增加一个holder即可;另外,布局复用也很方便,比如横向List和纵向List交替复用,相当于数据中出现了多个ContainnerBean,仍在List和RecyclerView的适配中。基本不用动什么代码。

写在本篇最后,本文主要目的是,通过一步步分析demo示例,揭示MVC中的C如何变得更小和清晰。demo粗糙,但想法不粗,希望能给读者带来启发。在接下来的一篇中,将主要针对M层的优化思想来场实战,以使得笔者的观点更容易理解。

篇幅较长,码字不易。

欢迎交流,共同进步!

1 0
原创粉丝点击