Flutter 원본 분석 (4): flutterrun 프로세스 분석

14735 단어 androidadbgwtaigradle
정보 flutter runflutter 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#RunCommandrunCommand 방법으로 호출될 것이다.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(후면 온도 변화
  • 구축이 완료된 후에 명령행 프로그램을 시작하도록 설정
  • 먼저 HotRunnerrun방법을 보세요.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에 남아서 분석할 기회가 있다.

    좋은 웹페이지 즐겨찾기