1. 程式人生 > >retrofit+rxjava+recyclerview+下拉重新整理+自動載入更多

retrofit+rxjava+recyclerview+下拉重新整理+自動載入更多

安卓開發過程中,網路請求與下拉重新整理分頁列表的控制元件幾乎可以說是必不可少的,但是每次開發一款產品都要重新開發,肯定是不可取的,那麼最好是可以自己整理一個開發框架,那麼以後開發,直接引入專案即可

網路框架的封裝,從httpclient,到xutils,再到volley,再到okhttp,每次整合都發現多多少少的不足,目前自己覺得最成熟的一個也就是retrofit+okhttp3+rxjava的組合,rxjava不懂的推薦看大神的深入淺出rxjava,retrofit的使用自己網上搜咯

現在來整合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咯

csdn下載地址