本文由 简悦SimpRead 转码,原文地址 medium.com
可以在你的iOS设备上热重载和热重启你的Flutter应用程序,而不必u......。
更新。Flutter 1.17.0现在使用XCode而不是libimobiledevice来列出设备。目前,你可以降级到
Flutter 1.12.13+hotfix.9,或者使用Codemagic的新功能,允许你远程控制一个Mac虚拟机,并在那里运行一个虚拟设备(blog.codemagic.io/remote-acce…
可以在你的iOS设备上热重载和热重启你的Flutter应用程序,而不需要使用Mac,就像你在Android设备上一样!如何做?下面是方法。
在您的iOS设备上运行您的应用程序的调试版
你可以用Mac(或Hackintosh,或VM)来做,但由于我们无法使用macOS机器,我们可以通过Codemagic或Travis CI来远程使用--完全免费 (只要你的项目是在GitHub、Bitbucket或GitLab仓库中)。
推荐使用。用Codemagic构建应用程序
推荐使用Codemagic,因为它需要的设置较少,而且构建速度相当快
首先,创建一个账户或登录到codemagic.io。
然后,点击你的应用程序旁边的设置(齿轮)图标。向下滚动并点击 "构建"。确保模式被设置为调试,并在平台的构建下选择iOS。
默认情况下,Codemagic会测试你的应用程序。除非你想使用这个功能,否则请禁用它。感谢Near Cz的反馈!(Thanks to Near Cz for the feedback!)
之后,构建应用程序(开始你的第一次构建)。
新:现在Codemagic让你远程控制它用来构建应用程序的macOS虚拟机。在那里,你可以运行一个模拟器,测试你的应用程序,并从XCode配置它。见 blog.codemagic.io/remote-acce…
Codemagic将通过电子邮件给你发送一个.app文件。
重命名它,使其以.zip结尾。
提取它,你会得到一个名为Runner.app的文件夹。
创建一个名为 "Payload "的文件夹,将 "Runner.app "放在那里。
最后,压缩名为 "Payload "的文件夹 - 这将是你的IPA文件(你可以把它重命名为".ipa")。
要继续,请跳到[安装和运行应用程序](#805c)。
替代方案。用Travis CI构建应用程序
注意:Travis CI的免费版本只支持公共GitHub仓库。
你需要在Travis CI上创建一个账户,让它访问你的GitHub账户。
然后,在你项目的根目录下创建.travis.yml,内容如下。
os: osx
language: generic
before_script:
- brew update
- brew install --HEAD usbmuxd
- brew unlink usbmuxd
- brew link usbmuxd
- brew install --HEAD libimobiledevice
- brew install ideviceinstaller
- brew install ios-deploy
- git clone https://github.com/flutter/flutter.git -b beta --depth 1
script:
- flutter/bin/flutter build ios --debug --no-codesign
cache:
directories:
- $HOME/.pub-cache
before_deploy:
- pushd build/ios/iphoneos
- mkdir Payload
- cd Payload
- ln -s ../Runner.app
- cd ..
- zip -r app.ipa Payload
- popd
你需要安装Travis的命令行工具:(如果下面的命令没有找到,请从rubygems.org/安装RubyGems)
$ gem install travis # Makes sure to have the Travis CLI installed
然后,设置部署到GitHub版本。
$ cd your_project
$ travis setup releases
当提示上传文件时,输入build/ios/iphoneos/app.ipa。
确保你的".travis.yml "像这样结束。
deploy:
provider: releases
api_key:
secure: #your api key will be here
file: build/ios/iphoneos/app.ipa
skip_cleanup: true #important or your built app would be deleted
on:
repo: #your repo will be here
现在,把修改推送到你的GitHub repo。
$ git add .travis.yml
$ git commit
$ git push
现在,你的应用程序将被构建,并被添加到你的GitHub发布中。等待一下,让Travis注意到你已经添加了.travis.yml;当它开始时,你可以跟踪你的构建进度。当它完成后,从你的项目的GitHub版本中下载你的app.ipa。
注意:如果你不想发布你的
.ipa文件,你可以在deploy:中添加draft: true,它将作为草稿发布在GitHub上。
安装和运行应用程序
要安装IPA文件,你需要从下面的链接下载Cydia Impactor。
如果你运行的是Windows,你需要先安装iTunes。(确保安装非微软商店的版本:在 "寻找其他版本?"选择Windows,然后向上滚动并下载)。
编辑:本文的前一个版本建议绕过iTunes的安装来安装驱动程序。很明显,这并不奏效。(感谢Andreas Opferkuch指出了这一点)
更新:Cydia Impactor目前似乎已经坏了。如果你有一个开发者账户,你可以按照Weisser Zwerg的教程(感谢分享!)来签署和安装该应用程序。如果你有一个越狱的设备,你可以使用AppSync unified和ideviceinstaller。还有一些替代Cydia Impactor的工具,但我不能保证它们没有恶意软件,所以使用它们的风险由你自己承担。
- 将你的iOS设备插入你的电脑
- 运行Impactor的可执行文件
- 点击 "Xcode",然后 "撤销证书"
- 现在,将 "app.ipa"(或你创建的ZIP文件)拖到Cydia Impactor窗口(或使用设备>安装包......)。
- 你会被提示输入你的苹果ID电子邮件和密码。如果它受到双因素认证的保护,或者你得到一个错误信息,你将需要创建一个应用程序特定的密码,并使用它代替你的密码。见support.apple.com/en-us/HT204…。(感谢Near Cz的指出!)
- 在你的iOS设备上,进入设置>通用>设备管理(或配置文件和设备管理),点击你刚才用来登录的Apple ID电子邮件,然后信任。
- 万岁! 我们的应用程序现在已经安装完毕。
准备好你的机器
安装依赖项
确保 "libimobiledevice "和 "ideviceinstaller "已经安装(后者并不使用,但如果不存在,Flutter会抱怨)。
在Windows上:
之前的链接已经没有二进制文件了,感谢Andreas Opferkuch分享的替代链接!
从这里下载imobiledevice二进制文件。你还需要一个"which"二进制文件,你可以下载here。将这些二进制文件添加到flutterbin(或任何其他位置,但要确保它在你的 **PATH**)。
在Arch/Manjaro/Antergos上:
$ pacaur -S libimobiledevice ideviceinstaller-git
在Ubuntu/Mint/etc上:
$ sudo apt install libimobiledevice6 ideviceinstaller
你可能还需要安装这些软件包(感谢João Matheus)。
$ sudo apt install libimobiledevice-utils libusbmuxd-tools
修改Flutter
当检测设备时,Flutter在搜索iOS设备之前会检查你是否在运行macOS。它还会检查XCode。让我们删除这些检查。
注意:它在当前的稳定版(1.17.0)中无法工作。同时,你可以用Flutter 1.12.13+hotfix.9代替。
显然,现在Flutter使用XCode utilities而不是libimobiledevice,所以补丁不会那么简单。在此期间,请使用旧版本,或帮助贡献补丁或其他解决方案(fork,或撤消特定提交的修改的方法)。一旦这个问题得到解决,我就会更新这篇文章。
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index 9544a18f7..d72e28bd4 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:io';
+
import 'package:meta/meta.dart';
import 'base/context.dart';
@@ -256,17 +258,15 @@ class CachedArtifacts extends Artifacts {
case Artifact.idevicescreenshot:
case Artifact.idevicesyslog:
case Artifact.idevicename:
- final String artifactFileName = _artifactToFileName(artifact);
- return cache.getArtifactDirectory('libimobiledevice').childFile(artifactFileName).path;
case Artifact.iosDeploy:
- final String artifactFileName = _artifactToFileName(artifact);
- return cache.getArtifactDirectory('ios-deploy').childFile(artifactFileName).path;
case Artifact.ideviceinstaller:
- final String artifactFileName = _artifactToFileName(artifact);
- return cache.getArtifactDirectory('ideviceinstaller').childFile(artifactFileName).path;
case Artifact.iproxy:
final String artifactFileName = _artifactToFileName(artifact);
- return cache.getArtifactDirectory('usbmuxd').childFile(artifactFileName).path;
+ final ProcessResult whichResult = Process.runSync('which', <String>[artifactFileName]);
+ if (whichResult.exitCode != 0) {
+ throw UnsupportedError('$artifactFileName is not in the path, please install the dependencies following the article, and add it to the PATH if necessary');
+ }
+ return whichResult.stdout.toString().trimRight();
default:
return _getHostArtifactPath(artifact, platform, mode);
}
diff --git a/packages/flutter_tools/lib/src/ios/devices.dart b/packages/flutter_tools/lib/src/ios/devices.dart
index b11a627b7..23659c95c 100644
--- a/packages/flutter_tools/lib/src/ios/devices.dart
+++ b/packages/flutter_tools/lib/src/ios/devices.dart
@@ -106,7 +106,7 @@ class IOSDevices extends PollingDeviceDiscovery {
IOSDevices() : super('iOS devices');
@override
- bool get supportsPlatform => platform.isMacOS;
+ bool get supportsPlatform => true;
@override
bool get canListAnything => iosWorkflow.canListDevices;
@@ -172,10 +172,8 @@ class IOSDevice extends Device {
bool get supportsStartPaused => false;
static Future<List<IOSDevice>> getAttachedDevices() async {
- if (!platform.isMacOS) {
- throw UnsupportedError('Control of iOS devices or simulators only supported on Mac OS.');
- }
if (!iMobileDevice.isInstalled) {
+ printError("libimobiledevice doesn't seem to be installed, reread the instalation steps or leave a comment in the article");
return <IOSDevice>[];
}
@@ -208,9 +206,6 @@ class IOSDevice extends Device {
apps = await processUtils.run(
<String>[_installerPath, '--list-apps'],
throwOnError: true,
- environment: Map<String, String>.fromEntries(
- <MapEntry<String, String>>[cache.dyLdLibEntry],
- ),
);
} on ProcessException {
return false;
@@ -233,9 +228,6 @@ class IOSDevice extends Device {
await processUtils.run(
<String>[_installerPath, '-i', app.deviceBundlePath],
throwOnError: true,
- environment: Map<String, String>.fromEntries(
- <MapEntry<String, String>>[cache.dyLdLibEntry],
- ),
);
return true;
} on ProcessException catch (error) {
@@ -250,9 +242,6 @@ class IOSDevice extends Device {
await processUtils.run(
<String>[_installerPath, '-U', app.id],
throwOnError: true,
- environment: Map<String, String>.fromEntries(
- <MapEntry<String, String>>[cache.dyLdLibEntry],
- ),
);
return true;
} on ProcessException catch (error) {
@@ -707,9 +696,6 @@ class IOSDevicePortForwarder extends DevicePortForwarder {
devicePort.toString(),
device.id,
],
- environment: Map<String, String>.fromEntries(
- <MapEntry<String, String>>[cache.dyLdLibEntry],
- ),
);
// TODO(ianh): This is a flakey race condition, https://github.com/libimobiledevice/libimobiledevice/issues/674
connected = !await process.stdout.isEmpty.timeout(_kiProxyPortForwardTimeout, onTimeout: () => false);
diff --git a/packages/flutter_tools/lib/src/ios/ios_workflow.dart b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
index ac9630ba9..5913050d4 100644
--- a/packages/flutter_tools/lib/src/ios/ios_workflow.dart
+++ b/packages/flutter_tools/lib/src/ios/ios_workflow.dart
@@ -17,7 +17,7 @@ class IOSWorkflow implements Workflow {
// We need xcode (+simctl) to list simulator devices, and libimobiledevice to list real devices.
@override
- bool get canListDevices => xcode.isInstalledAndMeetsVersionCheck && xcode.isSimctlInstalled;
+ bool get canListDevices => true;
// We need xcode to launch simulator devices, and ideviceinstaller and ios-deploy
// for real devices.
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index dd5597f62..bb2576520 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -97,7 +97,9 @@ class IMobileDevice {
_ideviceinfoPath = artifacts.getArtifactPath(Artifact.ideviceinfo, platform: TargetPlatform.ios),
_idevicenamePath = artifacts.getArtifactPath(Artifact.idevicename, platform: TargetPlatform.ios),
_idevicesyslogPath = artifacts.getArtifactPath(Artifact.idevicesyslog, platform: TargetPlatform.ios),
- _idevicescreenshotPath = artifacts.getArtifactPath(Artifact.idevicescreenshot, platform: TargetPlatform.ios);
+ _idevicescreenshotPath = artifacts.getArtifactPath(Artifact.idevicescreenshot, platform: TargetPlatform.ios) {
+ assert(artifacts.runtimeType == CachedArtifacts);
+ }
final String _ideviceIdPath;
final String _ideviceinfoPath;
@@ -111,9 +113,6 @@ class IMobileDevice {
_ideviceIdPath,
'-h',
],
- environment: Map<String, String>.fromEntries(
- <MapEntry<String, String>>[cache.dyLdLibEntry]
- ),
);
return _isInstalled;
}
@@ -176,11 +175,12 @@ class IMobileDevice {
_ideviceIdPath,
'-l',
],
- environment: Map<String, String>.fromEntries(
- <MapEntry<String, String>>[cache.dyLdLibEntry]
- ),
);
if (result.exitCode != 0) {
+ if (result.stderr.toString().contains('ERROR: Unable to retrieve device list!')) {
+ // idevice_id might throw this error if there are no connected devices
+ return '';
+ }
throw ToolExit('idevice_id returned an error:\n${result.stderr}');
}
return result.stdout as String;
@@ -199,9 +199,6 @@ class IMobileDevice {
'-k',
key,
],
- environment: Map<String, String>.fromEntries(
- <MapEntry<String, String>>[cache.dyLdLibEntry]
- ),
);
final String stdout = result.stdout as String;
final String stderr = result.stderr as String;
@@ -239,9 +236,6 @@ class IMobileDevice {
'-u',
deviceID,
],
- environment: Map<String, String>.fromEntries(
- <MapEntry<String, String>>[cache.dyLdLibEntry]
- ),
);
}
@@ -253,9 +247,6 @@ class IMobileDevice {
outputFile.path,
],
throwOnError: true,
- environment: Map<String, String>.fromEntries(
- <MapEntry<String, String>>[cache.dyLdLibEntry]
- ),
);
}
}
(我们将把这些修改应用于Flutter)
把这个文件下载到你的flutter文件夹中(点击 "查看原始文件 "并按Ctrl+S)。然后应用它。(这是在4月底到5月初用稳定分支测试的)
$ cd flutter
$ git apply ios.diff
然后重建Flutter工具。
Linux:
$ bin/cache/dart-sdk/bin/dart --snapshot=./bin/cache/flutter_tools.snapshot --packages=./packages/flutter_tools/.packages ./packages/flutter_tools/bin/flutter_tools.dart
Windows:
bin\cache\dart-sdk\bin\dart --snapshot=.\bin\cache\flutter_tools.snapshot --packages=.\packages\flutter_tools\.packages .\packages\flutter_toolsbin\flutter_tools.dart
确保在你的项目里有一个名为build的文件夹。如果不存在,就创建它!
好戏来了!。
使用IDE(VS Code)。
- 打开命令调色板(Ctrl+Shift+P),并选择 "调试。附加到Flutter进程"。
- 打开应用程序,并等待Flutter同步文件。
- 你现在可以像往常一样热重载和热重启了!
提示:如果热重载不起作用,请先热重启!
使用命令行。
- 运行 "flutter attach"。这将等待应用程序启动并开始调试,就像我们使用
flutter run一样。 - 打开应用程序,并等待Flutter同步文件。
- 你现在可以按r热重载,按R热重启!
学分
感谢Yegor Jbanov的帖子。
有任何问题或反馈?请留下评论!
如果你喜欢这个,不要忘了拍手,这将是极大的赞赏!