前言

最近在做一个Flutter桌面端(desktop)软件,需求兼容MacOS和Windows渠道。软件需求运用到Android的adb指令。adb指令装置复杂,为方便用户运用,决定将adb指令直接集成到App中。
本文记录了将指令文件(adb指令文件)集成到App装置包中,找到其途径,然后进行调用的办法。

将文件集成到App中

adb指令下载地址为 developer.android.com/studio/rele… 。MacOS渠道和Windows渠道为不同的文件。

直接集成

集成 文件/文件夹 到Flutter比较简略,正常引入即可。这种办法会把 文件/文件夹 直接导入到一切渠道的装置包里边。
简略两步:

  1. 将文件拖到项目目录下
  2. 在pubspec.yaml文件中,将文件的途径装备到assets里
    Flutter桌面端导入可执行文件并使用

在不同的渠道导入不同的文件

Flutter集成的办法,很简略,但有一个问题。便是一切的文件都打到装置包里边去,不论这个文件是不是这个渠道需求的。比如咱们情况,MacOS渠道需求的文件和Windows渠道需求的文件是不一样的。我不想把一切渠道的文件都打到一切的装置包里边。这会导致装置包体积增大。假如文件的体积小倒是没啥问题,但体积大的话就费事了。
所以我测验不同的渠道导入不同的文件。找了一圈,发现Flutter现在并不支撑这种需求,概况可看github上的issue Bundling assets only on a specific platform (and remove assets on another platform)。
总归并没有找到怎样从Flutter层面达到需求的办法。
那怎样办呢?Flutter层面解决不了,能够跑到原生处理。

MacOS渠道独自导入文件

作为一个iOS开发者,MacOS的文件导入小事一桩。

  1. 将文件复制到macos/Runner/目录下
  2. 用Xcode打开macos目录下的Runner.xcworkspace文件
  3. Xcode中选中左侧Runner目录后,右键 -> Add Files to “Runner”…
  4. 在弹出框里边挑选你要导入的文件,点击Add即可。
    留意:Added folders 选项我挑选了Create folder references,由于我想在装置包里边也有platform-tools文件夹,里边的文件比较好找。假如挑选了Create group的话,文件夹就没有了,一切的文件都会直接生成在装置包的根目录,很丑陋的。

Flutter桌面端导入可执行文件并使用
Flutter桌面端导入可执行文件并使用

Windows渠道独自导入文件

Windows渠道文件如何导入呢?Windows开发小白经过一顿捣鼓之后,仿照flutter_assets文件夹的办法试了一下,竟然可行。

  1. 将文件夹复制到windows/目录下
  2. 在windows/CMakeLists.txt文件的结尾加上下面的文件复制语句:
install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/platform-tools"
  DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime)

Flutter桌面端导入可执行文件并使用

找到文件的途径

文件导入完成了,那怎样找到文件的途径呢?假如是图片的话,Flutter会供给一个AssetImage来直接加载图片。json文件,视频等都能够直接加载(比如rootBundle.load办法)。
但是我不想加载啊,我只想要知道我刚导入文件夹的途径,找到其间的adb的可执行文件,然后调用adb的指令。
一顿Google后,我发现,Flutter竟然无法直接拿到导入的文件的途径!!!心中一万个草泥马奔腾而过…

代替计划1:复制文件到新途径

一顿折腾之后,现在有一个代替计划还算比较合理。便是将文件加载到程序中(rootBundle.load的办法),然后将文件写入到Documents文件夹或许Temporary文件夹中,写入文件的途径咱们是能够拿到的,自然就能找到咱们想要的文件途径了,尽管这个文件是复制后的…

Future<File> getImageFileFromAssets(String path) async {
 final byteData = await rootBundle.load('assets/$path');
 final buffer = byteData.buffer;
 Directory tempDir = await getTemporaryDirectory();
 String tempPath = tempDir.path;
 var filePath =
  tempPath + '/file_01.tmp'; // file_01.tmp is dump file, can be anything
 return File(filePath)
  .writeAsBytes(buffer.asUint8List(byteData.offsetInBytes, 
 byteData.lengthInBytes));
}

详细计划请参阅 How do I get the Asset’s file path in flutter?
这种计划有点操蛋,一是嫌复制费事,文件大的话是需求必定时间的。二是后续更新假如文件有改动的话,需求处理。别的,这么玩挺占空间的,垃圾Macbook的空间还是挺值钱的。总归便是嫌费事。所以测验找别的的代替计划。

代替计划2:获取App可执行完文件途径后定位

计划2便是经过找到装置包的根目录,然后定位到咱们的文件地点的途径。但是我竟然没找到怎样获取装置包的途径的办法!!! 尽管无法直接找到装置包的根目录,但是我找到Flutter App的可执行文件的途径。Flutter App的可执行文件的途径肯定是在装置包目录里边的。
选用Platform.resolvedExecutable能够获取Flutter App的可执行文件途径,然后能够定位到咱们需求的文件的地址。

// MacOS:在Flutter中导入文件的获取办法
// 办法:
//   1.文件夹放到工程根目录下
//   2.pubspec.yaml文件声明资源文件
//      assets:
//          - platform-tools/MacOS/
// 装置后文件途径:Contents/Frameworks/App.framework/Resources/flutter_assets/platform-tools/MacOS/adb
// App的可执行文件途径
String resolvedExecutablePath = Platform.resolvedExecutable;
// 找到装置包根目录
String rootPath = p.dirname(p.dirname(resolvedExecutablePath));
// 定位到adb文件途径
final adbPath = p.join(rootPath, "Frameworks", "App.framework", "Resources", "flutter_assets", "platform-tools", "MacOS", "adb");
debugPrint("MacOS:Flutter导入文件 adbPath=$adbPath");
// MacOS:在Xcode中导入文件
// 办法:
//   1.将文件复制到macos/Runner/目录下
//   2.用Xcode打开macos目录下的Runner.xcworkspace文件
//   3.Xcode中选中左侧Runner目录后,右键 -> Add Files to "Runner"...
//   4.在弹出框里边挑选你要导入的文件,点击Add即可。
// 装置后文件途径:Contents/Resources/platform-tools/adb
// App的可执行文件途径
String resolvedExecutablePath = Platform.resolvedExecutable;
// 找到装置包根目录
String rootPath = p.dirname(p.dirname(resolvedExecutablePath));
debugPrint("rootPath=$rootPath");
// 定位到adb文件途径
final adbPath = p.join(rootPath, "Resources", "platform-tools", "adb");
debugPrint("MacOS:在Xcode中导入文件 adbPath=$adbPath");
// Windows: 在Flutter中导入文件
// 办法:
//  1.文件夹放到工程根目录
//  2.pubspec.yaml文件声明资源文件
//    assets:
//        - platform-tools/Windows/
// 装置后文件途径:data\flutter_assets\platformTools\Windows\adb.exe
// App的可执行文件途径
String resolvedExecutablePath = Platform.resolvedExecutable;
String rootPath = p.dirname(resolvedExecutablePath);
// 定位到adb文件途径
final adbPath = p.join(rootPath, "data", "flutter_assets", "platform-tools", "Windows", "adb.exe");
debugPrint("Windows:在CMake里直接复制资源到装置包 adbPath=$adbPath");
// Windows:在CMake里直接复制资源到装置包
// 办法:
//  1.文件加放到windows目录
//  2.设置windows/CMakeLists.txt文件。在文件最终一个install办法的前面,加上下面的代码
//    install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/" DESTINATION "${CMAKE_INSTALL_PREFIX}" COMPONENT Runtime)
// 装置后文件途径:platform-tools\adb.exe
// App的可执行文件途径
String resolvedExecutablePath = Platform.resolvedExecutable;
String rootPath = p.dirname(resolvedExecutablePath);
// 定位到adb文件途径
final adbPath = p.join(rootPath, "platform-tools", "adb.exe");
debugPrint("Windows:在CMake里直接复制资源到装置包 adbPath=$adbPath");

注:这种计划不保证后续能用,假如Flutter有改动资源途径的话,可能会失效。

指令调用

指令调用运用的是process_run库。 导入process_run

process_run: ^0.12.5+2

引入头文件

import 'package:process_run/shell.dart';

然后就能够执行指令了

final shell = Shell();
try {
  await shell
      .runExecutableArguments(macAdbPath1!, ["--version"]);
} catch (e) {
  debugPrint("exec catch exception=$e");
}

注:MacOS渠道需求关闭沙箱设置,请参照官方文档来处理。

总结

Flutter导入文件到装置包,通用的办法是直接在pubspec.yaml文件里边导入。但这种办法现在会一起导入到多个渠道。假如想要单渠道导入的话,需求到原生工程去处理。希望Flutter后续能支撑不同渠道导入不同文件。
现在难以从Flutter端直接获取到导入的文件的途径,常见计划是将文件读取到内存,然后写入到文件中,这样能够获取到写入后的文件途径。我选用的计划是经过Platform.resolvedExecutable找到App的可执行文件的途径,然后再定位到咱们的文件。事实上获取导入文件的途径应该是由Flutter直接给API处理才对的,不知道什么时候才能支撑。
执行指令运用process_run库较多,可参照官网文档和其他的文章运用。

参阅

issue: Bundling assets only on a specific platform (and remove assets on another platform)
stackoverflow: How do I get the Asset’s file path in flutter?
issue: Support for AssetBundle to get each platforms bundle path
dart pakages: process_run
github: Demo地址