Android AOPまとめ

11612 ワード

一、AOP紹介
1.1 AOPとは
AOP,AspectOriented Programming面取りプログラミング
OOP,Object-orientedprogrammingオブジェクト向けプログラミング
AOPとOOPは異なるプログラミング思想である.OOPは高凝集,低結合,パッケージングを強調した.
機能をモジュール化し,対象化することを提唱している.
プリコンパイル方式とランタイムダイナミックエージェントにより、ソースコードを変更せずにプログラムに機能を動的に統一的に追加できる技術です.AOPは実際にはGoF設計モードの継続であり、設計モードがこつこつと追求しているのは呼び出し者と被呼び出し者との間のデカップリングであり、コードの柔軟性と拡張性を高め、AOPもこのような目標の実現であると言える.
     
1.2 AOP用途
ログ記録、パフォーマンス統計、セキュリティ制御、トランザクション、例外処理などのコードをビジネスロジックコードから区分し、これらの動作を分離することで、ビジネスロジックを指導しない方法に独立し、これらの動作を変更する際にビジネスロジックに影響を与えないコードを望んでいます.
 
1.3 AOP方式比較選択
 
Hookタイミング
Androidでシーンを適用
メリット
欠点
Dexposed
ランタイム動的hook
スライドスムーズモニタリングイベントモニタリング熱修復
システム通信を動的に監視し、通信できる様々な方法.
5.0以上の携帯電話はサポートされていません
Xposed
ランタイム動的hook
同Dexposed
システム通信を動的に監視し、通信できる様々な方法.
5.0以上の携帯電話の必須rootはサポートされていません
Java Proxy
ランタイム動的hook
hookとシステム通信インタフェース例えばプラグインsdk
JavaネイティブAPI、互換性の問題はありません
Interfaceがあるクラスはhookのみです
AspactJ
コンパイル時のコードの変更
統計メソッド実行時間メソッド前後注入ロジック
SprintオープンソースのAOPフレームワークは、強力な機能を備えています.注釈が多い.基本的にはすべてのコンパイル時の注入方式が含まれています
118 Kのjarを導入する必要があります
ASM
コンパイル時のコードの変更
同AspactJ
バイトコード操作ライブラリ、
自分で注釈を書き、スクリプトをコンパイルする必要があります.バイトコード挿入の作成が大変です
Javassit
コンパイル時のコードの変更
同AspactJ
Java反射に基づくバイトコード操作クラスライブラリ.ASMと比較して、作成が簡単です
ASMと比較してクラスを変更する場合、実行時間が長い
 
Dexposed,Xposed:原理,適用シーン,demo
 
DexposedはXposedに基づいて開発されたhook独自のappのライブラリです.宝を洗って源を開く.
原理:http://www.zhaoxiaodan.com/android/Android-Hook(1)-dexposed%E5%8E%9F%E7%90%86.html
http://blog.csdn.net/yueqian_scut/article/details/50939034
元のjavaメソッドのタイプをnativeに変更してjava関数の呼び出しをnativeレイヤに移動し、nativeレイヤではdvmの各種関数でMethodのポインタとオブジェクトを操作して関数フローを制御します.
Gitアドレス:https://github.com/alibaba/dexposed                  
Runtime
Android Version
Support
Dalvik
2.2
Not Test
Dalvik
2.3
Yes
Dalvik
3.0
No
Dalvik
4.0-4.4
Yes
ART
5.0
Testing
ART
5.1
No
ART
M
No
 
 
最近メンテナンスしていないようです.Github上のコードは更新されていません
使用方法:
compile'com.taobao.android:dexposed:0.1.7@aar'
 
コードの例:
/**
 * doFrame    hook
 */
  public static class FrameMethodHook extends XC_MethodHook {
  
    private long startTime = 0;
    private int frameNo = -1;
  
    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
        startTime = SystemClock.elapsedRealtime();
        frameNo = (Integer) param.args[1];
    }
  
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        long coastTime = SystemClock.elapsedRealtime() - startTime;
        Log.i(TAG, "frameNo:" + frameNo + ", coastTime:" + coastTime);
    }
}
  
  
  private XC_MethodHook.Unhook frameMethodunHook = null;
  
public void hookDoFrame() {
//   doFrame  ,  MethodHook
    FrameMethodHook frameMethodHook = new FrameMethodHook();
    frameMethodunHook = DexposedBridge.findAndHookMethod(Choreographer.class, "doFrame", long.class, int.class, frameMethodHook);
  }
  
  public void unHookDoFrame() {
    if (frameMethodunHook != null) {
        frameMethodunHook.unhook();
    }
}

実行後log:
03-10 16:43:59.440 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13831, coastTime:153
03-10 16:43:59.520 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13832, coastTime:79
03-10 16:43:59.595 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13846, coastTime:60
03-10 16:44:00.540 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13870, coastTime:38
03-10 16:44:00.580 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13872, coastTime:38
03-10 16:44:00.585 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13875, coastTime:3
03-10 16:44:00.605 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13876, coastTime:4
03-10 16:44:00.620 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13877, coastTime:3
03-10 16:44:00.635 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13878, coastTime:2
03-10 16:44:00.655 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13879, coastTime:2
03-10 16:44:00.670 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13880, coastTime:3
03-10 16:44:00.685 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13881, coastTime:2
03-10 16:44:00.705 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13882, coastTime:3
03-10 16:44:00.720 13821-13821/com.baidu.test.aop I/DexposedManager:frameNo:13883, coastTime:2

用途:性能モニタリング、スライドスムーズのモニタリング、view描画時間など.
aspactJ,原理,応用シーン,demo
sprintが提供するAOPフレームワークは、強力な機能を備えています.欠点は、118 KBのjarをAPKに統合する必要があることです.
コンパイル時にブロックすることもできます.コンパイル時にclassバイトコードを変更します.classファイルを書き直します.
使用方法:
classPathの導入
classpath'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:1.0.8'

ふえる
jar
compile'org.aspectj:aspectjrt:1.8.9'

gradleプラグインを追加
applyplugin: 'android-aspectjx'
この3つを追加するとaspectjが使用できます
AspectTestファイルを新規作成します.クラスの最初に@Aspect注記を追加します.コンパイラはコンパイル時に自動的に解析し,AspectJクラスのコードを呼び出す必要はない.
@Aspect
public class AspectTest {
    private static final StringTAG ="AspectTest";

    @Before("execution(*android.app.Activity.on**(..))")
    public void onActivityMethodBefore(JoinPoint joinPoint)throws Throwable {
        String key = joinPoint.getSignature().toString();
        Log.d(TAG,"onActivityMethodBefore: " + key);
    }
}

このテストコードは、Activityを継承するすべてのクラスでonXXXメソッドに実行する前に、ブロックを追加することを意味します.このコードを挿入します.
例えばActivityはこうです.
publicclass AnimationScaleActivityextendsFragmentActivity {

   @Override
   protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.an_scale);
      ImageView imgv=(ImageView)findViewById(R.id.img);
      AnimationalphaAnimation=AnimationUtils.loadAnimation(this,R.anim.scale);
      imgv.startAnimation(alphaAnimation);
       Intent intent = newIntent();
        intent.putExtra("rs","success");
        setResult(2,intent);
   }
}

コンパイルが終わったら、逆コンパイルします.
ハイライト部分は、自動的に生成されるコードです.
用途:コンパイルは切り込みです.ログ記録、打点統計などの仕事.
JAvaの動的エージェントメカニズムdemo
純java API.主なAPIクラスは次のとおりです.
Proxy.newProxyInstance
public static Object newProxyInstance(ClassLoader loader,Class>[]interfaces,InvocationHandler h)throws IllegalArgumentExceptionは、指定したインタフェースのプロキシクラスインスタンスを返します.このインタフェースは、指定した呼び出しハンドラにメソッド呼び出しを割り当てることができる.このメソッドは、Proxy.getProxyClass(loader, interfaces).          getConstructor(new Class[] { InvocationHandler.class }).          newInstance(new Object[] { handler });
 
  
Proxy.newProxyInstance    IllegalArgumentException,    Proxy.getProxyClass   。

パラメータ:loader-プロキシクラスを定義するクラスローダinterfaces-エージェントクラスが実装するインタフェースのリストh-メソッド呼び出しを割り当てる呼び出しハンドラ
戻り値:
指定したクラス・ローダによって定義され、指定したインタフェースを実装するプロキシ・クラスを持つ指定した呼び出しハンドラのプロキシ・インスタンス
放出:
IllegalArgumentException-getProxyClassに渡されたパラメータの制限に違反した場合
NullPointerException-interfaces配列パラメータまたはその任意の要素がnullである場合、または呼び出しハンドラhnullである場合
詳しくは、ブログをもう一度書きます.JDKにおけるproxy動的エージェントの原理解析
実行時hook.ただしhookインタフェースしかありません.Androidでの用途は、Android Framework層のインタフェースをhookすることができます.
JAvassit原理、適用シーン、demo
Javassistは、Javaバイトコードを分析、編集、作成するオープンソースクラスライブラリです.
使用方法は次のとおりです.
依存ライブラリの導入
compile 'javassist:javassist:3.12.0.GA'

 
例:メソッドの実行時間を記録します.印刷します.
注入するコードを宣言するには、次の手順に従います.
private static String injectStr = "System.out.println(\"Test Javassist Inject \" ); ";

private static String fieldT1Str = "private int t1;";
private static String fieldT2Str = "private int t2;";
private static String injectTimeBefore = "t1 = System.currentTimeMillis();";
private static String injectTimeAfter = "t2 = System.currentTimeMillis();
" + " long t = t2-t1;
" + " System.out.println(\"ActivityTime\"+ this.toString()+\", oncreate \" + t);";

デフォルトのClassPoolの取得
ClassPool pool = ClassPool.getDefault()
pool.appendClassPath(path); // path class    
public static void injectClass(String topPath, File file, String packageName, String methodName) {
        try {
            String filePath = file.getAbsolutePath();
            System.out.println("filePath:" + filePath);
            //       class  ,           class  ,      
            if (filePath.endsWith(".class")
                    && !filePath.contains("R.class")
                    && !filePath.contains("BuildConfig.class")
                    && !filePath.contains("$")) {
                int index = filePath.indexOf(packageName);
                boolean isPackageClass = index != -1; //        class
                if (isPackageClass) {
                    int end = filePath.length() - 6;// .class = 6
                    String className = filePath.substring(index, end)
                            .replace('\\', '.').replace('/', '.');
                    System.out.println("className:" + className);

                    //     class  
                    CtClass c = pool.getCtClass(className);
                    if (c.isFrozen()) {
                        c.defrost();
                    }
                    CtMethod ctMethod = c.getDeclaredMethod(methodName);
                    if (ctMethod != null) {
                        c.addField(CtField.make(fieldT1Str, c));
                        c.addField(CtField.make(fieldT2Str, c));
                        System.out.println("injectActivity");
                        ctMethod.insertBefore(injectTimeBefore);
                        ctMethod.insertAfter(injectTimeAfter);
                    }

                    c.writeFile(topPath);
                    c.detach();

                }
            }
        } catch (Exception e) {
            e.printStackTrace();

        }
    }

書き方は簡単です.APIもわかりやすいです.Java反射のAPIに似ています.
実行時間:aop time:712
元の方法:
protected void onCreate(Bundle savedInstanceState) {
   super.onCreate(savedInstanceState);
   setContentView(R.layout.an_scale);
  }

逆コンパイルの方法:
protected void onCreate(BundlesavedInstanceState) {
       this.t1 = (int) System.currentTimeMillis();
       super.onCreate(savedInstanceState);
       this.t2 = (int) System.currentTimeMillis();
       System.out.println(newStringBuffer().append("ActivityTime").append(toString()).append(",oncreate ").append((long) (this.t2 - this.t1)).toString());
}

利点:使いやすい.APKにサードパーティjarを導入する必要はありません.導入したjarはコンパイル時にgradleプラグインで使用されます.
ASM原理,応用シーン,demo
ASMはjavaバイトコードレベルに基づくコード分析と修正ツールである.ソースコードを提供する必要がなく、アプリケーションに必要なdebugコードを埋め込み、API性能分析を適用することができます.ASMは、バイナリclassファイルを直接生成したり、クラスがJVMに追加される前にクラスの動作を動的に変更したりすることができます.
詳しくは後述する.AndroidではASMを使い、Activityライフサイクルの打点統計をとる.