码迷,mamicode.com
首页 > 编程语言 > 详细

retrofit+rxjava+recyclerview+下拉刷新+自动加载更多

时间:2017-03-22 10:38:34      阅读:554      评论:0      收藏:0      [点我收藏+]

标签:body   empty   ntb   测试用例   imageview   androi   null   先来   images   

安卓开发过程中,网络请求与下拉刷新分页列表的控件几乎可以说是必不可少的,但是每次开发一款产品都要重新开发,肯定是不可取的,那么最好是可以自己整理一个开发框架,那么以后开发,直接引入项目即可

网络框架的封装,从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咯


github

csdn








retrofit+rxjava+recyclerview+下拉刷新+自动加载更多

标签:body   empty   ntb   测试用例   imageview   androi   null   先来   images   

原文地址:http://blog.csdn.net/u012402940/article/details/64439417

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!