Flutter aspectd(三)使用

@pragma('vm:entry-point')

在AOT编译中,如果没有被引用到的代码会丢弃掉,而AOP代码是不会被引用,需要使用该方式告诉编译器不要丢弃代码

PointCut

/// Object carrying callsite information and methods which can enable you to
/// call the original implementation.
@pragma('vm:entry-point')
class PointCut {
  /// PointCut default constructor.
  @pragma('vm:entry-point')
  PointCut(this.sourceInfos, this.target, this.function, this.stubKey,
      this.positionalParams, this.namedParams);

  /// Source infomation like file, linenum, etc for a call.
  final Map sourceInfos;

  /// Target where a call is operating on, like x for x.foo().
  final Object target;

  /// Function name for a call, like foo for x.foo().
  final String function;

  /// Unique key which can help the proceed function to distinguish a
  /// mocked call.
  final String stubKey;

  /// Positional parameters for a call.
  final List positionalParams;

  /// Named parameters for a call.
  final Map namedParams;

  /// Unified entrypoint to call a original method,
  /// the method body is generated dynamically when being transformed in
  /// compile time.
  @pragma('vm:entry-point')
  Object proceed() {
    return null;
  }
}

该对象中包含一些我们要调用或执行的代码的信息。包括源代码信息(如库名、文件名、行号等),方法调用对象,方法名,位置参数,命名参数等

proceed()是调用原始方法的入口点,pointcut.proceed()可实现对原始逻辑的调用。原始定义中的proceed方法体只是个空壳,其内容将会被在运行时动态生成

Aspect

/// Annotation indicating whether a class should be taken into consideration
/// when searching for aspectd implementations like AOP.
@pragma('vm:entry-point')
class Aspect {
  /// Aspect default constructor
  const factory Aspect() = Aspect._;

  @pragma('vm:entry-point')
  const Aspect._();
}

用来标记要进行AOP操作的类,方便AOP进行识别和提取,也可以起到开关的作用,如果希望禁掉此段AOP逻辑,移除@Aspect注解即可。

Call

@Aspect()
@pragma("vm:entry-point")
class RegularCallDemo {
  @pragma("vm:entry-point")
  RegularCallDemo();

  @Call("package:example/main.dart", "", "+appInit")
  @pragma("vm:entry-point")
  static dynamic appInit(PointCut pointcut) {
    print('[KWLM1]: Before appInit!');
    dynamic object = pointcut.proceed();
    print('[KWLM1]: After appInit!');
    return object;
  }

  @Call("package:example/main.dart", "MyApp", "+MyApp")
  @pragma("vm:entry-point")
  static dynamic myAppDefine(PointCut pointcut) {
    print('[KWLM2]: MyApp default constructor!');
    return pointcut.proceed();
  }

  @Call("package:example/main.dart", "MyHomePage", "+MyHomePage")
  @pragma("vm:entry-point")
  static dynamic myHomePage(PointCut pointcut) {
    dynamic obj = pointcut.proceed();
    print('[KWLM3]: MyHomePage named constructor!');
    return obj;
  }
}

@Call("package:example/main.dart", "MyApp", "+MyApp")中第一个参数表示的是包名,第二个参数是类名,第三个参数是方法名。其中方法名可以有一个前缀(-或+),-表示的是实例方法。而+表示的是静态方法或类方法

需要注意的是:写的aop方法的类型要和要注入的方法一致,即带有+的,方法前要有static。

Execute

@Aspect()
@pragma("vm:entry-point")
class RegularExecuteDemo {
  @pragma("vm:entry-point")
  RegularExecuteDemo();

  @Execute("package:example/main.dart", "_MyHomePageState", "-_incrementCounter")
  @pragma("vm:entry-point")
  dynamic _incrementCounter(PointCut pointcut) {
    dynamic obj = pointcut.proceed();
    print('[KWLM21]:${pointcut.sourceInfos}:${pointcut.target}:${pointcut.function}!');
    return obj;
  }

  @Execute("dart:math", "Random", "-next.*", isRegex: true)
  @pragma("vm:entry-point")
  static dynamic randomNext(PointCut pointcut) {
    dynamic obj = pointcut.proceed();
    print('[KWLM22]:randomNext!');
    return obj;
  }
}

Call和Execute的区别是插入代码的位置不同,call是插入到调用的地方,而Execute是插入到执行的地方。例如:在main方法中调用say方法,通过Call方式插入的代码会插入到main方法中,而通过Execute方式插入的代码会插入到say方法中。

Inject

@Aspect()
@pragma("vm:entry-point")
class InjectDemo{
 @Inject("package:example/main.dart","","+injectDemo", lineNum:27)
 @pragma("vm:entry-point")
 static void onInjectDemoHook1() {
   print('Aspectd:KWLM51');
 }

 @Inject("package:example/main.dart","C","+fc", lineNum:198)
 @pragma("vm:entry-point")
 static void onInjectDemoHook3() {
   print('Aspectd:KWLM52++++');
 }
}

inject就是往具体的行前插入代码。

如何使修改代码生效

对于不同位置的代码,修改后让其生效需要做的不同。分为example、aspect_impl、aspectd下的lib

example中的代码

当只修改了example中的代码时,可以直接运行就会生效

aspect_impl中的代码

当修改了aspect_impl中的代码时,需要分别在aspect_impl和example中执行flutter clean和flutter pub get之后才能生效。

aspectd下的lib下的代码

当修改了这下面的代码后,需要将flutter_frontend_server目录下的frontend_server.dart.snapshot文件删除掉,然后分别在aspect_impl和example中执行flutter clean和flutter pub get之后才能生效。

当更改flutter版本

需要重新执行如下:

git apply --3way path-for-aspectd-package/0001-aspectd.patch
rm bin/cache/flutter_tools.stamp

通过上一节分析我们知道,git apply之后,flutter源码中会新增aspectd.dart文件,该文件中会判断frontend_server.dart.snapshot是否存在,存在就使用,不存在就通过代码重新生成,所以当我们修改了lib下的代码后需要删掉frontend_server.dart.snapshot。

你可能感兴趣的:(Flutter aspectd(三)使用)