解析オープンソースプロジェクトのRetrofit(二)フレームワーク編


作者の労働の成果を大切にして、もし転載するならば、出所を明記して下さい.http://blog.csdn.net/zhengzechuan91/article/details/50349934
Retrofitはsquareのソースのhttpフレームで、簡単に使いやすくて、okhttpとRxJavaをサポートしています.もしあなたが煩雑なhttp要求を配置するために自分のネット要求フレームを書きたくないなら、この優雅なフレームを試してみてもいいです.
上記のブログでは、プロジェクトでの利用を紹介しました.今日はこの枠組みの実現原理について説明します.
Retrofitの原理
まず、Retrofitの運行原理を説明します.
  • は、RealServiceを生成し、Real ServiceをRealServiceとして、ReltAdapterの内部的なResTradlerダイナミックエージェントとして生成する.
  • RealServiceの方法を呼び出すと、Relt Handler.invoke()によってプロキシされます.
  • は、Request Buiderの例を生成し、set ArgementsによってRequest Buiderの例にパラメータを解析し、パラメータがPOJOタイプであれば、CoverterのtoBody(Object)を介してPOJOをTypedOutputに変換する.
  • 私たちが設定したRequest Interceptorを利用して、http要求を送信する前にRequest Buiderをブロックし、パラメータを追加して、最後にRequestに変換します.
  • はCient.executeを実行して、Requestに戻ります.
  • Reponseの状態コードが2*である場合、処理を継続します.そうでなければ、様々なRetrofitErr異常をスローします.
  • が必要とするリターンタイプはResonseで、同期は直接リターンし、非同期はResonseWrapperとして包装する.
  • に必要な戻りタイプがReponseでなければ、設定されたConterのfrom Body(Typed Input、Type)によりType InputをPOJOに変換し、同期してそのままリターンし、非同期でRespnseWrapperに包装します.
  • 上で見たように、このフレームワークは異なるプラットフォームをPlatformとして抽象化し、異なる要求方式をRequestとして抽象化し、異なるリターン結果をReponseとして抽象化し、CoverterはRequestとPOJOの変換とReponseとPOJOの変換を提供しています.
    私たちはフレームに対して全体的な理解を持っているので、フレームに対する理解を深めるために、いくつかの詳細を見てみます.
    フレーム
    私たちは上の考え方に基づいてプラットフォーム、要求、ブロック、バックの四つの角度からフレームに対して深く分析します.
    プラットフォーム
    プラットフォームの選択は抽象的なPlatformによって切り替わります.
    プラットフォームに関する変数は、Platformで抽象化されています.
    /* Platform.java */
    
      //  Converter
      abstract Converter defaultConverter();
      //  Client
      abstract Client.Provider defaultClient();
      //    http   
      abstract Executor defaultHttpExecutor();
      //    http   
      abstract Executor defaultCallbackExecutor();
      //  log
      abstract RestAdapter.Log defaultLog();
    
    三つの内部のAndroid、App Engine、Baseはそれぞれ三つの異なるプラットフォームを表す抽象的な方法を実現しました.選択する時、私達も前の二つのプラットフォームが満たされない時は、デフォルトのベースを使います.
    /* Platform$Android.java */
    
      private static class Android extends Platform {
        @Override Converter defaultConverter() {
          //   Gson  
          return new GsonConverter(new Gson());
        }
    
        @Override Client.Provider defaultClient() {
          final Client client;
          if (hasOkHttpOnClasspath()) {
            //   okhttp  ,  OkClient
            client = OkClientInstantiator.instantiate();
          } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) {
            //android2.3  ,  ApacheClient
            client = new AndroidApacheClient();
          } else {
            //android2.3   ,  HttpURLConnection
            client = new UrlConnectionClient();
          }
          return new Client.Provider() {
            @Override public Client get() {
              return client;
            }
          };
        }
    
        @Override Executor defaultHttpExecutor() {
          //          
          return Executors.newCachedThreadPool(new ThreadFactory() {
            @Override public Thread newThread(final Runnable r) {
              return new Thread(new Runnable() {
                @Override public void run() {
                  Process.setThreadPriority(THREAD_PRIORITY_BACKGROUND);
                  r.run();
                }
              }, RestAdapter.IDLE_THREAD_NAME);
            }
          });
        }
    
        @Override Executor defaultCallbackExecutor() {
          //              
          return new MainThreadExecutor();
        }
    
        @Override RestAdapter.Log defaultLog() {
          return new AndroidLog("Retrofit");
        }
      }
    
    ActheClientとUrlConnection Cientは直接Clientインターフェースを実現しました.OkClientはUrlConnection Cientを継承しました.
    ApachClientはapacheのHttp Clientのパッケージであり、UrlConnection CientはjavaのUrlConnection Clientのパッケージであり、OkClientはokhttpフレームのOkHttpClientのパッケージである.
    ApachClientとUrlConnection Cientの要求コードを見てみます.
    /* ApacheClient.java */
    
      @Override 
      public Response execute(Request request) throws IOException {
        // HttpUriRequest   Request uri head,   
        //Request body   ,  HttpUriRequest Entry   
        //Request body
        HttpUriRequest apacheRequest = createRequest(request);
        //  http  
        HttpResponse apacheResponse = execute(client, apacheRequest);
        //  HttpResponse head entry,   Response  
        //  entry   , Response body TypedByteArray
        return parseResponse(request.getUrl(), apacheResponse);
      }
    /* UrlConnectionClient.java */
    
        @Override
        public Response execute(Request request) throws IOException {
            //  Client  
            HttpURLConnection connection = openConnection(request);
            // connection   Request head,  Request 
            //body   ,  body  connection 
            //  body     -1,          
            //     -1,              
            //               ,    ,   oom
            prepareRequest(connection, request);
            //  connection      Response, Response 
            //body TypedInputStream
            return readResponse(connection);
        }
    上記のプラットフォームの関連コードを見たら、必ず質問があります.Requestのbody値はいつですか?
    まず、Request Builderという種類を見てみましょう.このクラスは呼び出す方法の解析です.中にはTyped Outputタイプのbodyに対して3つの場所が割り当てられています.
    要求
    RestAdapter adapter =   new RestAdapter.Builder()
            .setClient()
            .setEndpoint()
            .setRequestInterceptor()
            .setConverter()
            .setLogLevel(LogLevel.FULL)
            .build();
    
    RealService real = adapter.create(RealService.class);
    私たちはRertAdapter.Buider().build()にいる時、システムは私たちが実現していない構成を設定してくれます.これらのパラメータはすべてPlatformによって生成されたデフォルト値です.
    Reset Adapter()は、まず代理が必要なのはインターフェースであると判断し、他の種類やインターフェースとの継承や実現の関係がないと判断します.そして、RealServiceは、ReltAdapterの内部クラスのRelt Handler動的エージェントインターフェースを介して行われる.
    次のすべての操作は、RestHandler()で行われます.
    serviceMethodInfocacheにはRealServiceインターフェースごとに対応するRestMethodInfoキューがキャッシュされています.各MethodはRets MethodInfoに対応しています.
    RealServiceインターフェースに対応するキューをデフォルトで作成し、serviceMethodInfoCacheに追加します.
    RealServiceがget List()メソッドを呼び出してデータを取得すると、RealServiceがRest Handler()に実行されます.
            @Override
            public Object invoke(Object proxy, Method method, final Object[] args) throws Throwable {
                //               
                if (method.getDeclaringClass() 
                        == Object.class) {
                    return method.invoke(this, args);
                }
    
                //     RealService     
                // methodDetailsCache     ,
                //  Method       RestMethodInfo
                //      Method     
                final RestMethodInfo methodInfo = getMethodInfo(methodDetailsCache, method);
    
                //        object  
                if (methodInfo.isSynchronous) {
                    try {
                        return invokeRequest(requestInterceptor, methodInfo, args);
                    } catch (RetrofitError error) {
                        throw newError;
                    }
                }
    
    
                //   RequestInterceptorTape  
                final RequestInterceptorTape interceptorTape = new RequestInterceptorTape();
    
                //         Callback,  
                Callback<?> callback = (Callback<?>) args[args.length - 1];
    
                //     CallbackRunnable
                // 1.      obtainResponse()  
                // ResponseWrapper
                // 2.   Callback    Response
                httpExecutor.execute(new 
                CallbackRunnable(callback, 
                    callbackExecutor) {
                    @Override
                    public ResponseWrapper obtainResponse() {
                        return (ResponseWrapper) 
                        invokeRequest(interceptorTape, 
                        methodInfo, args);
                    }
                });
                return null; 
            }
    私たちは最後にRertAdapter(zhi invoke Request)メソッドに呼び出されました.この方法はブロック、http要求の詳細を封入して、Reponseに戻りました.
    この方法の論理を重点的に見ます.
        //  RestMethodInfo                  ,
        //                       
        methodInfo.init(); 
    
    Restit MethodInfo()メソッドのコードを見てみましょう.
        synchronized void init() {
            if(!this.loaded) {
                this.parseMethodAnnotations();
                this.parseParameters();
                this.loaded = true;
            }
        }
    そして、RestMethodInfo()は主に方法の注釈を解析しました.そこで@POST、@GETに対して相対的な経路を注釈する処理を見てみます.
        private void parsePath(String path) {
            //           /   ,     
            if (path == null || path.length() == 0 || path.charAt(0) != '/') {
                throw methodError("URL path \"%s\" must start with '/'.", path);
            }
    
            //url     
            String url = path;
            String query = null;
            int question = path.indexOf('?');
    
            //           ,            
            if (question != -1 && question < path.length() - 1) {
                url = path.substring(0, question);
                query = path.substring(question + 1);
    
                Matcher queryParamMatcher = PARAM_URL_REGEX.matcher(query);
    
                //               ,    
                if (queryParamMatcher.find()) {
                    throw methodError("URL query string \"%s\" must not have replace block.", query);
                }
            }
    
            //             
            Set<String> urlParams = parsePathParameters(path);
    
            //requestUrl           
            requestUrl = url;
            //requestUrlParamNames             
            requestUrlParamNames = urlParams;
            //requestQuery            :
            //' id=1&name="android" '
            requestQuery = query;
        }
    解析パラメータの注釈を担当する方法のRestMethodInfoは、この方法の各パラメータの名称をrequest ParameNames配列に置き、各パラメータの注釈のタイプをrequest ParameUsage配列に置く.
    この時、この方法の注釈はすでに解析済みで、このRequest Builderを作成します.
    // RequestBuilder  RestMethodInfo  
    //queryParams:' ?id=1&name="android" '
    RequestBuilder requestBuilder = new 
    RequestBuilder(serverUrl, methodInfo, converter);
    
    // head、url   args  ,args      null ,    
    requestBuilder.setArguments(args);
    
    //      url
    methodInfo.setRelativeUrl(requestBuilder.getRelativeUrl());
    methodInfo.setArgs(args);
    この時、私たちはもうRequest Builderにrequest時に必要なパラメータを用意しました.
    遮る
    http要求前に、headに公共パラメータを追加したいかもしれません.フレームワークはRequest Interceptorインターフェースを提供してくれます.Request Intercept orを通じて、パラメータを修正できます.
    public interface RequestInterceptor {
    
        void intercept(RequestInterceptor.RequestFacade var1);
    
        public interface RequestFacade {
            void addHeader(String var1, String var2);
    
            void addPathParam(String var1, String var2);
    
            void addEncodedPathParam(String var1, 
               String var2);
    
            void addQueryParam(String var1, String var2);
    
            void addEncodedQueryParam(String var1, 
               String var2);
    
            RestMethodInfo getMethodInfo();
        }
    }
    実際のコードの中でどうやってブロックされているか確認します.
    //  
    if (requestInterceptor instanceof
            RequestInterceptorTape) {
    
        RequestInterceptorTape interceptorTape = 
          (RequestInterceptorTape) requestInterceptor;
    
        interceptorTape.setMethodInfo(methodInfo);
    
        //        interceptorTape     
        //    RequestInterceptorTape#add*() tape   
        //  Command  
        RestAdapter.this.requestInterceptor
            .intercept(interceptorTape);
    
        // interceptorTape requestBuilder     
        //    RequestInterceptorTape#intercept()
        //  tape    Command   requestBuilder    
        interceptorTape.intercept(requestBuilder);
    } else { //  
    
        //        requestBuilder     
        requestInterceptor.intercept(requestBuilder);
    }
    
    パラメータを追加したら、私たちのブロックのタスクも完了します.
    戻る
    ブロックが終わったら、Request Buiderを通じて、urlのルートと相対パスを合わせて組み付けて、Request BuiderをRequestに転化しました.
    Request request = requestBuilder.build();
    現在のプラットフォームに従って要求を実行して、Resonseに戻ります.
    Response response = clientProvider.get().execute(request);
    タイプは方法の返却タイプで、これはAPIリターンデータです.Typed InputをPOJOに変換する時、Coverter_from Body(Typed Input、Type)の第二のパラメータで、私達の方法が戻るタイプを表しています.
    Type type = methodInfo.responseObjectType;
    戻り値の状態コードが2 XXでないと、RetrofitErrが異常です.
    リターンされた状態コードが2 XXである場合、APIの成功を要求することを説明する.
    もし戻ってきたのがレスリングだったら、そのまま戻ってきます.
    if (type.equals(Response.class)) {
        response = Utils.readBodyToBytesIfNecessary(response);
    
        if (methodInfo.isSynchronous) {
            return response;
        }
        return new ResponseWrapper(response, response);
    }
    もし戻るタイプがResonseではないなら、私達は転換します.
    TypedInput body = response.getBody();
    if (body == null) {
        return new ResponseWrapper(response, null);
    }
    
    ExceptionCatchingTypedInput wrapped = new ExceptionCatchingTypedInput(body);
    try {
        //         wrapped       type
        Object convert = converter.fromBody(wrapped, type);
        if (methodInfo.isSynchronous) {
            return convert;
        }
        return new ResponseWrapper(response, convert);
    } catch (ConversionException e) {
    }
    Responseに戻ったら同期したら、非同期スレッドに戻り、UIスレッドデータの更新を通知します.非同期であれば、Callbackに通知し、直接UIスレッドでデータを更新すれば良い.
    使用時の注意点をまとめます.1.非同期の最後のパラメータはCallbackで、戻りのタイプはvoidです.同期パラメータにはCallbackがありません.戻るタイプはObjectです.2.方法によってのみ注釈されている@FormUrlEncoded、@MultiiPadとパラメータ注釈@Bodyが注釈されている場合、パラメータの中にPOJOパラメータがあります.3.requestのPOJOはCoverter_Body(Object)を介してTypedOutputに変換されました.4.レスポンスはCoverter_from Body(Typed Input、Type)を通じてTyped InputをPOJOに変換したものです.5.@GETまたは@POSTの相対パスは/で始まる必要があります.さもなければ異常を投げます.6.私たちが定義したAPIを要求するインターフェースのパラメータにはnull値がないと、例外をスローします.
    はい、Retrofitフレームに関する分析はこれで終わります.