手把手为你封装一个MVP+RxJava+Retrofit2+Dagger2+BaseRecyclerView快速开发框架,两周一个版本不是梦

距离我上次发表文章都有超过半年时间了,年前一直在复习,年后一段时间都在找工作,期间还去了一家公司三天,觉得不合适就溜了,感觉挺对不起那家公司的。最后等了一个多月(期间自己也有一段时间去了复习怎么做网页)才入职一家比较知名的国企,拿到自己想要的薪水,也是对上一年自己学习成果的回报吧,也实现了自己不想再待在外包公司小小的愿望。现在回想起2016刚毕业真的觉得有点苦,白天在外包公司工作量成倍的增长,晚上还坚持看书学习做笔记,最后真正实现了逃亡的目标的时候自己也想放松一段日子,所以在2017年的上半年自己基本处于一种半颓废的状态,直到最近在新公司接到一个搭建新框架的任务才重新投入安卓的怀抱,2017年自己的计划也正式的算是开始了。
好吧,不扯个人经历这种无聊的话题了,回归正题,通过一段时间的归纳和总结网上很多我觉得很好的代码,就自己搭建了一套快速开发的框架,下面我就把自己的愚见和网上收集的资料分享一下。
如果不想听我多啰嗦的话可以在GitHub上直接下载源码下载链接

1.首先,介绍一下BaseRecyclerView:

BaseRecyclerView适配器的使用.png

可以看到,封装之后的Adapter,我们只需要重写convert一个方法就可以实现该有的效果,比原来要重写多个方法代码简洁多了。
我们只用了

viewHolder.setText(R.id.tv_title,item.getTitle());

一句代码就完成了对View的初始化和运用,下面我们看看ViewHolder是怎么写的:

public class BaseViewHolder extends RecyclerView.ViewHolder {

    private SparseArray<View> mViews;
    public View mConvertView;
    public Context mContext;

    public BaseViewHolder(Context context,View itemView) {
        super(itemView);
        mViews = new SparseArray<>();
        mConvertView = itemView;
        mContext = context;
    }

    public View getConvertView()
    {
        return mConvertView;
    }

    public static BaseViewHolder createViewHolder(Context context, View itemView){
        BaseViewHolder holder = new BaseViewHolder(context, itemView);
        return holder;
    }

    public static BaseViewHolder createViewHolder(Context context, ViewGroup parent, int layoutId){
        View itemView = LayoutInflater.from(context).inflate(layoutId, parent,
                false);
        BaseViewHolder holder = new BaseViewHolder(context, itemView);
        return holder;
    }

    public BaseViewHolder setText(@IdRes int viewId, CharSequence value) {
        TextView view = getView(viewId);
        view.setText(value);
        return this;
    }

    public BaseViewHolder setBackgroundColor(@IdRes int viewId, @ColorInt int color) {
        View view = getView(viewId);
        view.setBackgroundColor(color);
        return this;
    }

    public BaseViewHolder setBackgroundRes(@IdRes int viewId, @DrawableRes int backgroundRes) {
        View view = getView(viewId);
        view.setBackgroundResource(backgroundRes);
        return this;
    }

    public BaseViewHolder setTextColor(@IdRes int viewId, @ColorInt int textColor) {
        TextView view = getView(viewId);
        view.setTextColor(textColor);
        return this;
    }

    public BaseViewHolder setImageDrawable(@IdRes int viewId, Drawable drawable) {
        ImageView view = getView(viewId);
        view.setImageDrawable(drawable);
        return this;
    }

    public BaseViewHolder setImageBitmap(@IdRes int viewId, Bitmap bitmap) {
        ImageView view = getView(viewId);
        view.setImageBitmap(bitmap);
        return this;
    }

    public BaseViewHolder setVisible(@IdRes int viewId, boolean visible) {
        View view = getView(viewId);
        view.setVisibility(visible ? View.VISIBLE : View.GONE);
        return this;
    }

    //关于事件
    public BaseViewHolder setOnClickListener(@IdRes int viewId, View.OnClickListener listener) {
        View view = getView(viewId);
        view.setOnClickListener(listener);
        return this;
    }



    public BaseViewHolder setOnTouchListener(@IdRes int viewId,View.OnTouchListener listener)
    {
        View view = getView(viewId);
        view.setOnTouchListener(listener);
        return this;
    }

    public BaseViewHolder setOnLongClickListener(@IdRes int viewId,View.OnLongClickListener listener)
    {
        View view = getView(viewId);
        view.setOnLongClickListener(listener);
        return this;
    }



    public <T extends View> T getView(int viewId){
        View view = mViews.get(viewId);
        if(view == null){
            view = itemView.findViewById(viewId);
            mViews.put(viewId,view);
        }
        return (T) view;
    }
}

关键的三步:

1.用SparseArray来存储各个View;
2.用getView的方法去集体声明View;
3.用系统给出的setText等方法达到你想要的效果。

简单的封装就能省略了开发中初始化ViewHolder的很多重复繁杂的代码~
在BaseAdapter,我也做了一些小小的封装处理,把adapter的onCreateViewHolder(),onBindViewHolder(),getItemCount()三个必要写的方法也减少为只需要重写convert()一个方法。

BaseRecyclerView其他用法.png

可以看到,在adapter我同样封装了增加头部、尾部和子项监听事件,在下拉刷新,上拉加载方面,我用了之前自己写过的一个SwipeRecyclerView融合在一起,有兴趣的朋友可以去看看SwipeRecyclerView源码下载链接
至于怎么实现,我是参考了网上几个对RecyclerView封装的很好的框架进行修改的。以下是参考的链接:
BaseRecyclerViewHelperAdapter
为RecyclerView打造通用Adapter 让RecyclerView更加好用

2.RxJava和Retrofit2的两种网络链接方式:

因为各人都有自己的爱好,所以我听取别人给我的建议,选择去封装了两种不同链接方式。

(1)分参数上传:

分参数上传.png

分参数上传-View代码.png

分参数上传-Presenter代码.png

这种方式是RxJava和Retrofit2在MVP模式中最普遍的用法,它跟以前很多网络框架不一样要先确定参数和JavaBean,所以也有习惯了以前模式的人吐槽这种方式,特别是Presenter的代码量太多,冗余得代码也多,但是这种封装却是最能体现RxJava和Retrofit2特性的封装,以后查询接口可以一目了然地知道链接和所需要的参数。
这种链接方式比较常见,所以我也不做太多的解释。
以下是参考链接:
GeekNews

(2)批量参数上传:
批量参数上传-View层请求代码.png
批量参数上传-Presenter层请求代码.png

很明显,这种封装Presenter层的代码量看上去会减少很多,同时,这样的封装跟以前很多网络框架的模式很像:

(1)用一个HashMap装载所有的请求参数;
(2)url和参数直接在View层显示;
(3)在请求的时候返回是一个callback。

下面我们看看代码:
一开始我还以为Retrofit不能批量参数上传的,只能确定好每个参数,但是后来在网上才发现原来还有这种操作,所以就参照着几个好的框架来实现了一次~

public interface BaseServerApi {

    @POST("{path}")
    Observable<ResponseBody> post(
            @Path(value = "path", encoded = true) String path,
            @QueryMap Map<String, Object> map);

    @GET("{path}")
    Observable<ResponseBody> get(
            @Path(value = "path", encoded = true) String path,
            @QueryMap Map<String, Object> map);
}

Retrofit2的相关配置和Get、Post上传方法

public class RetrofitHelper {

    private static final int CONNECT_TIME_OUT = 20;
    private static final int READ_TIME_OUT = 20;
    private static final int WRITE_TIME_OUT = 20;
    private static final int MAX_CACHE_SIZE = 1024 * 1024 * 50;
    private static final String HTTP_CACHE_DIR = Environment.getExternalStorageDirectory() + "/HTTP_LIBRARY_CACHE";

    private static Retrofit retrofit;
    private static BaseServerApi baseServerApi;

    private static OkHttpClient mOkHttpClient = null;

    @Inject
    //初始化Retrofit
    public RetrofitHelper() {
        initOkHttp();
        retrofit = new Retrofit.Builder()
                .baseUrl(AppURL.BaseURL)
                .client(mOkHttpClient)
                .addConverterFactory(GsonConverterFactory.create()) //自定义的gsonConverter 在里面处理了登录失效处理
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .build();
        baseServerApi = retrofit.create(BaseServerApi.class);

    }

    //get方法
    public <T> T Get(String url, Map<String, Object> maps, BaseSubscriber<T> subscriber){

        return (T) baseServerApi.get(url,maps)
                .compose(RxUtil.<ResponseBody>rxSchedulerHelper())
                .compose(RxUtil.<T>handleResult())
                .subscribe(subscriber);
    }

    //post方法
    public <T> T Post(String url, Map<String, Object> maps, BaseSubscriber<T> subscriber){

        return (T) baseServerApi.post(url,maps)
                .compose(RxUtil.<ResponseBody>rxSchedulerHelper())
                .compose(RxUtil.<T>handleResult())
                .subscribe(subscriber);
    }

    //配置okHttp的数据
    public static void initOkHttp() {
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        if (Logger.DEBUG) {
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
            builder.addInterceptor(loggingInterceptor);
        }

        File cacheFile = new File(HTTP_CACHE_DIR);
        Cache cache = new Cache(cacheFile, MAX_CACHE_SIZE);
        Interceptor cacheInterceptor = new CacheInterceptor();
        //设置缓存
        builder.addNetworkInterceptor(cacheInterceptor);
        builder.addInterceptor(cacheInterceptor);
        builder.cache(cache);
        //设置超时
        builder.connectTimeout(CONNECT_TIME_OUT, TimeUnit.SECONDS);
        builder.readTimeout(READ_TIME_OUT, TimeUnit.SECONDS);
        builder.writeTimeout(WRITE_TIME_OUT, TimeUnit.SECONDS);

        //错误重连
        builder.retryOnConnectionFailure(true);
        mOkHttpClient = builder.build();
    }
}

按照一位以前班里的大神说:
这种方式来说,可读性可能比较差点,但是如果以后你想换一种网络框架,只需要把callback稍微改一下就好了而不需要像第一种那样整个项目都要伤筋动骨,但是这样做却把RxJava的链式结构的特性给抹杀掉了。。。
其实两种链接方式各有各的优点,看你喜欢哪一种,或者按照个人习惯去选择,两种方式我都放在框架里面了。
以下是参考链接:
Android 巧妙封装,基于Retrofit+RxJava网络框架“Leopard”---完整浅析
Novate:Retrofit2.0和RxJava的又一次完美改进加强!

其实,很多时候我们请求数据回来都需要有第一层封装去处理,譬如说超时登录等等,所以接下来我们也来封装一下:
第二种网络链接方式的处理:

第二种网络链接方式的第一层数据封装.png
public static <T> Observable.Transformer<ResponseBody, T> handleResult() {
        return new Observable.Transformer<ResponseBody, T>() {
            @Override
            public Observable<T> call(Observable<ResponseBody> tObservable) {
                return tObservable.flatMap(
                        new Func1<ResponseBody, Observable<T>>() {
                            @Override
                            public Observable<T> call(ResponseBody responseBody) {
                                try {
                                    String jstr = new String(responseBody.bytes());
                                    Type type = new TypeToken<WXHttpResponse>() {
                                    }.getType();
                                    Log.e("responseBody",jstr);
                                    //需要手动解析Json,WXHttpResponse相当于BaseBean,自己可以修改
                                    WXHttpResponse wxHttpResponse = new Gson().fromJson(jstr,type);
                                    //验证成功返回码
                                    if (wxHttpResponse.getCode() == 200) {
                                        Log.e("wxHttpResponse",wxHttpResponse.getNewslist().toString());
                                        return (Observable<T>) Observable.just(wxHttpResponse.getNewslist());
                                    } else {
                                        // 处理被踢出登录情况
                                        return Observable.error(new ReloginException("服务器返回error"));
                                    }
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                return Observable.error(new ReloginException("服务器返回error"));
                            }
                        }
                );
            }
        };
    }
public abstract class BaseSubscriber<T> extends Subscriber<T> {

    private Task<T> resultTask;
    private String msg;

    public BaseSubscriber(){
        Type type = getSuperClassGenricType(getClass(),0);
        this.resultTask = new Task(type);
    }

    @Override
    public void onCompleted() {

    }

    @Override
    public void onError(Throwable throwable) {
        throwable.printStackTrace();

        if(throwable instanceof ReloginException){
            // 踢出登录
        }else if (throwable instanceof UnknownHostException) {
            msg = "没有网络...";
        } else if (throwable instanceof SocketTimeoutException) {
            // 超时
            msg = "超时...";
        }else{
            msg = "请求失败,请稍后重试...";
        }
        onErrorT(msg);
    }

    @Override
    public void onNext(T t) {
        if(t != null) {
            Gson gson = new Gson();
            String result = gson.toJson(t);
            resultTask.setJson(result);
            onNextT(resultTask.getResult());
        }
    }

    public abstract void onNextT(T t);

    public abstract void onErrorT(String msg);

    // 获取超类类型
    private Type getSuperclassTypeParameter(Class<?> clazz, int index)
    {
        Type genType = clazz.getGenericSuperclass();

        if (!(genType instanceof ParameterizedType)) {
            return Object.class;
        }
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

        if (index >= params.length || index < 0) {
            return Object.class;
        }
        if (!(params[index] instanceof Class)) {
            return Object.class;
        }

        return (Class) params[index];
    }
  
    public static Type getSuperClassGenricType(final Class clazz, final int index) {

        //返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type。
        Type genType = clazz.getGenericSuperclass();

        if (!(genType instanceof ParameterizedType)) {
            return Object.class;
        }
        //返回表示此类型实际类型参数的 Type 对象的数组。
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

        if (index >= params.length || index < 0) {
            return Object.class;
        }
        if (!(params[index] instanceof Type)) {
            return Object.class;
        }

        return params[index];
    }

}

第一种连接方式跟第二种在这里是有区别的,第二种批量参数上传对比起第一种相对比较复杂,是因为这里没有用到retrofit2自动解析json的功能,要自己手动去解析,所以这里就只列出第二种连接方式给大家看看。
以下是参考链接:
RxJava简洁封装之道

3.Dagger2

对于Dagger2,我只能说自己的理解能力有限,一直都是靠文章和大神的帮助才慢慢理解,所以这里就不发表自己的愚见了,直接上几篇写的比较好的文章给大家参考吧~
Android:dagger2让你爱不释手-基础依赖注入框架篇
Dagger2从入门到放弃再到恍然大悟
Dagger2 入门,以初学者角度

4.相关工具类

相关工具类.png

应有尽有的相关工具类满足你的一切要求~
由衷感谢我的好友兼我的大神jaminchanks对我安卓学习提供的巨大支持,我有很多安卓资料都是从他那里学习的。他比较低调,但是他知道的比我多太多了,这是他的简书账号
接下来可能我还会更新图片框架的封装和其他的一些改善。
如果有哪位朋友喜欢这个框架的,可以下载源码,一起交流一下哪里可以改进的,共同进步~
源码下载链接

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容