安卓开发过程中,网络请求与下拉刷新分页列表的控件几乎可以说是必不可少的,但是每次开发一款产品都要重新开发,肯定是不可取的,那么最好是可以自己整理一个开发框架,那么以后开发,直接引入项目即可
网络框架的封装,从httpclient,到xutils,再到volley,再到okhttp,每次整合都发现多多少少的不足,目前自己觉得最成熟的一个也就是retrofit+okhttp3+rxjava的组合,rxjava不懂的推荐看大神的深入浅出rxjava,retrofit的使用自己网上搜咯
下拉刷新列表的实现,之前自己有基于listview整合的控件,用的到可以参考这里listview 实现下拉刷新上拉加载更多,现在有recyclerview整合的控件,看这里recyclerview实现下拉刷新自动加载更多
现在来整合retrofit+okhttp3+rxjava+recyclerview,丢掉handler与ancyctask,什么xutils,volley,httpclient都是浮云,老夫写代码只用retrofit,2333333333333333!
偷个图
先看一下以往的痛点
1.一不小心handler就造成了内存泄漏,程序关闭了,内存却无法释放
2.一不小心ancyctask的线程池模式,会让我们的请求按顺序执行完毕才行,即使页面退出,任务会继续执行,当快速关闭快速打开有网络请求的页面时,会等ancyctask所有未执行完毕的请求执行完以后才会执行当前的请求,这里就需要每次页面关闭就取消未执行完毕的请求
3.要为每个请求进行错误捕捉以及处理
4.请求时弹出进度提示,以及如何取消一个请求
那么利用这个整合的框架,所有问题就不再是问题
来看一下如何使用这个网络请求框架
必备的库
compile ‘com.squareup.retrofit2:retrofit:2.1.0‘ compile ‘com.squareup.retrofit2:converter-gson:2.1.0‘ compile ‘com.squareup.retrofit2:adapter-rxjava:2.1.0‘ compile ‘io.reactivex:rxjava:1.2.6‘ compile ‘io.reactivex:rxandroid:1.2.1‘
首先来看一下,如何使用retrofit+okhttp3+rxjava,进行非列表数据型网络请求
效果图么么,必须有
SubscriberOnNextListener<List<Subject>> getTopMovieOnNext = new SubscriberOnNextListener<List<Subject>>() { @Override public void onNext(List<Subject> subjects) { ToastUtil.ToastCenter("请求数据成功!"); Intent it=new Intent(mActivity,DetailActivity.class); it.putExtra("str",subjects.toString()); mActivity.startActivity(it); } }; ProgressSubscriber<List<Subject>> subscriber = new ProgressSubscriber<List<Subject>>(getTopMovieOnNext, mActivity, true, true); NetWorks.getInstance().Test250(subscriber, 0, 10);//网络请求
就是这么简单,如果有异常,将会在ProgressSubscriber中统一处理
ProgressSubscriber.java
/** * 用于在Http请求开始时,自动显示一个ProgressDialog * 在Http请求结束是,关闭ProgressDialog * 调用者自己对请求数据进行处理 */ public class ProgressSubscriber<T> extends Subscriber<T> implements ProgressCancelListener { private final boolean canShow;//是否显示进度条 private SubscriberOnNextListener mSubscriberOnNextListener; private ProgressDialogHandler mProgressDialogHandler; private Context context; /** * * @param mSubscriberOnNextListener * @param context * @param canShow 是否显示进度条 * @param canCancel 是否可以取消网络请求(只要progressbar消失,请求就被取消了) */ public ProgressSubscriber(SubscriberOnNextListener mSubscriberOnNextListener, Context context, boolean canShow, boolean canCancel) { this.mSubscriberOnNextListener = mSubscriberOnNextListener; this.context = context; this.canShow=canShow; mProgressDialogHandler = new ProgressDialogHandler(context, this, canCancel); } private void showProgressDialog(){ if (mProgressDialogHandler != null && canShow ) { mProgressDialogHandler.obtainMessage(ProgressDialogHandler.SHOW_PROGRESS_DIALOG).sendToTarget(); } } private void dismissProgressDialog(){ if (mProgressDialogHandler != null && canShow ) { mProgressDialogHandler.obtainMessage(ProgressDialogHandler.DISMISS_PROGRESS_DIALOG).sendToTarget(); mProgressDialogHandler = null; } } /** * 订阅开始时调用 * 显示ProgressDialog */ @Override public void onStart() { showProgressDialog(); } /** * 完成,隐藏ProgressDialog */ @Override public void onCompleted() { dismissProgressDialog(); } /** * 对错误进行统一处理 * 隐藏ProgressDialog * @param e */ @Override public void onError(Throwable e) { if (e instanceof SocketTimeoutException) { ToastUtil.ToastCenter("网络中断,请检查您的网络状态"); } else if (e instanceof ConnectException) { ToastUtil.ToastCenter("网络中断,请检查您的网络状态"); }else if (e instanceof HttpException){ //HTTP错误 ToastUtil.ToastCenter("网络异常,请检查您的网络状态"); e.printStackTrace(); } else if (e instanceof ApiException){ ToastUtil.ToastCenter(e.getMessage()); e.printStackTrace(); }else { //这里可以收集未知错误上传到服务器 ToastUtil.ToastCenter("服务器忙"); e.printStackTrace(); } dismissProgressDialog(); } /** * 将onNext方法中的返回结果交给Activity或Fragment自己处理 * * @param t 创建Subscriber时的泛型类型 */ @Override public void onNext(T t) { if (mSubscriberOnNextListener != null) { mSubscriberOnNextListener.onNext(t); } } /** * 取消ProgressDialog的时候,取消对observable的订阅,同时也取消了http请求 */ @Override public void onCancelProgress() { if (!this.isUnsubscribed()) { this.unsubscribe(); } } }
下面来看一下NetWorks做了哪些事情,也就算是定义一些网络请求的方法
public class NetWorks extends RetrofitUtils { private NetWorks() { super(); } //在访问HttpMethods时创建单例 private static class SingletonHolder { private static final NetWorks INSTANCE = new NetWorks(); } //获取单例 public static NetWorks getInstance() { return SingletonHolder.INSTANCE; } //测试用例子 public void Test250(Subscriber<List<Subject>> subscriber, int start, int count) { Observable observable = service.top250(start, count).map(new HttpResultFunc<List<Subject>>()); setSubscribe(observable, subscriber); } /** * 用来统一处理Http的resultCode,并将HttpResult的Data部分剥离出来返回给subscriber * * @param <T> Subscriber真正需要的数据类型,也就是Data部分的数据类型 */ private class HttpResultFunc<T> implements Func1<HttpResult<T>, T> { @Override public T call(HttpResult<T> httpResult) { /*if (httpResult.getCount() == 0) { throw new ApiException(100); }*/ return httpResult.getSubjects(); } } }
上面NetWorks是继承于RetrofitUtils,这才是真正的网络请求框架,这里整合了gson与rxjava,okhttp3
/** * retrofit工具类 */ public abstract class RetrofitUtils { private static Retrofit mRetrofit; private static OkHttpClient mOkHttpClient; protected static final NetService service = getRetrofit().create(NetService.class); /** * 获取Retrofit对象 * * @return */ protected static Retrofit getRetrofit() { if (null == mRetrofit) { if (null == mOkHttpClient) { mOkHttpClient = OkHttp3Utils.getOkHttpClient(); // mOkHttpClient = new OkHttpClient(); } //Retrofit2后使用build设计模式 mRetrofit = new Retrofit.Builder() //设置服务器路径 .baseUrl(UrlConstant.BASEURL) //添加转化库,默认是Gson .addConverterFactory(GsonConverterFactory.create()) //添加回调库,采用RxJava .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) //设置使用okhttp网络请求 .client(mOkHttpClient) .build(); } return mRetrofit; } /** * 插入观察者-泛型 * @param observable * @param observer * @param <T> */ public static <T> void setSubscribe(Observable<T> observable, Observer<T> observer) { observable.subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(observer); } }
OkhttpClient可以不用自己创建,retrofit会默认使用,这里自己创建,是为了添加一些拦截操作
public class OkHttp3Utils { private static OkHttpClient mOkHttpClient; //设置缓存目录 // private static File cacheDirectory = new File(MyApplication.getInstance().getApplicationContext().getCacheDir().getAbsolutePath(), "MyCache"); private static File cacheDirectory = new File(Constant.BASE_PATH, "MyCache"); private static Cache cache = new Cache(cacheDirectory, 10 * 1024 * 1024); /** * 获取OkHttpClient对象 * * @return */ public static OkHttpClient getOkHttpClient() { if (null == mOkHttpClient) { //同样okhttp3后也使用build设计模式 mOkHttpClient = new OkHttpClient.Builder() //设置一个自动管理cookies的管理器 // .cookieJar(new CookiesManager()) //没网络时的拦截器 .addInterceptor(new MyIntercepter()) //设置请求读写的超时时间 .connectTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .cache(cache) .build(); } return mOkHttpClient; } /** * 拦截器 */ private static class MyIntercepter implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request oldRequest = chain.request(); //gan-----start----------------以下代码为添加一些公共参数使用-------------------------- // 添加新的参数 String time = System.currentTimeMillis() / 1000 + ""; String mKey = "f6f712249f4b725fac309504d633f839"; HttpUrl.Builder authorizedUrlBuilder = oldRequest.url() .newBuilder() .scheme(oldRequest.url().scheme()) .host(oldRequest.url().host()); //.addQueryParameter("regionAId", "") //.addQueryParameter("regionZId", ""); //.addQueryParameter("os", "android") //.addQueryParameter("time", URLEncoder.encode(time, "UTF-8")) //.addQueryParameter("version", "1.1.0") //.addQueryParameter("sign", MD5.md5("key=" + mKey)); // 构建新的请求 Request newRequest = oldRequest.newBuilder() .method(oldRequest.method(), oldRequest.body()) .url(authorizedUrlBuilder.build()) .build(); //gan-----end if (!isNetworkReachable(MyApplication.getInstance().getApplicationContext())) { newRequest = newRequest.newBuilder() .cacheControl(CacheControl.FORCE_CACHE)//无网络时只从缓存中读取 .build(); } Response response = chain.proceed(newRequest); if (isNetworkReachable(MyApplication.getInstance().getApplicationContext())) { int maxAge = 60 * 60; // 有网络时 设置缓存超时时间1个小时 response.newBuilder() .removeHeader("Pragma") .header("Cache-Control", "public, max-age=" + maxAge) .build(); } else { int maxStale = 60 * 60 * 24 * 28; // 无网络时,设置超时为4周 response.newBuilder() .removeHeader("Pragma") .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) .build(); } //***************打印Log***************************** if (Constant.DEBUG) { String requestUrl = newRequest.url().toString(); // 获取请求url地址 String methodStr = newRequest.method(); // 获取请求方式 RequestBody body = newRequest.body(); // 获取请求body String bodyStr = (body == null ? "" : body.toString()); // 打印Request数据 LogUtils.D("gan-retrofit-okhttp3", "requestUrl=====>" + requestUrl); LogUtils.D("gan-retrofit-okhttp3", "requestMethod=====>" + methodStr); LogUtils.D("gan-retrofit-okhttp3", "requestBody=====>" + bodyStr); if (Constant.NET_DATA_SHOW) { //打印返回数据 LogUtils.D("gan-retrofit-okhttp3", "responseBody=====>" + response.body().string()); Constant.NET_DATA_SHOW=false; } } return response; } } private static String bodyToString(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"; } } /** * 自动管理Cookies */ private static class CookiesManager implements CookieJar { private final PersistentCookieStore cookieStore = new PersistentCookieStore(MyApplication.getInstance().getApplicationContext()); //在接收时,读取response header中的cookie @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { if (cookies != null && cookies.size() > 0) { for (Cookie item : cookies) { cookieStore.add(url, item); } } else { Log.i("gan", "cookie为null"); } } //分别是在发送时向request header中加入cookie @Override public List<Cookie> loadForRequest(HttpUrl url) { Log.i("gan", "url为---" + url); List<Cookie> cookies = cookieStore.get(url); if (cookies.size() < 1) { Log.i("gan", "cookies为null"); } return cookies; } } /** * 判断网络是否可用 * * @param context Context对象 */ public static Boolean isNetworkReachable(Context context) { ConnectivityManager cm = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo current = cm.getActiveNetworkInfo(); if (current == null) { return false; } return (current.isAvailable()); } }
不能忘了netService,接口就是在这里定义咯
public interface NetService { @GET("top250") Observable<HttpResult<List<Subject>>> top250(@Query("start") int start, @Query("count") int count); }
上面简单的就完成了一个网络请求,下面来与下拉刷新分页加载控件一起使用
先看效果
再看如何使用,这里我用的是子页面,activity中用法是一样的
public class MessagePager extends ContentBasePager implements MyRecycleView.RefreshLoadMoreListener { @BindView(R.id.message_page_recycleview) MyRecycleView recycleView; private CommonAdapter<Subject> mAdapter; private RecycleviewSubscriberOnNextListener<List<Subject>> getTopMovieOnNext; private List<Subject> actAllList = new ArrayList<Subject>(); private boolean isFirstIn =true; private RecycleviewSubscriber<List<Subject>> subscriber; public MessagePager(AppCompatActivity activity) { super(activity); ButterKnife.bind(this, mRootView); } @Override public void initData() { if (isFirstIn){ initView(); recycleView.firstLoadingView("数据加载中"); isFirstIn = false; } } @Override public void outData() { //ToastUtil.ToastCenter(mActivity,"离开HomePager"); } @Override public int getContentView() { return R.layout.pager_message; } private void initView() { initAdapter();//初始化适配器 recycleView.setRefreshLoadMoreListener(this);//下拉上拉加载更多监听 //prrv.setPullRefreshEnable(false);//禁用刷新 recycleView.setCanMore(false);//禁用加载更多用在setAdapter()之前 //设置布局管理 StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL); recycleView.setLayoutManager(layoutManager); recycleView.setAdapter(mAdapter); //条目监听 recycleView.setOnItemClickListener(new MyRecycleView.ItemClickListener() { @Override public void onClick(View view, RecyclerView.ViewHolder holder, int position) { } @Override public void onLongClick(View view, RecyclerView.ViewHolder holder, int position) { ToastUtil.ToastCenter("longclick-pos = " + position); } }); initNetListener(); } /** * 初始化适配器 */ private void initAdapter() { mAdapter = new CommonAdapter<Subject>(mActivity, R.layout.fragment_discover_cardview_item, actAllList) { @Override protected void convert(ViewHolder holder, Subject s, int position) { holder.setText(R.id.activity_title, s.getTitle()); /*holder.setText(R.id.activity_date, Utils.longtimeToDayDate(a .getStartDate()) + "-" + Utils.longtimeToDayDate(a.getEndDate()));*/ holder.setText(R.id.activity_date, s.getYear()); ImageView imageView=holder.getView(R.id.img_iv); ImageLoader.getInstance().displayImage(s.getImages().getLarge(), imageView); } }; } @Override public void onRefresh() { //Constant.NET_DATA_SHOW=true;//开启数据打印到log subscriber =new RecycleviewSubscriber<List<Subject>>(getTopMovieOnNext, recycleView, R.drawable.icon_nonet, R.drawable.icon_err); NetWorks.getInstance().Test250(subscriber, 0, 10); } @Override public void onLoadMore() { } private void initNetListener() { getTopMovieOnNext = new RecycleviewSubscriberOnNextListener<List<Subject>>() { @Override public void onNext(List<Subject> subjects) { recycleView.setDateRefresh(actAllList, subjects, R.drawable.icon_no_order, "暂无订单"); ToastUtil.ToastCenter("刷新完成"); } @Override public void onErr(int drawable, String msg) { if (actAllList.isEmpty()) recycleView.setDateRefreshErr(drawable, msg);//显示错误面板 else { ToastUtil.ToastCenter(msg);//提示信息 recycleView.stopRefresh();//停止刷新 } } }; } /**取消网络请求操作,activty中同样用在ondestroy方法中**/ @Override public void onDestroy() { if (subscriber!=null)subscriber.onActivityDestroy(); super.onDestroy(); } }
页面xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <RelativeLayout android:layout_width="match_parent" android:layout_height="@dimen/toolbarSize" android:background="?attr/colorPrimary" android:paddingRight="@dimen/dp13" android:paddingLeft="@dimen/dp13" > <TextView style="@style/first_title_white" android:text="@string/message_pager_title" android:layout_centerInParent="true"/> </RelativeLayout> <com.gan.myrecycleview.MyRecycleView android:id="@+id/message_page_recycleview" android:layout_width="match_parent" android:layout_height="match_parent" > </com.gan.myrecycleview.MyRecycleView> </LinearLayout>
之所以可以跟recyclerview一起使用,那么就靠自定义控件MyRecyleView与RecycleviewSubscriber了
** * 用于跟recycleview组合使用时的Subscriber * @param <T> */ public class RecycleviewSubscriber<T> extends Subscriber<T> { private final MyRecycleView recycleView; private final int noNet; private final int onErr; private RecycleviewSubscriberOnNextListener mSubscriberOnNextListener; /** * * @param mSubscriberOnNextListener * @param recycleView * @param onErr 出现异常时图片 * @param noNet //无网络时的图片 */ public RecycleviewSubscriber(RecycleviewSubscriberOnNextListener mSubscriberOnNextListener, MyRecycleView recycleView, int noNet, int onErr) { this.mSubscriberOnNextListener = mSubscriberOnNextListener; this.recycleView = recycleView; this.onErr=onErr; this.noNet=noNet; } /** * 订阅开始时调用 */ @Override public void onStart() { } /** * 完成 */ @Override public void onCompleted() { } /** * 对错误进行统一处理 * 隐藏ProgressDialog * @param e */ @Override public void onError(Throwable e) { if (e instanceof SocketTimeoutException) { doErr(noNet,"网络连接超时,请检查您的网络状态"); } else if (e instanceof ConnectException) { doErr(noNet,"网络中断,请检查您的网络状态"); }else if (e instanceof HttpException){ //HTTP错误 doErr(noNet,"网络异常,请检查您的网络状态"); e.printStackTrace(); } else if (e instanceof ApiException){ //ToastUtil.ToastCenter(e.getMessage()); doErr(onErr,e.getMessage()); e.printStackTrace(); }else { doErr(onErr,"服务器忙"); e.printStackTrace(); } } /** * 处理未知异常 */ private void doErr(int pic ,String err) { if (mSubscriberOnNextListener != null) { mSubscriberOnNextListener.onErr(pic,err); } } /** * 将onNext方法中的返回结果交给Activity或Fragment自己处理 * * @param t 创建Subscriber时的泛型类型 */ @Override public void onNext(T t) { if (mSubscriberOnNextListener != null) { mSubscriberOnNextListener.onNext(t); } } /** * activity销毁,取消对observable的订阅,同时也取消了http请求 */ public void onActivityDestroy() { if (!this.isUnsubscribed()) { this.unsubscribe(); } } }
最后肯定demo咯