OkHttp3.0(Retrofit 2/Rxjavaと組み合わせる)ブロッキングによるグローバルタイムアウト自動ログイン、統合パラメータの追加

13673 ワード

適用シーン:1.サービス側は、各プラットフォーム、バージョンの使用状況を統計するために、version(クライアントバージョン)、os(クライアントプラットフォームandroid/iOS)、userIdなどのパラメータをインタフェースに統一するように要求する場合があるが、インタフェースに追加すると煩雑になり、グローバル処理を考慮する.また,1回のログインに成功すると,ログイン状態はいずれも時効があるため,ログイン失効が発生した後に自動的にログイン状態を再リフレッシュする必要があり,また一般的には,1つのリクエストが発行される前にログインタイムアウトしたか否かを判断することができないため,全局的な処理案が必要となる.
実はこれはRetrofit 2/Rxjavaとは関係ないようですが、タイトルにこれを言及したのは、私のプロジェクトがこの2つのライブラリを組み合わせて使っているからです.私はこのような問題の解決方法を検索するときにRetrofit 2/RxjavaのretryWhen方法から始めました.その後、OkHttpClientに直接ブロッキングを追加すれば、所望の効果を実現することができ、グローバル性があることがわかりました.これはこのような問題の最も簡単な解決方法であるはずです.
主なコードは次のとおりです:Network.java
import android.text.TextUtils;
import android.util.Log;

import com.google.gson.Gson;
import com.google.gson.JsonObject;

import org.greenrobot.eventbus.EventBus;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

import okhttp3.Cookie;
import okhttp3.CookieJar;
import okhttp3.FormBody;
import okhttp3.HttpUrl;
import okhttp3.Interceptor;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.logging.HttpLoggingInterceptor;
import okio.BufferedSource;
import retrofit2.Call;
import retrofit2.CallAdapter;
import retrofit2.Converter;
import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

public class Network {

    private static final String TAG = "Network";

    private static APIService apis;
    private static ReLoginService reLoginService;

    private static Converter.Factory mExtraGsonConverterFactory = ExtraGsonConverterFactory.create();
    private static CallAdapter.Factory rxJavaCallAdapterFactory = RxJavaCallAdapterFactory.create();

    public static APIService getAPIService() {
        if (apis == null) {
            // TODO       
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

            // OkHttp3.0     
            OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .retryOnConnectionFailure(true)
                    .addInterceptor(new Interceptor() {
                        @Override
                        public Response intercept(Chain chain) throws IOException {
                            Request original = chain.request();
                            Request.Builder requestBuilder = original.newBuilder();
                            if (original.body() instanceof FormBody) {
                                FormBody.Builder newFormBody = new FormBody.Builder();
                                FormBody oldFormBody = (FormBody) original.body();
                                for (int i = 0; i < oldFormBody.size(); i++) {
                                    newFormBody.addEncoded(oldFormBody.encodedName(i), oldFormBody.encodedValue(i));
                                }
                                newFormBody.add("os", "android");
                                requestBuilder.method(original.method(), newFormBody.build());
                            } else if (original.body() instanceof MultipartBody) {
                                MultipartBody.Builder newFormBody = new MultipartBody.Builder();
                                //    multipart/mixed,  【     php            】
                                newFormBody.setType(MediaType.parse("multipart/form-data"));
                                MultipartBody oldFormBody = (MultipartBody) original.body();
                                for (int i = 0; i < oldFormBody.size(); i++) {
                                    newFormBody.addPart(oldFormBody.part(i));
                                }
                                newFormBody.addFormDataPart("os", "android");
                                requestBuilder.method(original.method(), newFormBody.build());
                            } else if (TextUtils.equals(original.method(), "POST")) {
                                FormBody.Builder newFormBody = new FormBody.Builder();
                                newFormBody.add("os", "android");
                                requestBuilder.method(original.method(), newFormBody.build());
                            }

                            Request request = requestBuilder.build();
                            return chain.proceed(request);
                        }
                    })
                    .addInterceptor(new Interceptor() {
                        @Override
                        public Response intercept(final Chain chain) throws IOException {
                            //     
                            Request request = chain.request();
                            Response response = chain.proceed(request);
                            ResponseBody responseBody = response.body();
                            BufferedSource source = responseBody.source();
                            source.request(Long.MAX_VALUE);
                            String respString = source.buffer().clone().readString(Charset.defaultCharset());
                            Log.d(TAG, "--->    ,respString = " + respString);
                            // TODO               
                            JSONObject j = null;
                            try {
                                j = new JSONObject(respString);
                            } catch (JSONException e) {
                                e.printStackTrace();
                            }
                            //            700      【   java,       cookie,  token  。       token,     】
                            if (j!= null && j.optInt("status") == 700) {
                                Log.d(TAG, "--->    ,      ");
                                // TODO         user  
                                UserInfo  user = SysApplication.getInstance().getDB().getCurrentUser();
                                if (user == null) {
                                    Log.d(TAG, "--->             ");
                                    //              (BaseSubscriber   )
                                    throw new ExtraApiException(700, "   ");
                                }
                                String phoneNum = user.getPhoneNum();
                                String password = user.getPass();
                                Call call = getReloginService().reLogin(phoneNum, password);
                                JsonObject json = call.execute().body();
                                //          
                                if (json.get("status").getAsInt() == 200) {
                                    // TODO      ,          、     
                                    //                 !!!
                                    response = chain.proceed(request);
                                    Log.d(TAG, "--->      ");
                                } else {
                                    Log.d(TAG, "--->      ");
                                    // TODO              (BaseSubscriber   ,              ,              )
                                    throw new ExtraApiException(700, "     ");
                                }
                            }
                            return response;
                        }
                    })
                    .cookieJar(new CookieJar() {
                        List cookies;
                        @Override
                        public void saveFromResponse(HttpUrl url, List cookies) {
                             // TODO           ,  cookies
                        }

                        @Override
                        public List loadForRequest(HttpUrl url) {

                            return cookies;
                        }
                    })
                    .addInterceptor(loggingInterceptor)
                    .build();

            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(Constant.HOST_APP)
                    .addConverterFactory(mExtraGsonConverterFactory)
                    .addCallAdapterFactory(rxJavaCallAdapterFactory)
                    .client(okHttpClient)
                    .build();
            apis = retrofit.create(APIService.class);
        }
        return apis;
    }


    //              interface ,                Gson   ,             status,       
    //      ,        ,   cookie   
    public static ReLoginService getReloginService() {
        if (reLoginService == null) {
            // TODO       
            HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

            // OkHttp3.0     
            OkHttpClient okHttpClient = new OkHttpClient.Builder()
                    .addInterceptor(loggingInterceptor)
                    .cookieJar(new CookieJar() {
                        @Override
                        public void saveFromResponse(HttpUrl url, List cookies) {
                            // TODO
                        }

                        @Override
                        public List loadForRequest(HttpUrl url) {
                            return new ArrayList<>();
                        }
                    })
                    .build();

            Retrofit retrofit = new Retrofit.Builder()
                    .baseUrl(Constant.HOST_APP)
                    .addConverterFactory(GsonConverterFactory.create())
                    .client(okHttpClient)
                    .build();
            reLoginService = retrofit.create(ReLoginService.class);
        }
        return reLoginService;

    }
}
ExtraGsonResponseBodyConverter.javaこれはカスタム解析器であり、サービス側が返す最外層情報を除去し、内部有効情報体のみを保持する役割を果たす.また,返されたstatusを統一的に処理し,カスタム例外を投げ出し,グローバルに統一されたSubscriberでonError処理を行うこともできる.
import android.text.TextUtils;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.charset.Charset;

import okhttp3.MediaType;
import okhttp3.ResponseBody;
import retrofit2.Converter;

import static okhttp3.internal.Util.UTF_8;

final class ExtraGsonResponseBodyConverter implements Converter {
    private final Gson gson;
    private final TypeAdapter adapter;

    ExtraGsonResponseBodyConverter(Gson gson, TypeAdapter adapter) {
        this.gson = gson;
        this.adapter = adapter;
    }

    @Override
    public T convert(ResponseBody value) throws IOException {
        try {
            JSONObject response = new JSONObject(value.string());


            //        ,      ,  Subscriber onError    
            if (response.optInt("status") != 200) {
                value.close();
                throw new ExtraApiException(response.optInt("status"), response.optString("message"));
            }

            //        、   ,        ……
            String info = response.optString("json");
            if (TextUtils.isEmpty(info)) {
                info = response.optString("resultList");
            }
            if (TextUtils.isEmpty(info) || TextUtils.equals(info.toLowerCase(), "null")) {
                info = "{}";
            }

            MediaType contentType = value.contentType();
            Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8;


            InputStream inputStream = new ByteArrayInputStream(info.getBytes());
            Reader reader = new InputStreamReader(inputStream, charset);
            JsonReader jsonReader = gson.newJsonReader(reader);

            return adapter.read(jsonReader);
        } catch (JSONException e) {
            throw new IOException();
        } finally {
            value.close();
        }
    }
}
ReLoginService.javaは、再ログイン要求を単独で行い、サービス側が返すすべての情報体を保持するために使用される.
import com.google.gson.JsonObject;

import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST;


public interface ReLoginService {

    /**
     *   
     *
     * @param name    
     * @param pass ,  
     */
    @FormUrlEncoded
    @POST("msLogin")
    Call reLogin(
            @Field("name") String name,
            @Field("pass") String pass
    );
}

ここで,問題解決の主な考え方は,OkHttpClientのOkHttpClient手法を利用することである.
  • 既存要求を再組立て
  • .
  • は応答情報を前処理し、対応するログインが失効した場合、再ログインを行い、ログインが成功すれば元の要求を再実行する.ログインが成功しない場合は、ユーザーに
  • へのログインを促す.
    また、いくつかの説明があります.
  • カスタムConverterFactoryは、返信情報の無効な情報または複数の階層を統一的に処理するのに有利であり、単一の要求が成功した後の処理ロジック
  • を簡略化する.
  • カスタムExceptionは、例外
  • をグローバルに統合されたSubscriber(RxJavaと組み合わせた)で処理するのに有利である.
  • 要求情報の表示を補助するHttpLoggingInterceptorは、最後にaddInterceptorを行います.そうしないと、前に行った処理(クッキー、追加されたパラメータ)がログに表示されない可能性があります.