解决 flutter module 中 .android 和 .ios 目录不被覆盖的问题

1,984 阅读3分钟

在对 flutter module 进行修改的时候,不知道为什么,会发现 .android 目录下和 .ios 目录下的文件会被修改覆盖掉;后来发现,只要我们变动 pubspec.yaml 的文件, 然后执行命令 flutter packages get,就会重新从flutter 模板中替换 .android.ios 目录; 因为我们的 .android目录下有自己定义 gradle 脚本(主要为了解决打包aar的问题);就不希望这个 gradle 被覆盖;

解决方法

  1. 在 flutter-sdk 中修改模板,把自己写好的 gradle 放到模板中;
  2. 找到执行 flutter packages get 背后的逻辑,通过修改逻辑代码,不去覆盖现有的代码;

替换模板的目录

所有的模板目录都在Flutter_HOME/packages/flutter_tools/templates 下面:

以上红色箭头的地方,是我替换和添加的模板代码;ios 应该也可以找到相应的模板进行添加和修改;

查看 flutter 的 shell 脚本命令

1. flutter 命令的开始

因为是 flutter 命令所以我们可以去看下 flutter 命令的源码;用文本编辑器打开编辑 FLUTTER_HOME/bin/flutter 文件;最后一行;

"$DART" --packages="$FLUTTER_TOOLS_DIR/.packages" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@"

为了方便观察打印(echo)一下这个命令:

echo "$DART" --packages="$FLUTTER_TOOLS_DIR/.packages" $FLUTTER_TOOL_ARGS "$SNAPSHOT_PATH" "$@" 

执行命令 flutter --no-color packages get得到如下:

/Users/xxxx/flutter/bin/cache/dart-sdk/bin/dart --packages=/Users/xxxx/flutter/packages/flutter_tools/.packages /Users/xxxx/flutter/bin/cache/flutter_tools.snapshot --no-color packages get

很明显他是根据 flutter_tools.snapshot 这个文件进行执行的;参数是 --no-color packages get; 那么 flutter_tools.snapshot 这个二进制文件是怎么生成的呢?

2. flutter_tools.snapshot 文件的生成

可以继续查看一下 bin/flutter 这个文件;

"$DART" $FLUTTER_TOOL_ARGS --snapshot="$SNAPSHOT_PATH" --packages="$FLUTTER_TOOLS_DIR/.packages" "$SCRIPT_PATH"

这个命令主要是用来编译flutter_tools.snapshot 的,我们可以打印(echo)一下这个命令; 修改一下 shell 脚本,让它进入 if 语句(怎么修改可以看下第3段)里面;打印出来的结果如下;

/Users/xxxx/flutter/bin/cache/dart-sdk/bin/dart --snapshot=/Users/xxxx/flutter/bin/cache/flutter_tools.snapshot --packages=/Users/xxx/flutter/packages/flutter_tools/.packages /Users/xxxx/flutter/packages/flutter_tools/bin/flutter_tools.dart

从上面的命令我们可以看出来所有的源码主要了来自 /Users/xxxx/flutter/packages/flutter_tools 这里目录里面,进去看一下,都是dart 源码;

3. 稍微看下需要编译 flutter_tools.snapshot 的条件是什么

if [[ 
	! -f "$SNAPSHOT_PATH" 
	|| ! -s "$STAMP_PATH" 
	|| "$(cat "$STAMP_PATH")" != "$revision" 
	|| "$FLUTTER_TOOLS_DIR/pubspec.yaml" -nt "$FLUTTER_TOOLS_DIR/pubspec.lock" 
]]; then
	echo Building flutter tool...
 fi

从上面来看,只要满足这里4个条件其中一个,就会去编译 flutter_tools 生成一个 flutter_tools.snapshot;

  • flutter_tools.snapshot 文件不存在
  • STAMP_PATH 这个文件的 size 为0, 主要用来缓存一个 git commit
  • 如果被缓存的 git commit 和 revision 不相同也会触发,revision = git rev-parse HEAD
  • 如果 pubspec.yaml 文件最后修改时间大于pubspec.lock 文件 (nt: new then)

经过以上3 点,接下来,我们可以去看下 flutter_tools 的源码了;

查看 flutter_tools 的源码

我们把源码导入 Android studio 查看更加方便;main 方法从 flutter_tools/bin/flutter_tools.dart 文件开始;这个类非常简单:

import 'package:flutter_tools/executable.dart' as executable;

void main(List<String> args) {
  executable.main(args);
}

所以,所有的开始应该在 executeable.dart 里面;里面主要是把所有的命令都封装成一对象,然后放到一个数组里面注册,因为我们主要是观察 flutter packages get 这个命令,所以我们去看下 PackagesCommand

从类构造来看,他含有子命令; PackagesGetCommand; 先不管;因为所有的 FlutterCommand 都会执行 Future<FlutterCommandResult> runCommand() 这个方法;所以我们来看下这个的逻辑;

 @override
  Future<FlutterCommandResult> runCommand() async {
   // .........省略没必要的
    await _runPubGet(target);
    final FlutterProject rootProject = FlutterProject.fromPath(target);
    // 下面这行代码主要是用来刷新 .android 和 .ios的目录
    await rootProject.ensureReadyForPlatformSpecificTooling(checkProjects: true);

    // Get/upgrade packages in example app as well
    if (rootProject.hasExampleApp) {
      final FlutterProject exampleProject = rootProject.example;
      await _runPubGet(exampleProject.directory.path);
      await exampleProject.ensureReadyForPlatformSpecificTooling(checkProjects: true);
    }
	// 省略没必要的
  }
}

主要刷新逻辑在 ensureReadyForPlatformSpecificTooling

/// Generates project files necessary to make Gradle builds work on Android
  /// and CocoaPods+Xcode work on iOS, for app and module projects only.
  Future<void> ensureReadyForPlatformSpecificTooling({bool checkProjects = false}) async {
    if (!directory.existsSync() || hasExampleApp) {
      return;
    }
    refreshPluginsList(this); // 这里更新 .flutter-plugin 文件
    if ((android.existsSync() && checkProjects) || !checkProjects) {
      await android.ensureReadyForPlatformSpecificTooling();// 这里更新
    }
    if ((ios.existsSync() && checkProjects) || !checkProjects) {
      await ios.ensureReadyForPlatformSpecificTooling();// 这里更新
    }
    
    await injectPlugins(this, checkProjects: checkProjects);// 把一些channel 注册到对应的平台
  }
  • refreshPluginsList(this): 刷新 .flutter-plugins 文件
  • android.ensureReadyForPlatformSpecificTooling(): 刷新 .android 文件
  • await ios.ensureReadyForPlatformSpecificTooling(): 刷新 .ios 文件
  • injectPlugins(this, checkProjects: checkProjects): 把一些channel 注册到对应的平台, GeneratedPluginRegistrant 里面相关的代码生成;

从上面的代码可以看出, 要想 flutter package get 不去刷新,重新创建模板,只要把对应 ensureReadyForPlatformSpecificTooling() 代码不执行就好了;然后修改源码,重新编译;编译的方法在 flutter 脚本中的 4 种方式里面;