Flutter 원본 분석 (4): flutterrun 프로세스 분석
flutter run
flutter run
flutter 프로젝트를 구축하고 생산물을 해당 설비에 출력하며 기본적인 상호작용 제어를 제공한다. 사용 효과는 다음과 같다.$ flutter run
Launching lib/main.dart on COL AL10 in debug mode...
Running Gradle task 'assembleDebug'...
Running Gradle task 'assembleDebug'... Done 23.8s
✓ Built build/app/outputs/flutter-apk/app-debug.apk.
Installing build/app/outputs/flutter-apk/app.apk... 13.7s
Waiting for COL AL10 to report its views... 7ms
Syncing files to device COL AL10... 279ms
Flutter run key commands.
r Hot reload. ????????????
R Hot restart.
h Repeat this help message.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
An Observatory debugger and profiler on COL AL10 is available at: http://127.0.0.1:65470/jL7vVcN78XI=/
D/AwareBitmapCacher(10010): handleInit switch not opened pid=10010
다음은 이 명령의 집행 절차를 읽어 보겠습니다.
lib/executable.dart#main
lib/executable.dart#main
는 모든 명령이 실행되는 포털입니다.Future main(List args) async {
....
await runner.run(args, () => [
AssembleCommand(),
AttachCommand(verboseHelp: verboseHelp),
RunCommand(verboseHelp: verboseHelp),
ScreenshotCommand(),
ShellCompletionCommand(),
....
);
}
층층이 봉인을 통해 변조된 후
flutter run
이 명령은 최종적으로 lib/src/commands/run.dart#RunCommand
의 runCommand
방법으로 호출될 것이다.RunCommand#runCommand
핵심 코드는 다음과 같습니다.
@override
Future runCommand() async {
// Enable hot mode by default if `--no-hot` was not passed and we are in
// debug mode.
final bool hotMode = shouldUseHotMode();
writePidFile(stringArg('pid-file'));
......
ResidentRunner runner;
final String applicationBinaryPath = stringArg('use-application-binary');
if (hotMode && !webMode) {
runner = HotRunner(
flutterDevices,
target: targetFile,
debuggingOptions: _createDebuggingOptions(),
benchmarkMode: boolArg('benchmark'),
applicationBinary: applicationBinaryPath == null
? null
: globals.fs.file(applicationBinaryPath),
projectRootPath: stringArg('project-root'),
dillOutputPath: stringArg('output-dill'),
stayResident: stayResident,
ipv6: ipv6,
);
} else if (webMode) {
......
} else {
......
}
DateTime appStartedTime;
final Completer appStartedTimeRecorder = Completer.sync();
// This callback can't throw.
unawaited(appStartedTimeRecorder.future.then(
(_) {
appStartedTime = globals.systemClock.now();
if (stayResident) {
TerminalHandler(runner)
..setupTerminal()
..registerSignalHandlers();
}
}
));
final int result = await runner.run(
appStartedCompleter: appStartedTimeRecorder,
route: route,
);
if (result != 0) {
throwToolExit(null, exitCode: result);
}
......
}
중요한 논리는 두 가지가 있다.
HotRunner
(후면 온도 변화HotRunner
의run
방법을 보세요.HotRunner#run
Future run({
Completer connectionInfoCompleter,
Completer appStartedCompleter,
String route,
}) async {
......
final List> startupTasks = >[];
final PackageConfig packageConfig = await loadPackageConfigWithLogging(
globals.fs.file(debuggingOptions.buildInfo.packagesPath),
logger: globals.logger,
);
for (final FlutterDevice device in flutterDevices) {
await runSourceGenerators();
if (device.generator != null) {
startupTasks.add(
device.generator.recompile(
globals.fs.file(mainPath).uri,
[],
suppressErrors: applicationBinary == null,
outputPath: dillOutputPath ??
getDefaultApplicationKernelPath(trackWidgetCreation: debuggingOptions.buildInfo.trackWidgetCreation),
packageConfig: packageConfig,
).then((CompilerOutput output) => output?.errorCount == 0)
);
}
startupTasks.add(device.runHot(
hotRunner: this,
route: route,
).then((int result) => result == 0));
}
try {
final List results = await Future.wait(startupTasks);
if (!results.every((bool passed) => passed)) {
appFailedToStart();
return 1;
}
cacheInitialDillCompilation();
} on Exception catch (err) {
globals.printError(err.toString());
appFailedToStart();
return 1;
}
return attach(
connectionInfoCompleter: connectionInfoCompleter,
appStartedCompleter: appStartedCompleter,
);
}
주로 두 가지가 있다.
attach
디바이스FlutterDevice#runHot
이 방법FlutterDevice#runHot
Future runHot({
HotRunner hotRunner,
String route,
}) async {
.....
// Start the application.
final Future futureResult = device.startApp(
package,
mainPath: hotRunner.mainPath,
debuggingOptions: hotRunner.debuggingOptions,
platformArgs: platformArgs,
route: route,
prebuiltApplication: prebuiltMode,
ipv6: hotRunner.ipv6,
userIdentifier: userIdentifier,
);
final LaunchResult result = await futureResult;
.....
return 0;
}
계속 추적
startApp
방법, 이 방법은 플랫폼에 따라 서로 다른 실현이 있다. 여기서 보면 AndroidDevice
의: @override
Future startApp(
AndroidApk package, {
String mainPath,
String route,
DebuggingOptions debuggingOptions,
Map platformArgs,
bool prebuiltApplication = false,
bool ipv6 = false,
String userIdentifier,
}) async {
......
if (!prebuiltApplication || _androidSdk.licensesAvailable && _androidSdk.latestVersion == null) {
_logger.printTrace('Building APK');
final FlutterProject project = FlutterProject.current();
await androidBuilder.buildApk(
project: project,
target: mainPath,
androidBuildInfo: AndroidBuildInfo(
debuggingOptions.buildInfo,
targetArchs: [androidArch],
fastStart: debuggingOptions.fastStart
),
);
// Package has been built, so we can get the updated application ID and
// activity name from the .apk.
package = await AndroidApk.fromAndroidProject(project.android);
}
if (!await _installLatestApp(package, userIdentifier)) {
return LaunchResult.failed();
}
.....
List cmd;
cmd = [
'shell', 'am', 'start',
'-a', 'android.intent.action.RUN',
'-f', '0x20000000', // FLAG_ACTIVITY_SINGLE_TOP
'--ez', 'enable-background-compilation', 'true',
'--ez', 'enable-dart-profiling', 'true',
....
}
보시다시피 주로buildapk을 사용하고
_installLatestApp
설치를 책임지고 마지막에 시작합니다.먼저 buildapk 프로세스를 보십시오
AndroidBuilderImpl#buildApk
:
@override
Future buildApk({
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
}) async {
try {
await buildGradleApp(
project: project,
androidBuildInfo: androidBuildInfo,
target: target,
isBuildingBundle: false,
localGradleErrors: gradleErrors,
);
} finally {
globals.androidSdk?.reinitialize();
}
}
마지막으로 진정한 구축 논리인지,gradle을 통해 호출되는지:
Future buildGradleApp({
@required FlutterProject project,
@required AndroidBuildInfo androidBuildInfo,
@required String target,
@required bool isBuildingBundle,
@required List localGradleErrors,
bool shouldBuildPluginAsAar = false,
int retries = 1,
}) async {
......
final BuildInfo buildInfo = androidBuildInfo.buildInfo;
final String assembleTask = isBuildingBundle
? getBundleTaskFor(buildInfo)
: getAssembleTaskFor(buildInfo);
......
final List command = [
gradleUtils.getExecutable(project),
];
if (globals.logger.isVerbose) {
command.add('-Pverbose=true');
} else {
command.add('-q');
}
.....
if (target != null) {
command.add('-Ptarget=$target');
}
assert(buildInfo.trackWidgetCreation != null);
command.add('-Ptrack-widget-creation=${buildInfo.trackWidgetCreation}');
if (buildInfo.extraFrontEndOptions != null) {
command.add('-Pextra-front-end-options=${encodeDartDefines(buildInfo.extraFrontEndOptions)}');
}
if (buildInfo.extraGenSnapshotOptions != null) {
command.add('-Pextra-gen-snapshot-options=${encodeDartDefines(buildInfo.extraGenSnapshotOptions)}');
}
if (buildInfo.fileSystemRoots != null && buildInfo.fileSystemRoots.isNotEmpty) {
command.add('-Pfilesystem-roots=${buildInfo.fileSystemRoots.join('|')}');
}
if (buildInfo.fileSystemScheme != null) {
command.add('-Pfilesystem-scheme=${buildInfo.fileSystemScheme}');
}
if (androidBuildInfo.splitPerAbi) {
command.add('-Psplit-per-abi=true');
}
.....
command.add(assembleTask);
.....
}
.....
try {
exitCode = await processUtils.stream(
command,
workingDirectory: project.android.hostAppGradleRoot.path,
allowReentrantFlutter: true,
environment: gradleEnvironment,
mapFunction: consumeLog,
);
} on ProcessException catch(exception) {
.....
// Gradle produced an APK.
final Iterable apkFilesPaths = project.isModule
? findApkFilesModule(project, androidBuildInfo)
: listApkPaths(androidBuildInfo);
final Directory apkDirectory = getApkDirectory(project);
final File apkFile = apkDirectory.childFile(apkFilesPaths.first);
.....
final String appSize = (buildInfo.mode == BuildMode.debug)
? '' // Don't display the size when building a debug variant.
: ' (${getSizeAsMB(apkFile.lengthSync())})';
globals.printStatus(
'$successMark Built ${globals.fs.path.relative(apkFile.path)}$appSize.',
color: TerminalColor.green,
);
}
gradle에 필요한 모든 인자를 만들고
processUtils
실행을 시작하면, 실행하면 APK 파일이 구축 디렉터리에 나타납니다.이때
androidBuilder.buildApk
실행이 끝났고 뒤의_installLatestApp
는 apk 파일의 설치를 책임진다.
Future _installLatestApp(AndroidApk package, String userIdentifier) async {
final bool wasInstalled = await isAppInstalled(package, userIdentifier: userIdentifier);
if (wasInstalled) {
if (await isLatestBuildInstalled(package)) {
_logger.printTrace('Latest build already installed.');
return true;
}
}
_logger.printTrace('Installing APK.');
if (!await installApp(package, userIdentifier: userIdentifier)) {
.....
진정한 설치 작업:
@override
Future installApp(
AndroidApk app, {
String userIdentifier,
}) async {
.....
final Status status = _logger.startProgress(
'Installing ${_fileSystem.path.relative(app.file.path)}...',
timeout: _timeoutConfiguration.slowOperation,
);
final RunResult installResult = await _processUtils.run(
adbCommandForDevice([
'install',
'-t',
'-r',
if (userIdentifier != null)
...['--user', userIdentifier],
app.file.path
]));
status.stop();
.....
return true;
}
본질은adb 명령을 통해 설치하는 것을 알 수 있다.
attach
설치가 시작되면 이전
setupTerminal
에서 대화식 명령줄이 초기화됩니다.
void setupTerminal() {
if (!globals.logger.quiet) {
globals.printStatus('');
residentRunner.printHelp(details: false);
}
globals.terminal.singleCharMode = true;
subscription = globals.terminal.keystrokes.listen(processTerminalInput);
}
processTerminalInput
사용자 입력 처리
Future processTerminalInput(String command) async {
......
try {
lastReceivedCommand = command;
await _commonTerminalInputHandler(command);
// Catch all exception since this is doing cleanup and rethrowing.
} catch (error, st) { // ignore: avoid_catches_without_on_clauses
....
Future _commonTerminalInputHandler(String character) async {
globals.printStatus(''); // the key the user tapped might be on this line
switch(character) {
....
case 'r':
if (!residentRunner.canHotReload) {
return false;
}
final OperationResult result = await residentRunner.restart(fullRestart: false);
if (result.fatal) {
throwToolExit(result.message);
}
if (!result.isOk) {
globals.printStatus('Try again after fixing the above error(s).', emphasis: true);
}
return true;
case 'R':
// If hot restart is not supported for all devices, ignore the command.
if (!residentRunner.canHotRestart || !residentRunner.hotMode) {
return false;
}
final OperationResult result = await residentRunner.restart(fullRestart: true);
if (result.fatal) {
throwToolExit(result.message);
}
if (!result.isOk) {
globals.printStatus('Try again after fixing the above error(s).', emphasis: true);
}
return true;
....
}
return false;
}
이것은 명령행 환경을 초기화했을 뿐이고 장치가 명령을 보내는 채널
flutter attach
과 같은 채널을 구축해야 한다. 여기는DartVM과의 원격 통신이 관련되어 있고 flutter attach
에 남아서 분석할 기회가 있다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Kotlin의 기초 - 2부지난 글에서는 Kotlin이 무엇인지, Kotlin의 특징, Kotlin에서 변수 및 데이터 유형을 선언하는 방법과 같은 Kotlin의 기본 개념에 대해 배웠습니다. 유형 변환은 데이터 변수의 한 유형을 다른 데이터...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.