记录对flutter aspectd的优化与flutter2.5的适配修改

1,126 阅读5分钟

aspectd是咸鱼团队发布的一个针对Flutter的aop框架,在实际应用中可以起到非常好的作用,比如日志打点. 但是随着flutter 2.5的发布,这个项目并没有跟上适配进度,或许咸鱼团队也已经放弃这个项目了(难道真的是传说的kpi项目吗?).

我们的项目中也用到了aspectd,主要用于日志打点方面,但是它没有适配flutter2.5让我很难受,于是决定自己动手丰衣足食.

之前没有深入去了解aspectd的实现原理,基本是拿来就用的,现在仔细研究终于发现为什么咸鱼团队没有对它进行适配了,随着dart 2.14的发布,他们屏蔽了MethodInvocation 和 PropertyGet 的api,并不是像以前一样改改.看了一下源码,发现可以用InstanceInvocationInstanceGet 代替,不过修改难度还是挺大的.

其实在用aspectd之前,我对aspectd的设计就抱着很多的疑问:

  1. aspectd为什么要对flutter_tools的代码进行修改呢? Flutter的代码编译是通过frontend_server.dart.snapshot这个dart程序进行的,我们只需要重新编译出可以支持aop功能的frontend_server.dart.snapshot,何必去修改flutter_tools?这样反而对以后flutter的版本升级造成不便

  2. 为什么要有aspect_impl这样的奇特设计?直接将注入代码放在主程序代码里,或者以plugin的方式被主程序引用不就好了吗?何必这么多此一举?

  3. aspectd不支持hot reloadhot restart,每次修改了注入代码都需要冷启动,真的是带来了极大的不方便.是技术原因无法实现吗?

随后又研究了aspect的代码,带来了更多的疑问:

  1. 为什么要hook package:vm/target/flutter.dart 这个类?实际上FrontendCompiler这个类已经添加了ProgramTransformer可以让我们对代码进行转换.

  2. Execute的功能实现起来是不是太过于繁琐了?每次注入都在PointCut这个类里面注入一个以aop_stub_命名新方法然后将注入的逻辑放在这个方法里.为什么要做这么复杂的逻辑实现呢?直接修改源代码中的functionNode.body让其调用注入方法的实现不就好了吗?

带着上面的几个疑问,我开始对aspectd进行了修改:

  1. 去掉了aspect_impl等设计
  2. 去掉了flutter_tools相关的代码实现
  3. 由于我的项目里暂时只用到了Execute的相关逻辑,暂时没有添加Inject和Call
  4. 优化了Execute的实现
  5. 修改了AspectTransformer的入口
  6. 去掉了注入的标签,用系统自带的pragma

以下为注入Demo代码:

@pragma('vm:entry-point')
  @pragma("aopd:inject", {
    "importUri": "package:example/main.dart",
    "clsName": "_MyHomePageState",
    "methodName": "-_test2",
    "isRegex": false
  })
  //必须是static,必然不起作用
  static Future<bool> _test2(
      Object target,
      String functionName,
      List<dynamic> positionalParams,
      Map<String, dynamic> namedParams,
      Function proceed) {
    print(
        "[Inject] $functionName start ${positionalParams[0]} ${positionalParams[1]} ${namedParams["key3"]}");
    return Function.apply(
        proceed, positionalParams, _transToNamedParams(namedParams));
  }

以下为部分修改代码逻辑: frontend_server.dart.snapshot入口设计:

import 'dart:io';
import 'package:args/args.dart';

import 'package:frontend_server/frontend_server.dart' as frontend;
import 'package:vm/target/flutter.dart';
import 'transformer.dart';

Future<void> main(List<String> args) async {
  try {
    ArgResults options = frontend.argParser.parse(args);
    frontend.FrontendCompiler compiler = frontend.FrontendCompiler(stdout,
        printerFactory: frontend.BinaryPrinterFactory(),
        //添加注入逻辑
        transformer: AspectAopTransformer(),
        unsafePackageSerialization: options['unsafe-package-serialization'],
        incrementalSerialization: options['incremental-serialization'],
        useDebuggerModuleNames: options['debugger-module-names'],
        emitDebugMetadata: options['experimental-emit-debug-metadata'],
        emitDebugSymbols: options['emit-debug-symbols']);
    //FlutterTarget.flutterProgramTransformer = AspectAopTransformer();
    final int exitCode = await frontend.starter(args, compiler: compiler);
    if (exitCode != 0) {
      exit(exitCode);
    }
  } catch (error) {
    print('ERROR: $error\n');
    print(frontend.usage);
    return;
  }
}

以下为注入的主要代码实现:


//判断该Procedure是否已经被注入过,如果被注入过则应该略过
bool isInjectBlock(
      Block? block, Procedure injectProcedure, Procedure originProcedure) {
    if (block == null) {
      return false;
    }
    if (block.statements.length != 1) {
      return false;
    }
    Expression? expression;
    if (block.statements.first is ReturnStatement) {
      expression = (block.statements.first as ReturnStatement).expression;
    } else if (block.statements.first is ExpressionStatement) {
      expression = (block.statements.first as ExpressionStatement).expression;
    } else {
      return false;
    }

    if (expression is StaticInvocation) {
      if (expression.arguments.positional.length != 5) {
        print(
            "[AspectAopTransformer] arguments is not 5 so return ${expression.arguments.positional.length}");
        return false;
      }
      if (expression.arguments.positional[1] is StringLiteral &&
          expression.arguments.positional[2] is ListLiteral &&
          expression.arguments.positional[3] is MapLiteral &&
          expression.arguments.positional[4] is FunctionExpression) {
        if ((expression.arguments.positional[1] as StringLiteral).value ==
            originProcedure.name.text) {
          return true;
        }
        return false;
      }
      return false;
    } else {
      print(
          "[AspectAopTransformer] expression is not StaticInvocation so return ");
      return false;
    }
  }

//对方法进行注入操作
void transformInstanceMethodProcedure(
      Library? originalLibrary, AopItem aopItem, Procedure originalProcedure) {
    if (AopUtils.manipulatedProcedureSet.contains(originalProcedure)) {
      //如果已经处理完过,就直接返回
      return;
    }
    AopUtils.manipulatedProcedureSet.add(originalProcedure);
    //FunctionNode 中定义了方法的参数和、body、返回值类型等
    final FunctionNode functionNode = originalProcedure.function;

    //当前方法的处理逻辑
    Block? bodyStatements = functionNode.body as Block?;
    if (bodyStatements == null) {
      print(
          "[AspectAopTransformer] bodyStatements ${originalProcedure.name.toString()} is null so return");
      return;
    }
    //bodyStatements = getInjectBlock(bodyStatements, originalProcedure);
    //检查当前方法是否已经被注入
    if (isInjectBlock(
        bodyStatements, aopItem.aopMember as Procedure, originalProcedure)) {
      print(
          "[AspectAopTransformer] isInjectBlock1 ${originalProcedure.name.toString()} so return");
      return;
    }
    //是否需要返回
    final bool shouldReturn =
        !(originalProcedure.function.returnType is VoidType);
    //将aspect相关的包导入源码文件
    AopUtils.insertLibraryDependency(
        originalLibrary!, aopItem.aopMember.parent!.parent as Library?);
    //获取原方法的参数
    Arguments originArguments =
        AopUtils.argumentsFromFunctionNode(functionNode);

    String functionName = originalProcedure.name.text;
    //创建调用静态方法的参数
    final Arguments redirectArguments = Arguments.empty();

    //target
    if (originalProcedure.isStatic) {
      redirectArguments.positional.add(StringLiteral(functionName));
    } else {
      redirectArguments.positional.add(ThisExpression());
    }

    //functionName
    redirectArguments.positional.add(StringLiteral(functionName));
    //positionalParams
    redirectArguments.positional.add(ListLiteral(originArguments.positional));
    //namedParams
    final List<MapLiteralEntry> entries = <MapLiteralEntry>[];
    for (NamedExpression namedExpression in originArguments.named) {
      //这里用SymbolLiteral貌似无效,退而求其次用StringLiteral
      entries.add(MapLiteralEntry(
          StringLiteral(namedExpression.name), namedExpression.value));
    }
    redirectArguments.positional.add(MapLiteral(entries));
    //proceed
    final FunctionNode newFunctionNode = FunctionNode(bodyStatements,
        typeParameters: AopUtils.deepCopyASTNodes<TypeParameter>(
            functionNode.typeParameters),
        positionalParameters: functionNode.positionalParameters,
        namedParameters: functionNode.namedParameters,
        requiredParameterCount: functionNode.requiredParameterCount,
        returnType: shouldReturn
            ? AopUtils.deepCopyASTNode(functionNode.returnType)
            : const VoidType(),
        asyncMarker: functionNode.asyncMarker,
        dartAsyncMarker: functionNode.dartAsyncMarker);
    FunctionExpression newFunctionExpression =
        FunctionExpression(newFunctionNode);
    redirectArguments.positional.add(newFunctionExpression);
    //创建静态方法调用
    final StaticInvocation callExpression =
        StaticInvocation(aopItem.aopMember as Procedure, redirectArguments);
    Block block = AopUtils.createProcedureBodyWithExpression(
        callExpression, shouldReturn);
    //将原本的处理流程替换成注入后的流程
    functionNode.body = block;
    print(
        "[AspectAopTransformer] inject ${originalProcedure.name.toString()} success");
  }

修改完成后发现hot reload,hot restart意外的能够用了,开心

以上实现已经提交到github上:github.com/lancexin/as…

如果不想自己编译,但是又想直接拿来用,可以执行下载编译好的frontend_server.dart.snapshot,覆盖 flutter_macos_stable/bin/cache/artifacts/engine/darwin-x64/frontend_server.dart.snapshot就可以直接拿来用了.

后记

发现这么做时AspectTransformer是在代码优化后执行的,如果是在release下,会发现namedParams会优化成positionalParams,而且部分constant参数并不会传入,因此AspectTransformer执行在aot的模式下必须是在优化前触发,如果这么做的话不得不修改vm 和front_end中的部分实现逻辑了:

在dart-sdk中 package:frontend_server/frontend_server.dart 做以下修改:


export 'package:vm/kernel_front_end.dart';  


//将ProgramTransformer接口移动到 package:vm/kernel_front_end.dart中
//abstract class ProgramTransformer {
//  void transform(Component component);
//}

...
results = await _runWithPrintRedirection(
        () => compileToKernel(_mainSource, compilerOptions,
            includePlatform: options['link-platform'],
            deleteToStringPackageUris: options['delete-tostring-package-uri'],
            aot: options['aot'],
            useGlobalTypeFlowAnalysis: options['tfa'],
            environmentDefines: environmentDefines,
            enableAsserts: options['enable-asserts'],
            useProtobufTreeShakerV2: options['protobuf-tree-shaker-v2'],
            minimalKernel: options['minimal-kernel'],
            treeShakeWriteOnlyFields: options['tree-shake-write-only-fields'],
            fromDillFile: options['from-dill'],
            //调用compileToKernel时传入 transformer
            transformer: transformer),
      );
...


if (results.component != null) {
   //只有增量热更新才触发transformer
  if (options['incremental']) {
    transformer?.transform(results.component);
}

在dart-sdk中 package:fpackage:vm/kernel_front_end.dart 做以下修改:

//修改compileToKernel增添transformer逻辑
Future<KernelCompilationResults> compileToKernel(
    Uri source, CompilerOptions options,
    {bool includePlatform: false,
    List<String> deleteToStringPackageUris: const <String>[],
    bool aot: false,
    bool useGlobalTypeFlowAnalysis: false,
    required Map<String, String> environmentDefines,
    bool enableAsserts: true,
    bool useProtobufTreeShakerV2: false,
    bool minimalKernel: false,
    bool treeShakeWriteOnlyFields: false,
    String? fromDillFile: null,
    ProgramTransformer? transformer}) async {
    
   ...
    
  if (deleteToStringPackageUris.isNotEmpty && component != null) {
    to_string_transformer.transformComponent(
        component, deleteToStringPackageUris);
  }
  //在aot优化前执行transform
  if (component != null) {
    transformer?.transform(component);
  }

  // Run global transformations only if component is correct.
  if ((aot || minimalKernel) && component != null) {
    await runGlobalTransformations(target, component, useGlobalTypeFlowAnalysis,
        enableAsserts, useProtobufTreeShakerV2, errorDetector,
        minimalKernel: minimalKernel,
        treeShakeWriteOnlyFields: treeShakeWriteOnlyFields);

    if (minimalKernel) {
      // compiledSources is component.uriToSource.keys.
      // Make a copy of compiledSources to detach it from
      // component.uriToSource which is cleared below.
      compiledSources = compiledSources!.toList();

      component.metadata.clear();
      component.uriToSource.clear();
    }
  }
  ...
}