美文网首页Flutter圈子
Flutter桌面端导入可执行文件并使用

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

作者: 谦言忘语 | 来源:发表于2023-04-11 17:42 被阅读0次

    前言

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

    将文件集成到App中

    adb命令下载地址为 https://developer.android.com/studio/releases/platform-tools?hl=zh-cn 。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的话,文件夹就没有了,所有的文件都会直接生成在安装包的根目录,很难看的。
      Add Files to "Runner"
      弹出框里面选择你要导入的文件

    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)
    
    Windows平台导入文件

    找到文件的路径

    文件导入完成了,那怎么找到文件的路径呢?如果是图片的话,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地址

    相关文章

      网友评论

        本文标题:Flutter桌面端导入可执行文件并使用

        本文链接:https://www.haomeiwen.com/subject/ydngddtx.html