搭建自己的 Android MVP 快速开发框架

Android 开发进入「死丢丢」的时代后,引用三方库在 Gradle 的支持下变得十分轻松。各种高手写的开源框架,极大程度降低了新手入行(坑)的门槛,「一周开发一款 App 并上线」也不再遥不可及。

关于快速开发,笔者本人的意见是不一定什么功能都自己写,但框架最好是自己搭。虽然网上有很多非常成熟好用的完整框架,但直接「拿来主义」的话可能有 2 点不妥之处——

框架提供的功能你未必都用得到。比如你只写一个纯阅读类型的应用(不带大数据收藏功能),那么你就用不到本地数据库,这样完整框架里有关数据库的内容,就给白白浪费了。

高手也有疏忽时,即便技术大牛,也不敢保证自己写的代码没有任何 bug,在任何使用场景都健壮坚挺。如果某天突然发现完整框架有什么 bug 或者局限,自己又没能力解决,到头来只能重构大块内容甚至整个项目,这代价就非常大了。

综上,笔者更倾向新手「站在巨人的肩膀上搭积木」,用高手写的不同功能库,自己动手搭属于自己的快速开发框架。而且在搭框架的过程中,你能不知不觉中学到很多进阶知识,对自己的成长也很有利。

限于水平和篇幅,笔者只用老牌轮子Volley做例子,搭一个仅涉及网络请求和图片加载的MVP框架。当下最流行的原生框架应属RxJava + Retrofit + OkHttp + Dagger,如果你想了解得更多,推荐下面几篇文章——

给 Android 开发者的 RxJava 详解

RxJava 与 Retrofit 结合的最佳实践

Dagger 2 从入门到放弃再到恍然大悟

MVP + Dagger2 + Retrofit 实现更清晰的架构

当然,这些库本质和 Volley 一样,都是去实现具体功能的轮子,而MVP 的架构是不变的,所以下文的内容对它们同样适用。

动手开始

打开 Android Studio,新建一个项目MvpFrameTest。再在项目根目录右键 new 一个 Module,选择第二项 Android Library ,取名MVP

这时你会看到你的项目下面多了一个叫 mvp 的Module(和 app 一样是加粗显示的),不过角标是一个书架而非手机。这代表此模块是一个依赖库,而非独立运行的应用,我们今天主要的代码都写在它里面。

搭建自己的 Android MVP 快速开发框架

1.png

导入依赖

点开mvp 下面的 build.gradle文件(别错搞成 app 下面的了哦),在dependency节点下面导入我们要用的轮子——

compile'com.android.support:design:25.3.1'compile'com.android.volley:volley:1.0.0'compile'com.google.code.gson:gson:2.7'

这里我希望内容尽量简洁一点,因此只导入设计适配(包含RecyclerView以及各种 Material Design 控件)、Volley(包含网络请求图片加载)和Gson(包含Json 解析)三个库。语句后面的版本号仅供参考,因为当你看到这篇文章时,建议使用的版本号可能又变了。

下面点开app 下面的 build.gradle文件,同样在dependency节点下面添加依赖——

compile project(path:':mvp')

点击提示行里面的Sync Now,一会儿任务完成,从此以后你在 mvp 里面依赖的库(包括自己写的各种类),app 就都可以用了。

建议把整体性的功能诸如网络请求、图片加载等写在 mvp 里,具体性的实现诸如 UI 配色、访问地址写在 app 里,这样你的框架使用起来才更加灵活。

如果你对 Gradle 还不了解,推荐一篇文——

给 Android 初学者的 Gradle 知识普及

MVP

下面开始写自己的东西了,由于我们想要的是 MVP 设计模式,首先应该完成通用的 M、V 和 P。对 MVP 不了解的推荐一篇文——

浅谈 MVP in Android

找到 mvp 下面的com.example.mvp包,如图所示

搭建自己的 Android MVP 快速开发框架

2.png

在里面新建两个接口(Interface),分别取名BaseViewBaseModel

publicinterfaceBaseView{voidshowLoading();voidhideLoading();voidshowError();}

BaseView 里面我们定义了三个抽象方法,分别用于显示加载、隐藏加载和显示加载失败的内容。这些方法最终会交给你的视图(也就是 Activity 或者 Fragment)去实现。

publicinterfaceBaseModel{}

BaseModel 里面目前可以什么都不写。如果你参与一个团队开发,接口和数据有比较统一的格式,那可以在此做一些规范工作。

OK,M 和 V 都有了,再新建一个抽象类,取名BasePresenter

publicabstractclassBasePresenter{protectedM mModel;protectedWeakReference mViewRef;protectedvoidonAttach(M model, V view){        mModel = model;        mViewRef =newWeakReference<>(view);    }protectedVgetView(){returnisViewAttached() ? mViewRef.get() :null;    }protectedbooleanisViewAttached(){returnnull!= mViewRef &&null!= mViewRef.get();    }protectedvoidonDetach(){if(null!= mViewRef) {            mViewRef.clear();            mViewRef =null;        }    }}

首先声明了两个泛型 M 和 V,M 对应要处理的 Model,V 则对应负责展示的View。由于 V 一般比较大,这里采用了弱引用的写法,避免内存泄漏。

isViewAttached()用于检测 V 是否已关联 P,为真则让getView()返回对应的 V,否则返回 null。另外两个方法负责 V 和 P 的关联与解关联,很简单。

等等,你这不都是具体方法么,为啥还要弄成抽象类?待会自见分晓。

应用入口

新建一个MyApp类,继承 Application,用于获取应用全局的上下文。

publicclassMyAppextendsApplication{privatestaticMyApp instance;publicstaticMyAppgetInstance(){returninstance;    }@OverridepublicvoidonCreate(){super.onCreate();        instance =this;    }}

这个类是你整个应用的入口,一些你希望在应用一跑起来就立即完成的工作(比如初始化一些三方库,包括 SDK),可以写入它的 onCreate() 方法。

切记不要用 instance = new MyApp() 一类的赋值去获取实例,这样你得到的只是一个普通的 Java 类,不会具备任何 Application 的功能!

完成以后别忘了去app 模块的 AndroidManifest.xml,在 Application 节点下添加一行——

android:name="com.example.mvp.MyApp"

网络请求

前面已经说过,网络请求这类整体功能的封装应写入框架,这样应用调用起来就很方便。这里用的请求库是Volley,不够了解的请看这篇文——

Android Volley 完全解析

这是一个系列文,共四篇,新手建议看完前三篇。

新建一个RequestManager类,用于管理网络请求。

publicclassRequestManager{privateRequestQueuequeue;privatestaticvolatileRequestManager instance;privateRequestManager(){queue= Volley.newRequestQueue(MyApp.getInstance());    }publicstaticRequestManagergetInstance(){if(instance == null) {            synchronized (RequestManager.class) {if(instance == null) {                    instance =newRequestManager();                }            }        }returninstance;    }publicRequestQueuegetRequestQueue(){returnqueue;    }}

这里定义了一个请求队列的对象,在构造器里实例化,对象和构造器均设为私有,只暴露两个 get 方法。因为请求队列一个便够(多了很浪费资源哦),这里采用了双重校验锁单例模式的写法。不了解单例模式请看——

Android 设计模式之单例模式

下面定制我们的专属网络请求,网上大多数 API 返回数据都是 Json 对象,可以通过 Gson 很轻松的把它们转换成 Java 对象。新建一个MyRequest类,继承 Volley 里面的 Request 类。

publicclassMyRequestextendsRequest{privateGson mGSon;privateClass mClass;privateResponse.Listener mListener;publicMyRequest(String url, Class clazz,

                    Response.Listener listener, Response.ErrorListener errorListener){this(Request.Method.GET, url, clazz, listener, errorListener);    }publicMyRequest(intmethod, String url, Class clazz,                    Response.Listener listener, Response.ErrorListener errorListener){super(method, url, errorListener);        mGSon =newGson();        mClass = clazz;        mListener = listener;    }@OverrideprotectedResponseparseNetworkResponse(NetworkResponse response){try{            String json =newString(response.data,                    HttpHeaderParser.parseCharset(response.headers));returnResponse.success(mGSon.fromJson(json, mClass),                    HttpHeaderParser.parseCacheHeaders(response));        }catch(UnsupportedEncodingException e) {returnResponse.error(newParseError(e));        }    }@OverrideprotectedvoiddeliverResponse(T response){        mListener.onResponse(response);    }}

代码看着不少,其实很好理解。首先我们想要的 Java 对象不确定,所以用一个泛型 T 去描述,并指定为与 Request 类的泛型相同。

构造器是重写自父类,里面实例化了马上要讲到的 Gson,然后重载了一个不带请求类型的,此时默认请求类型为 GET。

接下来就是重写 Request 类的parseNetworkResponse()和 ** deliverResponse()** 方法,前者用于解析请求到的响应(也就是返回数据),后者用于将响应传递给回调接口mListener。解析时我们采用了Gson,它会强制我们处理UnsupportedEncodingException,最终返回的便是我们想要的 Java 对象。对 Gson 不了解请看——

你真的会用 Gson 吗?Gson 使用指南

这是一个系列文,共四篇,新手可以只看第一篇。

现在去处理响应,首先新建一个接口MyListener——

publicinterfaceMyListener{voidonSuccess(T result);voidonError(String errorMsg);    }

这是一个回调,成功时携带泛型描述的 Java 对象,失败时则携带错误信息。

然后补充前面的 RequestManager,添加发送 GET 和 POST 请求的封装。

publicvoidsendGet(String url, Class clazz,finalMyListener listener){        MyRequest request =newMyRequest<>(url, clazz,newResponse.Listener() {@OverridepublicvoidonResponse(T response){                listener.onSuccess(response);            }        },newResponse.ErrorListener() {@OverridepublicvoidonErrorResponse(VolleyError error){                listener.onError(error.getMessage());            }        });        addToRequestQueue(request);    }publicvoidsendPost(String url, Class clazz,finalHashMap map,finalMyListener listener){        MyRequest request =newMyRequest(Request.Method.POST, url, clazz,newResponse.Listener() {@OverridepublicvoidonResponse(T response){                listener.onSuccess(response);            }        },newResponse.ErrorListener() {@OverridepublicvoidonErrorResponse(VolleyError error){                listener.onError(error.getMessage());            }        }) {@OverrideprotectedMapgetParams()throwsAuthFailureError{returnmap;            }        };        addToRequestQueue(request);    }publicvoidaddToRequestQueue(Request req){        getRequestQueue().add(req);    }

网络请求搞定!这里很明显看出 Volley 的局限,就是不支持 POST 大数据,因此不适合上传文件(下载文件倒是可以通过 DownloadManager 实现)。如果你的项目有上传文件需求,应该转战 Retrofit 或 OkHttp。

图片加载

这里只用 Volley 自带的 ImageLoader 模块实现图片加载。该模块性能不错,但功能不如 Glide 一类的专业图片加载框架丰富,大家可根据需求自行选择合适的轮子。新手推荐看下面这篇文——

Android开源项目推荐之「图片加载到底哪家强」

新建一个ImageUtil类,用于封装图片加载。

publicclassImageUtil{publicstaticvoidloadImage(String url, ImageView iv,intplaceHolder,interrorHolder){        ImageLoader loader =newImageLoader(                RequestManager.getInstance().getRequestQueue(),newBitmapCache());if(ivinstanceofNetworkImageView) {            ((NetworkImageView) iv).setDefaultImageResId(placeHolder);            ((NetworkImageView) iv).setErrorImageResId(errorHolder);            ((NetworkImageView) iv).setImageUrl(url, loader);        }else{            ImageLoader.ImageListener listener = ImageLoader.getImageListener(iv,                    placeHolder, errorHolder);            loader.get(url, listener);        }    }privatestaticclassBitmapCacheimplementsImageLoader.ImageCache{privateLruCache cache;privatefinalintmaxSize =10*1024*1024;//缓存大小设为10MBitmapCache() {            cache =newLruCache(maxSize) {@OverrideprotectedintsizeOf(String key, Bitmap value){returnvalue.getByteCount() /1024;                }            };        }@OverridepublicBitmapgetBitmap(String url){returncache.get(url);        }@OverridepublicvoidputBitmap(String url, Bitmap bitmap){            cache.put(url, bitmap);        }    }}

首先写了一个内部类BitmapCache(因为工具类对外方法是静态的,所以它也应是静态),实现 Volley 的 ImageCache 接口并重写方法。这里采用了 LruCache 实现图片缓存,不了解请看这篇文——

Android高效加载大图、多图解决方案,有效避免程序OOM

然后暴露一个loadImage()方法给外部调用。Volley 带有一个 继承自 ImageView 的控件NetworkImageView,并有一套专属的加载流程,因此在 loadImage() 方法里,针对它和原生 ImageView 做了区分。

OK,图片加载也搞定了。回首一看我们已写了不少类和接口,整理一下吧,如下图示。这已经是一个还算像样的 MVP 快速开发框架了。

搭建自己的 Android MVP 快速开发框架

3.png

补充润色

继续添加轮子。我们都知道 MVP 的优点,但它也是有不少坑的——

类爆炸,这也是 MVP 最受诟病之处。严格的 MVP 写法下,每写 1 个页面(不算适配器和实体),要为之创建 8 个类。

P 应当具备和 V 相似的生命周期,但在众多 V 里一个个调用 onAttach() 和 onDetach() 一个个关联解关联,显然是重复劳动。

有些 V 的展现内容是共通的,比如进度条、空白页。

另外实际开发中我们还有一些需求,简单列举 2 个——

View 加载控件和数据的逻辑有时会很多,混杂一起阅读相当不方便。

应用要求单击返回键只弹出提示警告,双击才是回到桌面。

现在我们就来解决它们。

首先在 util 目录下新建两个类,分别取名ToastUtilReflectUtil

publicclassToastUtil{privatestaticToast toast;publicstaticvoidshowToast(String text){if(toast ==null) {            toast = Toast.makeText(MyApp.getInstance(), text, Toast.LENGTH_SHORT);        }else{            toast.setText(text);        }        toast.show();    }}

该类用于显示一段土司(原生的接口有不妥之处,连续点击会连续土司)。

publicclassReflectUtil{publicstaticTgetT(Object o,inti){try{return((Class) ((ParameterizedType)                    (o.getClass().getGenericSuperclass())).getActualTypeArguments()[i]).newInstance();        }catch(Exception e) {            e.printStackTrace();        }returnnull;    }}

该类则用于反射获取指定泛型。

然后在 base 目录下新建两个抽象类BaseActivityBaseMvpActivity,前者继承 AppCompatActivity,并实现我们写的 BaseView;后者继承前者。

publicabstractclassBaseActivityextendsAppCompatActivityimplementsBaseView{@OverrideprotectedvoidonCreate(@Nullable Bundle savedInstanceState){super.onCreate(savedInstanceState);        setContentView(getLayoutId());        initView();    }protectedabstractintgetLayoutId();protectedabstractvoidinitView();@OverridepublicvoidshowLoading(){    }@OverridepublicvoidhideLoading(){    }@OverridepublicvoidshowError(){    }@OverridepublicbooleanonKeyDown(intkeyCode, KeyEvent event){returncheckBackAction() ||super.onKeyDown(keyCode, event);    }//双击退出相关privatebooleanmFlag =false;privatelongmTimeout = -1;privatebooleancheckBackAction(){longtime =3000L;//判定时间设为3秒booleanflag = mFlag;        mFlag =true;booleantimeout = (mTimeout == -1|| (System.currentTimeMillis() - mTimeout) > time);if(mFlag && (mFlag != flag || timeout)) {            mTimeout = System.currentTimeMillis();            ToastUtil.showToast("再点击一次回到桌面");returntrue;        }return!mFlag;    }}

有时我们的活动只是一个静态的容器(比如欢迎页),这时其实是没必要使用 MVP 的。所以把包括 UI 的逻辑(双击退出)封装在此。BaseView 里面的方法也在此重写,简明起见,就不具体实现了。

另外为了提升可读性,BaseActivity 添加了两个抽象方法getLayoutId()initView()。子类在重写时,将前者的返回值改为布局 ID,在后者中进行初始化(findViewById、setOnClickListener)即可。如果子类不在 onCreate() 方法里干其它事,重写 onCreate() 一步也可以省略。

皮埃斯:如果你用了 ButterKnife、Dagger 等依赖注入框架,初始化和解绑(去 onDestory() 方法)工作同样可以在这个 BaseActivity 里完成。

有意思的是如果你在子类里用了 Android Studio 一款关于 ButterKnife 的助手插件(人气很高的说),它依然会很「认真负责」的帮你重写 onCreate() 和 onDestory()…… 只有自己动手咔嚓掉了。

publicabstractclassBaseMvpActivityextendsBaseActivity{protectedT mPresenter;protectedM mModel;@OverrideprotectedvoidonCreate(@Nullable Bundle savedInstanceState){super.onCreate(savedInstanceState);        mPresenter = ReflectUtil.getT(this,0);        mModel = ReflectUtil.getT(this,1);        mPresenter.onAttach(mModel,this);    }@OverrideprotectedvoidonStart(){super.onStart();        loadData();    }protectedabstractvoidloadData();@OverrideprotectedvoidonDestroy(){super.onDestroy();        mPresenter.onDetach();    }}

遇到动态的,有数据请求和处理的页面,再让 MVP 出马。这个 BaseMvpActivity 继承了 BaseActivity,因此包含了里面全部功能,同时又添加了一个抽象方法loadData(),有关数据交互的方法写在里面即可。

举一反三,如果要让碎片也能选择性使用 MVP,你应该能写出对应的 BaseFragment 和 BaseMvpFragment 来了吧?

最后在 base 下创建接口MvpListener,用于数据从 M 到 V 的层间传递。

publicinterfaceMvpListener{voidonSuccess(T result);voidonError(String errorMsg);}

好了,属于你的简易 MVP 快速开发框架已经搭建完成,撒花庆祝一下吧。

搭建自己的 Android MVP 快速开发框架

4.png

开车上路

现在就在 app 模块中写个「知乎日报」测试测试,顺便也学习一下 MVP 杜绝类爆炸的使用姿势。简明起见,只用一个 RecyclerView 请求今天的内容(图片 + 标题),不再涉及详情。

首先创建知乎日报的实体类DailyBean。推荐用Postman做请求,然后用 Android Studio 的插件Gson Format自动生成。

publicclassDailyBean{privateString date;privateList stories;publicStringgetDate(){returndate;    }publicvoidsetDate(String date){this.date = date;    }publicListgetStories(){returnstories;    }publicvoidsetStories(List stories){this.stories = stories;    }publicstaticclassStoriesBean{privateinttype;privateintid;privateString ga_prefix;privateString title;privatebooleanmultipic;privateList images;publicintgetType(){returntype;        }publicvoidsetType(inttype){this.type = type;        }publicintgetId(){returnid;        }publicvoidsetId(intid){this.id = id;        }publicStringgetGa_prefix(){returnga_prefix;        }publicvoidsetGa_prefix(String ga_prefix){this.ga_prefix = ga_prefix;        }publicStringgetTitle(){returntitle;        }publicvoidsetTitle(String title){this.title = title;        }publicbooleanisMultipic(){returnmultipic;        }publicvoidsetMultipic(booleanmultipic){this.multipic = multipic;        }publicListgetImages(){returnimages;        }publicvoidsetImages(List images){this.images = images;        }    }}

然后创建一个契约接口DailyContract,这是 Google 推荐的类爆炸解决方案(不过笔者此处并没严格按照官方要求去执行)——

publicinterfaceDailyContract{interfaceDailyModelextendsBaseModel{voidloadDaily(String url, MvpListener> listener);    }interfaceDailyViewextendsBaseView{voidsetData(List beanList);    }abstractclassDailyPresenterextendsBasePresenter{protectedabstractvoidloadData(String url);    }}

接口里同时承载了 Daily 这个模块的 M,V 和 P(现在明白为何一开始要把 BasePresenter 弄成抽象类了吧),并且定义了方法规则。

下面开始具体实现这三层,首先是 P 层,创建一个DailyPresenterImpl类,让它继承契约里面的 DailyPresenter。

publicclassDailyPresenterImplextendsDailyContract.DailyPresenter{@OverridepublicvoidloadData(String url){finalDailyContract.DailyView mView = getView();if(mView ==null) {return;        }        mView.showLoading();        mModel.loadDaily(url,newMvpListener>() {@OverridepublicvoidonSuccess(List result){                mView.hideLoading();                mView.setData(result);            }@OverridepublicvoidonError(String errorMsg){                mView.hideLoading();                mView.showError();            }        });    }}

逻辑很简单,首先拿到契约里 DailyView 的实例 mView,做非空判断,然后调用 showLoading() 方法显示加载进度条。

此后调用 mModel(也就是契约里 DailyModel 的实例)的 loadDaily() 方法,出结果后告知 mView,首先关闭进度条。成功则执行 setData() 展示数据,失败则执行 showError() 展示错误信息。

创建DailyModelImpl类,继承契约里的 DailyModel。

publicclassDailyModelImplimplementsDailyContract.DailyModel{@OverridepublicvoidloadDaily(String url,finalMvpListener> listener){        RequestManager.getInstance().sendGet(url, DailyBean.class,newMyListener() {@OverridepublicvoidonSuccess(DailyBean result){                listener.onSuccess(result.getStories());            }@OverridepublicvoidonError(String errorMsg){                listener.onError(errorMsg);            }        });    }}

这里具体实现 loadDaily() 方法去请求数据,具体途径当然是之前我们封装的网络请求类。成功则执行 MvpListener 的成功回调,失败则执行失败回调。

创建我们用于展示的条目布局文件item_daily

这里我没添加分割线,其实也不推荐直接在 item 里加分割线。

这里插播 2 个小知识——

在层级相同时,FrameLayout 的性能略高于 LinearLayout,LinearLayout 又略高于RelativeLayout。对应的百分比布局同理。

约束布局能保证布局层级始终为 1,如果你的 item 很复杂,有必要考虑一下它。如果你不习惯拖拖拽拽,可以先写 XML 再转换。

创建知乎日报的适配器DailyAdapter。这里我用了 RecyclerView,因为它的依赖已经包含在了 mvp 里,app 里就不用再重复声明了。

publicclassDailyAdapterextendsRecyclerView.Adapter{privateContext context;privateList beanList;publicDailyAdapter(Context context){this.context = context;        beanList =newArrayList<>();    }publicvoidsetBeanList(List list){this.beanList.addAll(list);        notifyDataSetChanged();    }@OverridepublicDailyHolderonCreateViewHolder(ViewGroup parent,intviewType){returnnewDailyHolder(LayoutInflater.from(context)                .inflate(R.layout.item_daily, parent,false));    }@OverridepublicvoidonBindViewHolder(DailyHolder holder,intposition){        DailyBean.StoriesBean bean = beanList.get(position);        holder.tv.setText(bean.getTitle());        ImageUtil.loadImage(bean.getImages().get(0), holder.iv,                R.mipmap.ic_launcher_round, R.mipmap.ic_launcher_round);    }@OverridepublicintgetItemCount(){returnbeanList.size();    }staticclassDailyHolderextendsRecyclerView.ViewHolder{        TextView tv;        NetworkImageView iv;        DailyHolder(View itemView) {super(itemView);            tv = (TextView) itemView.findViewById(R.id.item_daily_tv);            iv = (NetworkImageView) itemView.findViewById(R.id.item_daily_iv);        }    }}

简单起见我们只加载当天的全部内容。onBindViewHolder() 方法里面用到了之前封装的图片工具,占位图就简单用小机器人代替了。

创建主界面的布局文件activity_main

创建一个日期工具类DateUtil,封装日期格式化流程。

聪明如你,应该知道这个类是放 app 更好,还是放 mvp 更好吧?

publicclassDateUtil{privatestaticfinalLocale LOCALE = Locale.CHINA;publicstaticStringformat(Date date, String s){returnnewSimpleDateFormat(s, LOCALE).format(date);    }}

养成好习惯,创建一个类Api,统一管理访问接口。

publicclassApi{publicstaticfinalString DAILY_HISTORY ="http://news.at.zhihu.com/api/4/news/before/";}

最后写展示用的类MainActivity,也就是 MVP 的 V 层。继承 BaseMvpActivity 并实现契约里的 DailyView。

publicclassMainActivityextendsBaseMvpActivityimplementsDailyContract.DailyView{privateDailyAdapter adapter;@OverrideprotectedintgetLayoutId(){returnR.layout.activity_main;    }@OverrideprotectedvoidinitView(){        adapter =newDailyAdapter(this);        RecyclerView rcv = (RecyclerView) findViewById(R.id.ac_main_rcv);        rcv.setLayoutManager(newLinearLayoutManager(this));        rcv.setHasFixedSize(true);        rcv.setAdapter(adapter);    }@OverrideprotectedvoidloadData(){        mPresenter.loadData(Api.DAILY_HISTORY                + DateUtil.format(newDate(),"yyyyMMdd"));    }@OverridepublicvoidsetData(List beanList){        adapter.setBeanList(beanList);    }}

由于无须在活动创建时做其它事,onCreate() 方法可以不重写了。其它 4 个重写方法依次负责布局文件,初始化控件,请求和展示数据,一目了然。

最后别忘了在AndroidManifest里添加网络访问权限——

OK,可以跑应用了~~

搭建自己的 Android MVP 快速开发框架

1.gif

实际效果比 gif 更好,Volley 做纯阅读应用还是比较给力的。

再看一看我们搭好框架后真正写的代码(笔者做了归类整理)——

搭建自己的 Android MVP 快速开发框架

5.png

除去适配器和实体类,一个页面我们只写了 4 个类,有效解决了类爆炸;如果是类似欢迎页那样不涉及交互的,那直接继承 BaseActivity 即可,不再用 MVP 模式写了,这样一个页面只须写 1 个类。

本文结束,欢迎指教 and  拍砖~~

作者:彼岸sakura

链接:https://www.jianshu.com/p/965e67222454

來源:简书

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。