解決 flutter module 中 .android 和 .ios 目錄不被覆蓋的問題

老碼雜談發表於2019-09-20

在對 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 下面:

解決 flutter module 中 .android 和 .ios 目錄不被覆蓋的問題

以上紅色箭頭的地方,是我替換和新增的模板程式碼;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

解決 flutter module 中 .android 和 .ios 目錄不被覆蓋的問題

從類構造來看,他含有子命令; 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 種方式裡面;

相關文章