Retrofit 2動的エージェントの解析

12971 ワード

Retrofit 2は現在ますます主流が安定しており、ついに他のネットワークライブラリを完全に捨ててOkHttp 3を依存として使用し、機能もさらにプラグイン化されている.動的エージェントという言葉をよく耳にしますが、今は知識レベルが高いので、分析してみましょう.ǎng)読者.
Retrofitを1つの言葉で要約すると、「パッチワーク」が最も正確で、以前のサーバプログラミングにおける動的エージェント技術を参考にして、インタフェースを通じて実行時にバイトコードを生成します.次に注釈によってHTTP要求を組み立てる.最後にOkHttpをパッケージし,Rx,スレッドに対するadaptionを実現した.
本文は主にプロセス(practice)であり、チュートリアル(tutorial)ではないので、コードの実践をダウンロードしてほしい.
本論文では,retrofit:2.0.0-beta3jdk1.8.0_05に基づいて解析を行った.
君は学ぶ
エージェントの概要定義インタフェースは、動的エージェントの使用前と使用後の比較である.
Retrofitはどのようにinterface(方法、注釈と汎用)からOkhttpリクエストオブジェクトへの変換(埋め込まれるピット)を完了するか.
RetrifitはどのようにOkhttpを使ってそしてコールバックする時スレッドの切り替えを行って、すでに別の文章を書いてを書きました
ツールの準備
バイトコードを解析する逆コンパイルツールバイトコードに保存されたバイトコード保存ツールクラスは、これを使用してclassファイルをエクスポートできます.もちろん、jvm
 ProxyUtils.saveProxyClass("123.class", "Github", new Class[] { GitHub.class });
でも構いません.
Retrofit 2のソースコードは、mavenベースであることに注意し、Ideaで
 git clone --depth=1 https://github.com/square/retrofit.git
にインポートすることが望ましい.
Retrofitの使い方はまだ分かりませんか?まずRetrofit解析JSONデータを見てみましょう1.エージェントとは
エージェントは設計モードであり、静的エージェントと動的エージェントに分けられる.
エージェントが存在しない前に、呼び出しは次のようになります.
呼び出し者ビジネスの詳細使用すると、呼び出しの順序は次のとおりです.
呼び出し者ビジネスロジック(一般的に抽象メソッドまたはインタフェース)ビジネスの詳細中間にエージェントを追加することで、ビジネスロジックと実装の詳細を分離し、上位開発者を便利にします.
1.1. 例を挙げる
1.1.1. 手続きをする.
私は関係部門に行ってたくさんの手続きをしなければなりませんが、走り回りたくないので、仲介業者を見つけました.
  <--               -->   
   <--             -->     

エージェントを通じて、私はランニングの詳細を理解する必要がなくて、業務を完成することができて、これはエージェントの長所で、業務に向かって詳細を無視します.もちろんこれは代価があり(例えばランニング代を払う必要がある)、符号化ではエージェントクラスが符号化量を増やした.
1.1.2. インターネットに接続する
海外のサイトにアクセスする必要がありますが、速度が遅いので、サービス業者を見つけました.
  <--     pac -->      
      <---->   

エージェントを通じて、私はpacをコピーするだけで、ファイアウォールとどのように戦うかを考えなくても、ビジネスを完成することができます.同じように、これは代価が必要です.
1.2. 理論
1.2.1. スタティツクエージェント
ユーザ によって符号化されるか、または において自動的に生成される.
AppCompatActivityのAppCompatDelegate抽象クラスは静的エージェントであり、抽象クラスのインスタンス化は静的文ブロックstatic{}領域でバージョン番号に基づいて実現され、このように書くことでActivity自体の業務が冗長すぎることを回避し、抽象クラス(インタフェースでもある)と実装クラスを分離することができ、上層開発者は具体的な実装にかかわらず業務に専念することができる.
AppCompatActivity -    -     

AndroidでのIPCアクセスでは、プロセス間アクセス方法が必要な場合はAIDL通信を使用する必要があり、呼び出し元がリモートメソッドを呼び出すと、Stubクラス、すなわちリモートプロセスの杭が呼び出され、その後、この杭はASHMEM( )を介して転送され、最後の方法の実装はリモートサーバにある.
     - Stub  ( AIDL  ) -     

上のstub,delegateはいずれも静的エージェントであり,それ自体が抽象クラスであり,呼び出し者にインタフェース向けのビジネスプログラミングを実現させた.
1.2.2. ダイナミックエージェント
動的エージェントはjavaにおけるバイトコード生成技術である.インタフェースによってエージェントクラスが生成され、エージェントクラスの実装はInvocationHandlerに具体的な実装として渡される.
Retrofitでは複雑なネットワーク要求を簡略化するために,動的エージェントによる解析とパッチワークの処理を行う.
あるメソッドに実行中に権限制御またはlogを追加することを望んでいる場合、動的エージェント生成後にhandlerに余分なトラフィックを装飾し、従来のメソッドを保存してトラフィック自体(すなわちAOPプログラミング)に専念することができます.例をここに示します.
360のプラグイン化テクノロジーの実装など、実行時に「隠す」
動的エージェントのプロセス
動的エージェントはjavaの反射特性を利用して、書きやすいインタフェースと注釈を通じて、ユーザーが自動的に実際のオブジェクトを生成することを助けて、インタフェースの中の方法の本当の呼び出しはInvocationHandlerに渡します.
Input
interface


Proxy.newProxyInstance
インタフェースを包装し、内部方法はhandlerによって委任される.


Output
interface(with InvocationHandler)
ユーザが生成インタフェースのメソッドを呼び出すと、InvocationHandlerのinvokeメソッドが実際に呼び出される
動的エージェント前のインタフェースファイルを,エージェント後の実行で生成された逆コンパイル後のファイルと比較する.
生成前
公式のサンプルインタフェース
public interface GitHub {
  @GET("/repos/{owner}/{repo}/contributors") Call<List<Contributor>> contributors( @Path("owner") String owner, @Path("repo") String repo);
}

公式インタフェースの呼び出し
Call<List<Contributor>> call = github.contributors("square", "retrofit");

エージェント生成中
Retrofitのcreateにパラメータとして入力され、次の方法を呼び出してインタフェース情報を読み出し、実行時にバイトコードを生成しclassloaderでロードします.
Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service },
  new InvocationHandler() {
    private final Platform platform = Platform.get();

    @Override public Object invoke(Object proxy, Method method, Object... args)
         .....
         //          OkHttp call
      return loadMethodHandler(method).invoke(args);
    }
  });
}

生成後
これは動的エージェント生成後にツールdumpで出て逆コンパイルされたclassである(以下,Objectが持つ方法,および大量の異常捕捉を省略する)
public final class Github extends Proxy implements SimpleService.GitHub {
  private static Method m0;//   hashCode  
  private static Method m1;//   equals  
  private static Method m2;//   toString
  private static Method m3;//         

  static {
    try {
    //  Object        
      m0,m1,m2 = ...
      //        
      m3 = Class.forName("com.example.retrofit.SimpleService$GitHub")
          .getMethod("contributors",
              new Class[] { Class.forName("java.lang.String"), Class.forName("java.lang.String") });
    } catch (Throwable e) {
      throw new NoSuchMethodError(e.getMessage());
    } catch (Throwable e2) {
      throw new NoClassDefFoundError(e2.getMessage());
    }
  }

  public Github(InvocationHandler invocationHandler) {
    super(invocationHandler);
  }

    //               "square", "retrofit"
  public final Call contributors(String str, String str2) {
    RuntimeException e;
    try {
    //this.h InvocationHandler,        Handler
    //          handler `invoke`  
      return (Call) this.h.invoke(this, m3, new Object[] { str, str2 });
    } catch (Throwable e) {
      throw e;
    } 
  }

  ....Object       ...
}

InvocationHandlerが最も重要な修飾であることがわかり、Handlerのinvokeは実際に処理されていますが、これらは私たちが書き直したもので、具体的にどのように処理したのかについては、これらのピットは後で補完されます.
ダイナミックエージェントの原理
エージェント生成の過程はsunパケット中の閉ソース法を用いており,逆コンパイルで大まかに見ると,まずインタフェースをProxyGenerator.generateProxyClass()でClassファイルの仕様に従ってパッチワークしてbyte[]バイトコードを生成し,次にnative法defineClass0()でオブジェクトに変換し,動的エージェントが本質的に大量の を生成する過程であることが分かる.動的エージェントは、静的エージェントと比較して、XXXImplなどのエージェントクラスの作成作業量を削減できます.
ダイナミックエージェント(反射)はパフォーマンスを低下させましたか?
いいえ、テストを受けました.retrofit構造は動的エージェントと加算された時間は0.65 msであり,16 msに対してネットワーク要求に比べて全く1桁ではない.もしあなたが強迫症であれば、単例化することができます.
Refference
https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/
http://a.codekk.com/detail/Android/Caij/%E5%85%AC%E5%85%B1%E6%8A%80%E6%9C%AF%E7%82%B9%E4%B9%8B%20Java%20%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86
http://docs.oracle.com/javase/tutorial/reflect/
http://tutorials.jenkov.com/java-reflection/generics.html
http://www.ibm.com/developerworks/cn/java/j-jtp08305.html
http://www.jianshu.com/p/a56c61da55dd