mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
[flutter_tools] hot reload/restart update for asset manager change (#66742)
Do not upload all assets on initial devFS sync. This should increase the reliability of the initial connection, even in the face of flaky devfs behavior, in addition to a moderate perf improvement. Updates fast-start to build assets as part of the initial bundle Requires flutter/engine#21436 Requires flutter/engine#21586 Requires flutter/engine#21611
This commit is contained in:
parent
a755c03819
commit
9a3a0dc18f
@ -893,11 +893,7 @@ abstract class BaseFlutterTask extends DefaultTask {
|
|||||||
// cache.
|
// cache.
|
||||||
String[] ruleNames;
|
String[] ruleNames;
|
||||||
if (buildMode == "debug") {
|
if (buildMode == "debug") {
|
||||||
if (fastStart) {
|
ruleNames = ["debug_android_application"]
|
||||||
ruleNames = ["faststart_android_application"]
|
|
||||||
} else {
|
|
||||||
ruleNames = ["debug_android_application"]
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
|
ruleNames = targetPlatformValues.collect { "android_aot_bundle_${buildMode}_$it" }
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,9 @@ abstract class AndroidAssetBundle extends Target {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
List<String> get depfiles => <String>[
|
List<String> get depfiles => <String>[
|
||||||
if (_copyAssets)
|
'flutter_assets.d',
|
||||||
'flutter_assets.d',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
bool get _copyAssets => true;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> build(Environment environment) async {
|
Future<void> build(Environment environment) async {
|
||||||
@ -61,21 +59,19 @@ abstract class AndroidAssetBundle extends Target {
|
|||||||
environment.fileSystem.file(isolateSnapshotData)
|
environment.fileSystem.file(isolateSnapshotData)
|
||||||
.copySync(outputDirectory.childFile('isolate_snapshot_data').path);
|
.copySync(outputDirectory.childFile('isolate_snapshot_data').path);
|
||||||
}
|
}
|
||||||
if (_copyAssets) {
|
final Depfile assetDepfile = await copyAssets(
|
||||||
final Depfile assetDepfile = await copyAssets(
|
environment,
|
||||||
environment,
|
outputDirectory,
|
||||||
outputDirectory,
|
targetPlatform: TargetPlatform.android,
|
||||||
targetPlatform: TargetPlatform.android,
|
);
|
||||||
);
|
final DepfileService depfileService = DepfileService(
|
||||||
final DepfileService depfileService = DepfileService(
|
fileSystem: environment.fileSystem,
|
||||||
fileSystem: environment.fileSystem,
|
logger: environment.logger,
|
||||||
logger: environment.logger,
|
);
|
||||||
);
|
depfileService.writeToFile(
|
||||||
depfileService.writeToFile(
|
assetDepfile,
|
||||||
assetDepfile,
|
environment.buildDir.childFile('flutter_assets.d'),
|
||||||
environment.buildDir.childFile('flutter_assets.d'),
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -108,17 +104,6 @@ class DebugAndroidApplication extends AndroidAssetBundle {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A minimal android application that does not include assets.
|
|
||||||
class FastStartAndroidApplication extends DebugAndroidApplication {
|
|
||||||
const FastStartAndroidApplication();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String get name => 'faststart_android_application';
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get _copyAssets => false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An implementation of [AndroidAssetBundle] that only includes assets.
|
/// An implementation of [AndroidAssetBundle] that only includes assets.
|
||||||
class AotAndroidAssetBundle extends AndroidAssetBundle {
|
class AotAndroidAssetBundle extends AndroidAssetBundle {
|
||||||
const AotAndroidAssetBundle();
|
const AotAndroidAssetBundle();
|
||||||
|
@ -49,7 +49,6 @@ const List<Target> _kDefaultTargets = <Target>[
|
|||||||
CopyFlutterBundle(),
|
CopyFlutterBundle(),
|
||||||
// Android targets,
|
// Android targets,
|
||||||
DebugAndroidApplication(),
|
DebugAndroidApplication(),
|
||||||
FastStartAndroidApplication(),
|
|
||||||
ProfileAndroidApplication(),
|
ProfileAndroidApplication(),
|
||||||
// Android ABI specific AOT rules.
|
// Android ABI specific AOT rules.
|
||||||
androidArmProfileBundle,
|
androidArmProfileBundle,
|
||||||
|
@ -472,7 +472,6 @@ class DevFS {
|
|||||||
String dillOutputPath,
|
String dillOutputPath,
|
||||||
bool fullRestart = false,
|
bool fullRestart = false,
|
||||||
String projectRootPath,
|
String projectRootPath,
|
||||||
bool skipAssets = false,
|
|
||||||
}) async {
|
}) async {
|
||||||
assert(trackWidgetCreation != null);
|
assert(trackWidgetCreation != null);
|
||||||
assert(generator != null);
|
assert(generator != null);
|
||||||
@ -484,24 +483,23 @@ class DevFS {
|
|||||||
final Map<Uri, DevFSContent> dirtyEntries = <Uri, DevFSContent>{};
|
final Map<Uri, DevFSContent> dirtyEntries = <Uri, DevFSContent>{};
|
||||||
|
|
||||||
int syncedBytes = 0;
|
int syncedBytes = 0;
|
||||||
if (bundle != null && !skipAssets) {
|
if (bundle != null) {
|
||||||
final String assetBuildDirPrefix = _asUriPath(getAssetBuildDirectory());
|
final String assetBuildDirPrefix = _asUriPath(getAssetBuildDirectory());
|
||||||
// We write the assets into the AssetBundle working dir so that they
|
// The tool writes the assets into the AssetBundle working dir so that they
|
||||||
// are in the same location in DevFS and the iOS simulator.
|
// are in the same location in DevFS and the iOS simulator.
|
||||||
final String assetDirectory = getAssetBuildDirectory();
|
final String assetDirectory = getAssetBuildDirectory();
|
||||||
bundle.entries.forEach((String archivePath, DevFSContent content) {
|
bundle.entries.forEach((String archivePath, DevFSContent content) {
|
||||||
|
if (!content.isModified || bundleFirstUpload) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
final Uri deviceUri = _fileSystem.path.toUri(_fileSystem.path.join(assetDirectory, archivePath));
|
final Uri deviceUri = _fileSystem.path.toUri(_fileSystem.path.join(assetDirectory, archivePath));
|
||||||
if (deviceUri.path.startsWith(assetBuildDirPrefix)) {
|
if (deviceUri.path.startsWith(assetBuildDirPrefix)) {
|
||||||
archivePath = deviceUri.path.substring(assetBuildDirPrefix.length);
|
archivePath = deviceUri.path.substring(assetBuildDirPrefix.length);
|
||||||
}
|
}
|
||||||
// Only update assets if they have been modified, or if this is the
|
dirtyEntries[deviceUri] = content;
|
||||||
// first upload of the asset bundle.
|
syncedBytes += content.size;
|
||||||
if (content.isModified || (bundleFirstUpload && archivePath != null)) {
|
if (archivePath != null && !bundleFirstUpload) {
|
||||||
dirtyEntries[deviceUri] = content;
|
assetPathsToEvict.add(archivePath);
|
||||||
syncedBytes += content.size;
|
|
||||||
if (archivePath != null && !bundleFirstUpload) {
|
|
||||||
assetPathsToEvict.add(archivePath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -818,7 +818,6 @@ class WebDevFS implements DevFS {
|
|||||||
String dillOutputPath,
|
String dillOutputPath,
|
||||||
bool fullRestart = false,
|
bool fullRestart = false,
|
||||||
String projectRootPath,
|
String projectRootPath,
|
||||||
bool skipAssets = false,
|
|
||||||
}) async {
|
}) async {
|
||||||
assert(trackWidgetCreation != null);
|
assert(trackWidgetCreation != null);
|
||||||
assert(generator != null);
|
assert(generator != null);
|
||||||
|
@ -0,0 +1,83 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:file/file.dart';
|
||||||
|
|
||||||
|
import '../src/common.dart';
|
||||||
|
import 'test_data/hot_reload_with_asset.dart';
|
||||||
|
import 'test_driver.dart';
|
||||||
|
import 'test_utils.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
Directory tempDir;
|
||||||
|
final HotReloadWithAssetProject project = HotReloadWithAssetProject();
|
||||||
|
FlutterRunTestDriver flutter;
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
tempDir = createResolvedTempDirectorySync('hot_reload_test.');
|
||||||
|
await project.setUpIn(tempDir);
|
||||||
|
flutter = FlutterRunTestDriver(tempDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDown(() async {
|
||||||
|
await flutter?.stop();
|
||||||
|
tryToDelete(tempDir);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('hot reload does not need to sync assets on the first reload', () async {
|
||||||
|
final Completer<void> onFirstLoad = Completer<void>();
|
||||||
|
final Completer<void> onSecondLoad = Completer<void>();
|
||||||
|
|
||||||
|
flutter.stdout.listen((String line) {
|
||||||
|
// If the asset fails to load, this message will be printed instead.
|
||||||
|
// this indicates that the devFS was not able to locate the asset
|
||||||
|
// after the hot reload.
|
||||||
|
if (line.contains('FAILED TO LOAD')) {
|
||||||
|
fail('Did not load asset: $line');
|
||||||
|
}
|
||||||
|
if (line.contains('LOADED DATA')) {
|
||||||
|
onFirstLoad.complete();
|
||||||
|
}
|
||||||
|
if (line.contains('SECOND DATA')) {
|
||||||
|
onSecondLoad.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
flutter.stdout.listen(print);
|
||||||
|
await flutter.run();
|
||||||
|
await onFirstLoad.future;
|
||||||
|
|
||||||
|
project.uncommentHotReloadPrint();
|
||||||
|
await flutter.hotReload();
|
||||||
|
await onSecondLoad.future;
|
||||||
|
});
|
||||||
|
|
||||||
|
testWithoutContext('hot restart does not need to sync assets on the first reload', () async {
|
||||||
|
final Completer<void> onFirstLoad = Completer<void>();
|
||||||
|
final Completer<void> onSecondLoad = Completer<void>();
|
||||||
|
|
||||||
|
flutter.stdout.listen((String line) {
|
||||||
|
// If the asset fails to load, this message will be printed instead.
|
||||||
|
// this indicates that the devFS was not able to locate the asset
|
||||||
|
// after the hot reload.
|
||||||
|
if (line.contains('FAILED TO LOAD')) {
|
||||||
|
fail('Did not load asset: $line');
|
||||||
|
}
|
||||||
|
if (line.contains('LOADED DATA')) {
|
||||||
|
onFirstLoad.complete();
|
||||||
|
}
|
||||||
|
if (line.contains('SECOND DATA')) {
|
||||||
|
onSecondLoad.complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
flutter.stdout.listen(print);
|
||||||
|
await flutter.run();
|
||||||
|
await onFirstLoad.future;
|
||||||
|
|
||||||
|
project.uncommentHotReloadPrint();
|
||||||
|
await flutter.hotRestart();
|
||||||
|
await onSecondLoad.future;
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import '../test_utils.dart';
|
||||||
|
import 'project.dart';
|
||||||
|
|
||||||
|
class HotReloadWithAssetProject extends Project {
|
||||||
|
@override
|
||||||
|
final String pubspec = '''
|
||||||
|
name: test
|
||||||
|
environment:
|
||||||
|
sdk: ">=2.0.0-dev.68.0 <3.0.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
flutter:
|
||||||
|
assets:
|
||||||
|
- pubspec.yaml
|
||||||
|
''';
|
||||||
|
|
||||||
|
@override
|
||||||
|
final String main = r'''
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
final ByteData message = const StringCodec().encodeMessage('AppLifecycleState.resumed');
|
||||||
|
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/lifecycle', message, (_) { });
|
||||||
|
runApp(MyApp());
|
||||||
|
}
|
||||||
|
|
||||||
|
class MyApp extends StatelessWidget {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
rootBundle.evict('pubspec.yaml');
|
||||||
|
rootBundle.load('pubspec.yaml').then((_) {
|
||||||
|
print('LOADED DATA');
|
||||||
|
}, onError: (dynamic error, StackTrace stackTrace) {
|
||||||
|
print('FAILED TO LOAD');
|
||||||
|
});
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
''';
|
||||||
|
|
||||||
|
void uncommentHotReloadPrint() {
|
||||||
|
final String newMainContents = main.replaceAll(
|
||||||
|
'LOADED DATA',
|
||||||
|
'SECOND DATA',
|
||||||
|
);
|
||||||
|
writeFile(fileSystem.path.join(dir.path, 'lib', 'main.dart'), newMainContents);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user