简介

最近公司新创立一个项目,准备开始前期工作,搭建框架,正在想着要怎么搭建更好的框架,以便轻松应付后续需求。想着最近比较流行的RxJava+Retrofit+MVP框架,自己也准备写一个通用的前端项目框架,撸起袖子准备开干。

备注:该项目会一直维护在github上,去往github看。

RxJava

RxJava 在 GitHub 主页上的自我介绍是 "a library for composing asynchronous and event-based programs using observable sequences for the Java VM"(一个在 Java VM 上使用可观测的序列来组成异步的、基于事件的程序的库)。这就是 RxJava ,概括得非常精准。当然这是比较官网的解释,也是一头雾水,以我的理解,其实就是观察者模式,观察者与被观察者,类似Android里面的button点击事件。用来实现异步操作,Android里面有很多方法实现异步操作,RxJava的优势就是很简洁,代码看着很舒服,像我们这种有代码洁癖的人来说简直就是福音,更重要的是随着项目的开展,产品的需求迭代,依然能保持简洁。当然网上有很多RxJava的博文,一些大神讲解的也是非常的细,比我讲的那是好多了,我就不一一说了,推荐几篇文章。 

RxJava:http://gank.io/post/560e15be2dca930e00da1083#toc_1。

Retrofit

retrofit是一款针对Android网络请求的开源框架,它与okhttp一样出自Square公司。Rotrofit2.0的网络框架全部交给了okhttp来实现,Android N之后Apache的httpclient已经被Google从SDK中移除,Okhttp则成功上位。Retrofit的网络请求实现风格与URLconnection和httpClient有较大的差别。创建请求的时候需要先创建基于注解的服务接口(不了解的可以先了解一下注解),进行网络请求的时候再通过Retrofit.creat()方法来创建请求。以我的理解其实就是对Okhttp进行了一层的封装,而且retrofit可以很好的搭配RxJava使用,所以说retrofit和RxJava更配哦。。
Retrofit:https://blog.csdn.net/carson_ho/article/details/73732076


MVP

MVP模式是MVC模式在Android上的一种变体,要介绍MVP就得先介绍MVC。在MVC模式中,Activity应该是属于View这一层。而实质上,它既承担了View,同时也包含一些Controller的东西在里面。这对于开发与维护来说不太友好,耦合度大高了。把Activity的View和Controller抽离出来就变成了View和Presenter,这就是MVP模式。以我的理解其实MVP模式跟MVC差不多,MVP分离的更细,有利于扩展,特别是项目需求不停的更改时,就能理解到MVP是多么的好用,更重要的代码看上去也非常的简洁。

MVP:https://blog.csdn.net/briblue/article/details/52839242


项目搭建

介绍完一堆理论,最重要的就是撸起袖子写代码,这才是关键,很多人很喜欢看技术博文,但是自己很少动手自己写,很快就会遗忘,很多问题只有自己去动手写才能发现。话不多说,开撸。

引入我们需要的JAR包

在gradle引入jar包

  //Retrofit    compile 'com.squareup.retrofit2:retrofit:2.4.0'    compile 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'    compile 'com.squareup.retrofit2:converter-gson:2.4.0'    //RxJava    compile 'io.reactivex.rxjava2:rxjava:2.1.12'    //Rxlife用于管理RxJava的订阅和解除    compile 'com.trello.rxlifecycle2:rxlifecycle:2.2.1'    compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.2.1'    //RxAndroid    compile 'io.reactivex.rxjava2:rxandroid:2.0.2'
public interface ApiService {    /**     * 广告banner     * @return     */    @GET("/ace-app/bannerInfo/{id}")    Observable>> getBannerInfo(@Path("id") String id);    /**     * 测试数据接口     * @param q     * @param tag     * @param start     * @param end     * @return     */    @GET("book/search")    Observable getBooks(@Query("q") String q,                                   @Query("tag") String tag,                                   @Query("start") String start,                                   @Query("end") String end);}

因为结合RxJava使用,需要返回被观察者Observable对象。

创建Retrofit辅助类,用于网络请求

/** * Retrofit 辅助类 * @author Veer * @email 276412667@qq.com * @date 18/7/2 */public class RetrofitHelper {    private static String TGA = "RetrofitHelper";    private  long CONNECT_TIMEOUT = 60L;    private  long READ_TIMEOUT = 30L;    private  long WRITE_TIMEOUT = 30L;    private static RetrofitHelper mInstance = null;    private Retrofit mRetrofit = null;    public static RetrofitHelper getInstance(){        synchronized (RetrofitHelper.class){            if (mInstance == null){                mInstance = new RetrofitHelper();            }        }        return mInstance;    }    private RetrofitHelper(){        init();    }    private void init() {        resetApp();    }    private void resetApp() {        mRetrofit = new Retrofit.Builder()                .baseUrl(Contacts.DEV_BASE_URL)                .client(getOkHttpClient())                .addConverterFactory(GsonConverterFactory.create())                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())                .build();    }    /**     * 获取OkHttpClient实例     *     * @return     */    private  OkHttpClient getOkHttpClient() {        OkHttpClient   okHttpClient = new OkHttpClient.Builder()                .retryOnConnectionFailure(true)                .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)                .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)                .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)                .addInterceptor(new RqInterceptor())                .addInterceptor(new LogInterceptor())                .build();        return okHttpClient;    }    /**     * 请求拦截器     */    private  class RqInterceptor implements Interceptor {        @Override        public Response intercept(Interceptor.Chain chain) throws IOException {            Request request  =  chain.request()                    .newBuilder()                    .addHeader("X-APP-TYPE","android")                    .build();            Response response = chain.proceed(request);            return response;        }    }    /**     * 日志拦截器     */    private class LogInterceptor implements Interceptor {        @Override        public Response intercept(Interceptor.Chain chain) throws IOException {            Request request  =  chain.request();            String url = request.url().toString();            String params = requestBodyToString(request.body());            Response response = chain.proceed(request);            String responseString = JsonHandleUtils.jsonHandle(response.body().string());            String time = DateUtils.getNowDateFormat(DateUtils.DATE_FORMAT_2);            String log = "\n\n*****请求时间*****:\n" + time+"\n*******路径*******:\n" + url + "\n*******参数*******:\n" + params +  "\n*******报文*******:\n"  + responseString+"\n \n";            Log.d(TGA,log);            return chain.proceed(request);        }    }    private  String requestBodyToString(final RequestBody request) {        try {            final RequestBody copy = request;            final Buffer buffer = new Buffer();            if (copy != null){                copy.writeTo(buffer);            }            else{                return "";            }            return buffer.readUtf8();        } catch (final IOException e) {            return "did not work";        }    }    public ApiService getServer(){        return mRetrofit.create(ApiService.class);    }}

这里面代码比较多,了解Okhttp的同学应该看得懂,Retrofit也是基于Okhttp的,我使用了单例进行了一层的处理,方便的我们的接口请求,这里比较重要的是拦截器,我们在配置Okhttp的时候可以配置一些拦截器,做一些请求头和日志等处理。看不懂的同学也不用担心,我会把项目源码贴出来,GitHub上也会一直维护。

配置完了Retrofit,现在最重要的就是搭建MVP,搭建MVP前期工作一定要做好,方便我们后续的扩展,前期可能会写很多接口,如果想后续不被产品的需求磨得脑壳疼的话,这些都是免不了的。

/** * 基础配置约定 * @author Veer * @email 276412667@qq.com * @date 18/7/2 */public interface BaseContract {    interface BasePresenter{        /**         * view挂载         *         * @param view         */        void attachView(T view);        /**         * View卸载         */        void detachView();    }    interface BaseView{        /**         * 显示进度中         */        void showLoading();        /**         * 隐藏进度         */        void hideLoading();        /**         * 显示请求成功         *         * @param message         */        void showSuccess(String message);        /**         * 失败重试         *         * @param message         */        void showFailed(String message);        /**         * 显示当前网络不可用         */        void showNoNet();        /**         * 重试         */        void onRetry();        /**         * 绑定生命周期         *         * @param          * @return         */         LifecycleTransformer bindToLife();    }}
/** * 书本配置约定 * @author Veer * @email 276412667@qq.com * @date 18/7/2 */public interface BookContract {    interface View extends BaseContract.BaseView{        void setBook(BookModel model);    }    interface Presenter extends BaseContract.BasePresenter{        void getBook(FrameLayout4Loading frameLayout4Loading,String p, String tag, String start, String end);    }}


这里包括了P V,P用来处理业务逻辑,V更新视图。activity中的代码就少了很多了

/** * 描述 * * @author Veer * @email 276412667@qq.com * @date 18/7/2 */@Route(path= ActivityContracts.ACTIVITY_BOOK)public class BookActivity extends BaseActivity implements BookContract.View{    @BindView(R.id.tv_book)    TextView mTvBook;    @BindView(R.id.loading)    FrameLayout4Loading mFrameLayout4Loading;    @Override    public void setBook(BookModel model) {        mTvBook.setText(model.getBooks().toString());    }    @Override    protected void initView() {        mFrameLayout4Loading.setRefreashClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                mPresenter.getBook(mFrameLayout4Loading,"三国演义","","0","1");            }        });        mPresenter.getBook(mFrameLayout4Loading,"三国演义","","0","1");    }    @Override    protected  BookPresenter initPresenter() {        return new BookPresenter();    }    @Override    protected int getActivityLayoutID() {        return R.layout.activity_book;    }}

业务逻辑都在P中处理

public class BookPresenter extends BasePresenter implements BookContract.Presenter{    @Override    public void getBook(final FrameLayout4Loading frameLayout4Loading, String p, String tag, String start, String end) {//       mView.showLoading();        frameLayout4Loading.showLoadingView();        RetrofitHelper.getInstance().getServer()                .getBooks(p,tag,start,end)                .compose(RxSchedulers.applySchedulers())                .compose(mView.bindToLife())                .subscribe(new Consumer() {                    @Override                    public void accept(BookModel bookModel) throws Exception {//                        mView.hideLoading();                        frameLayout4Loading.hideLoadingView();                        mView.setBook(bookModel);                    }                }, new Consumer() {                    @Override                    public void accept(Throwable throwable) throws Exception {//                       mView.hideLoading();                        frameLayout4Loading.hideLoadingView();                        frameLayout4Loading.showDefaultExceptionView();                    }                });    }}

因为在大部分项目中都要网络请求,都需要加载框,统一封装了一个控件,用于加载框显示。

大部分加载框都有用到动画,可以使用Lottie  非常好用的一个开源动画,适用于android ios  h5。可参考 Lottie的使用。

我就贴一些主要代码:

/*** * 加载页面 * @author Veer * @email  276412667@qq.com * @date   18/7/3 */public class FrameLayout4Loading extends FrameLayout {/*** 没有数据显示 */public static final int ViewType_EmptyView = -1;/*** 请求中 */public static final int ViewType_Loading = 0;/*** 加载异常, 在无合适的异常类型使用的情况下使用此异常 */public static final int ViewType_DefaultException = 1;/*** 无网络 */public static final int ViewType_NoNetwork = 4;/*** 网络超时 */public static final int ViewType_Timeout = 5;/*** 数据异常, 服务器返回数据有误(比如反序列化失败) */public static final int ViewType_ServerException = 3;/** 其他错误 */public static final int EXP_DEFAULT_FAIL = 10001;/** 无法连接到网络,请检查网络配置 */public static final int EXP_NETWORK_NOTAVAILABLE = 90001;/** 超时了,请您重试 */public static final int EXP_REQUEST_TIMEOUT = 90003;/** 服务出错啦,请稍后重试 */public static final int EXP_SERVICE_FAIL = 90004;/** 服务调用出错,但不带重试按钮只显示信息 */public static final int EXP_SERVICE_FAIL_INFO = 150101;private static Animation loadingAnim;private SparseArray defaultLayout = new SparseArray(7);public SparseArray cachedLayout = new SparseArray(7);private LayoutInflater mInflater;private OnClickListener mRefreshClickListener;private ImageView icon;private TextView tip;private TextView subTip;private boolean viewUp = false;private LottieAnimationView mLottieAnimationView;private Context mContext;public boolean isViewUp() {return viewUp;}public void setViewUp(boolean viewUp) {this.viewUp = viewUp;}public FrameLayout4Loading(Context context) {super(context);init(context, null);}public FrameLayout4Loading(Context context, AttributeSet attrs) {super(context, attrs);init(context, attrs);}public FrameLayout4Loading(Context context, AttributeSet attrs,   int defStyle) {super(context, attrs, defStyle);init(context, attrs);}private void init(Context context, AttributeSet attrs) {this.mContext = context;if (isInEditMode())return;if (loadingAnim==null){loadingAnim = AnimationUtils.loadAnimation(context, R.anim.round_loading);}mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);if (attrs != null) {TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.FrameLayout4Loading);if (a != null) {setDefaultView(ViewType_Loading, a.getResourceId(R.styleable.FrameLayout4Loading_loadingView,R.layout.common_loading_indicator));setDefaultView(ViewType_NoNetwork, a.getResourceId(R.styleable.FrameLayout4Loading_loadingView,R.layout.common_error_default_style));setDefaultView(ViewType_Timeout, a.getResourceId(R.styleable.FrameLayout4Loading_loadingView,R.layout.common_error_default_style));setDefaultView(ViewType_DefaultException, a.getResourceId(R.styleable.FrameLayout4Loading_loadingView,R.layout.common_error_default_style));setDefaultView(ViewType_ServerException, a.getResourceId(R.styleable.FrameLayout4Loading_defaultExceptionView,R.layout.common_error_default_style));setDefaultView(ViewType_EmptyView,R.layout.common_empty_view);a.recycle();}}}public void setDefaultView(int viewType, int resLayout) {defaultLayout.put(viewType, resLayout);}public int getDefaultViewResId(int viewType) {return defaultLayout.get(viewType);}public void showView(int viewType, Drawable background) {int count = defaultLayout.size();for (int i = 0; i < count; i++) {int key = defaultLayout.keyAt(i);if (key == viewType) {doShowView(key, background);} else {hideView(key, background);}}}private void hideView(int viewType, Drawable background) {View view = cachedLayout.get(viewType);if (view == null)return;if (background != null) {view.setBackground(background);}view.setVisibility(GONE);}/** * 没有数据时显示 * @param background  图片 * @param msg  描述文字 */public void doShowNoData(Drawable background, String msg) {doShowNoData(background,msg,null,null);}/** * 没有数据时显示 * @param background  图片 * @param msg  描述文字 * @param btnTxt  按钮文字 * @param click   点击事件 */public void doShowNoData(Drawable background, String msg, String btnTxt, OnClickListener click) {int resLayoutId = defaultLayout.get(ViewType_EmptyView);if (resLayoutId <= 0)throw new IllegalStateException("layout is not set for " + ViewType_EmptyView);View view = cachedLayout.get(ViewType_EmptyView);if (view == null || click != null) {view = mInflater.inflate(resLayoutId, null);try {ImageView image = (ImageView)view.findViewById(R.id.empty_icon_iv);image.setBackground(background);TextView empText = (TextView) view.findViewById(R.id.empty_tip_tv);empText.setText(msg);if (click!=null){TextView empty_btn = (TextView) view.findViewById(R.id.empty_btn);empty_btn.setVisibility(View.VISIBLE);empty_btn.setOnClickListener(click);empty_btn.setText(btnTxt);}} catch (Exception e) {e.printStackTrace();}cachedLayout.put(ViewType_EmptyView, view);addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,Gravity.CENTER));initView(view, ViewType_EmptyView);initListener(view);}view.setVisibility(VISIBLE);view.bringToFront();}private void doShowView(int viewType, Drawable background) {int resLayoutId = defaultLayout.get(viewType);if (resLayoutId <= 0)throw new IllegalStateException("layout is not set for " + viewType);View view = cachedLayout.get(viewType);if (view == null) {view = mInflater.inflate(resLayoutId, null);try {TextView errorText = (TextView) view.findViewById(R.id.loading_error_text);if ( viewType == ViewType_Timeout|| viewType == ViewType_NoNetwork) {errorText.setText("亲爱的,您的网络开小差了哦");}else if(viewType == ViewType_DefaultException||viewType == ViewType_ServerException){errorText.setText("系统繁忙,请稍后再试!");}} catch (Exception e) {e.printStackTrace();}if (background != null) {if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {view.setBackground(background);} else {view.setBackgroundDrawable(background);}}cachedLayout.put(viewType, view);addView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,Gravity.CENTER));initView(view, viewType);initListener(view);}view.setVisibility(VISIBLE);view.bringToFront();}private void initView(View v, int viewType) {if (ViewType_EmptyView == viewType) {icon = (ImageView) v.findViewById(R.id.empty_icon_iv);tip = (TextView) v.findViewById(R.id.empty_tip_tv);subTip = (TextView) v.findViewById(R.id.empty_sub_tip_tv);} else if (ViewType_Loading == viewType) {mLottieAnimationView = v.findViewById(R.id.loading_iv);//设置加载速度mLottieAnimationView.setSpeed(10);}}private void initListener(View view) {View refreshBtn = view.findViewById(R.id.loading_refreash_btn);if (refreshBtn != null && mRefreshClickListener != null) {refreshBtn.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {mRefreshClickListener.onClick(v);}});}}/** * 显示加载动态图 */public void showLoadingView() {showLoadingView(null);}/** * 显示加载动态图 * @param backgroundId  背景图ID */public void showLoadingView(int backgroundId) {showLoadingView(getResources().getDrawable(backgroundId));}/** * 显示加载动态图 * @param background  背景图 */public void showLoadingView(Drawable background) {showView(ViewType_Loading, background);}/** * 隐藏加载动态图 */public void hideLoadingView() {hideLoadingView(null);}public void hideLoadingView(int backgroundId) {hideLoadingView(getResources().getDrawable(backgroundId));}public void hideLoadingView(Drawable background) {hideView(ViewType_Loading, background);}public void showDefaultExceptionView() {showExceptionView(EXP_DEFAULT_FAIL);}/** * 显示空页面 */public void showEmptyView() {showEmptyView(null);}public void showEmptyView(int backgroundId) {showEmptyView(getResources().getDrawable(backgroundId));}public void showEmptyView(Drawable background) {showView(ViewType_EmptyView, background);}/** * 显示异常页面 * @param errorCode */public void showExceptionView(int errorCode) {showView(getViewTypeByErrorCode(errorCode), null);}public void showExceptionView(int errorCode, int backgroundId) {showExceptionView(errorCode, getResources().getDrawable(backgroundId));}public void showExceptionView(int errorCode, Drawable background) {showView(getViewTypeByErrorCode(errorCode), background);}public void setExceptionViewIconVisibility(int visibility) {View view = cachedLayout.get(ViewType_DefaultException);view.findViewById(R.id.listview_error_pic).setVisibility(visibility);}public  FrameLayout4Loading setEmptyViewIcon(T obj) {if (obj == null || icon == null)return null;if (obj instanceof Integer)icon.setImageResource((Integer) obj);elseViewCompat.setBackground(icon, (Drawable) obj);return this;}public  FrameLayout4Loading setEmptyViewTip(T obj) {if (obj == null || tip == null)return null;if (obj instanceof Integer)((TextView) tip).setText(getResources().getString((Integer) obj));else if (obj instanceof CharSequence)((TextView) tip).setText((CharSequence) obj);return this;}public  FrameLayout4Loading setEmptyViewTip(T obj, T obj1) {if (obj == null || tip == null)return null;if (obj instanceof Integer)((TextView) tip).setText(getResources().getString((Integer) obj));else if (obj instanceof CharSequence)((TextView) tip).setText((CharSequence) obj);if (obj1 == null || subTip == null) {return null;}subTip.setVisibility(View.VISIBLE);if (obj1 instanceof Integer)((TextView) subTip).setText(getResources().getString((Integer) obj1));else if (obj instanceof CharSequence)((TextView) subTip).setText((CharSequence) obj1);return this;}public void hideAllMask() {hideAllMask(null);}public void hideAllMask(int backgroundId) {hideAllMask(getResources().getDrawable(backgroundId));}public void hideAllMask(Drawable background) {int count = cachedLayout.size();for (int i = 0; i < count; i++) {int key = cachedLayout.keyAt(i);hideView(key, background);}}private static int getViewTypeByErrorCode(int errorCode) {switch (errorCode) {case EXP_NETWORK_NOTAVAILABLE :return ViewType_NoNetwork;case EXP_REQUEST_TIMEOUT :return ViewType_Timeout;case EXP_SERVICE_FAIL :return ViewType_ServerException;case EXP_DEFAULT_FAIL :case EXP_SERVICE_FAIL_INFO :default :return ViewType_DefaultException;}}public void setRefreashClickListener(OnClickListener listener) {mRefreshClickListener = listener;}}

然后项目中海使用了一些比较方便编程的框架:

阿里的ARouter :activity直接跳转的管理。

ButterKnife  :控件注解。

Glide :图片框架。

这个项目框架后续我后一直更新完善:https://github.com/fuweiwei/RxJavaRetrofitMvp


源码下载


更多相关文章

  1. Android中网络传输不同内容
  2. Android(安卓)Lottie动画实战踩坑
  3. Android(安卓)UI框架概览
  4. Android(安卓)OpenGL ES
  5. Android基础—WebView(网页视图)基本用法
  6. Android(安卓)通过inputstream 加载非Drawable 文件夹下的 .9 pa
  7. Android(安卓)图片预览器加载微博长图,大图
  8. 解决ListView,GridView,Gallery的Adapter中的getView多次调...
  9. Java集合框架——Android中的ArrayList源码分析

随机推荐

  1. Android(安卓)直接打开log的一种方法
  2. android界面布局
  3. Android(安卓)网络多线程断点下载
  4. Android(安卓)各种基础控件布局
  5. Android(安卓)设置系统SystemUI 顶部Stat
  6. Android(安卓)8、Android(安卓)9获取手机
  7. Android之发送短信和接收验证码
  8. 详解Android中的屏幕方向
  9. android应用去掉title bar 及全屏处理方
  10. android关机充电流程、充电画面显示