我想将视频列表下载到本地存储,然后在 Android 上的 Flutter 中自动播放。
video_player
包和一次一个 VideoPlayerController
来完成此操作,但是当我初始化新控制器时,视频之间会出现暂停。OutOfMemoryError
,并且应用程序会在第三个视频时崩溃。这是一个重现该问题的示例项目。由于很难找到要下载的视频的良好工作链接,因此本示例将通过将视频放入资产中然后将其复制到应用程序支持目录来模拟下载。
如果您需要下载一些视频并将其放入资产文件夹中,请使用 Pixibay 中的这些视频:
将以下依赖项添加到pubspec.yaml:
dependencies:
video_player: ^2.7.0
path_provider: ^2.1.0
path: ^1.8.3
在 lib 文件夹中创建一个名为 manager.dart 的文件并粘贴以下代码:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
class AppManager {
final isDownloadingNotifier = ValueNotifier<bool>(true);
late List<File> videoFiles;
Future<void> init() async {
await _copyFiles(); // can comment this line out if already copied
videoFiles = await _getDownloadedFiles();
isDownloadingNotifier.value = false;
}
// simulate downloading files by copying from assets
Future<void> _copyFiles() async {
final assetFiles = [
'assets/video01.mp4',
'assets/video02.mp4',
'assets/video03.mp4',
'assets/video04.mp4',
];
final directory = await getApplicationSupportDirectory();
for (var assetPath in assetFiles) {
print('loading $assetPath');
final data = await rootBundle.load(assetPath);
final bytes = data.buffer.asUint8List();
final filename = basename(assetPath);
final filePath = join(directory.path, filename);
final file = File(filePath);
await file.writeAsBytes(bytes, flush: true);
print('saved $filename');
}
}
Future<List<File>> _getDownloadedFiles() async {
final List<File> files = [];
final directory = await getApplicationSupportDirectory();
await for (var entity in directory.list()) {
if (entity is File && entity.path.endsWith('.mp4')) {
files.add(entity);
}
}
files.forEach((file) => print(file.path));
return files;
}
}
然后将 main.dart 替换为以下代码。 (此代码主要来自这个 Stack Overflow 答案,它在我从网络而不是本地存储播放视频列表时适用。)
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_playground/manager.dart';
import 'package:video_player/video_player.dart';
final manager = AppManager();
main() {
WidgetsFlutterBinding.ensureInitialized();
manager.init();
runApp(
MaterialApp(
home: ValueListenableBuilder<bool>(
valueListenable: manager.isDownloadingNotifier,
builder: (context, isDownloading, child) {
if (isDownloading) {
return const DownloadingPlaceholder();
}
return VideoPlayerDemo(videoFiles: manager.videoFiles);
},
),
),
);
}
class DownloadingPlaceholder extends StatefulWidget {
const DownloadingPlaceholder({super.key});
@override
State<DownloadingPlaceholder> createState() => _DownloadingPlaceholderState();
}
class _DownloadingPlaceholderState extends State<DownloadingPlaceholder> {
@override
Widget build(BuildContext context) {
return const Center(child: CircularProgressIndicator());
}
}
class VideoPlayerDemo extends StatefulWidget {
const VideoPlayerDemo({
super.key,
required this.videoFiles,
});
final List<File> videoFiles;
@override
State<VideoPlayerDemo> createState() => _VideoPlayerDemoState();
}
class _VideoPlayerDemoState extends State<VideoPlayerDemo> {
int index = 0;
double _position = 0;
double _buffer = 0;
bool _lock = true;
final Map<String, VideoPlayerController> _controllers = {};
final Map<int, VoidCallback> _listeners = {};
late final _files = widget.videoFiles;
@override
void initState() {
super.initState();
if (_files.isNotEmpty) {
_initController(0).then((_) {
_playController(0);
});
}
if (_files.length > 1) {
_initController(1).whenComplete(() => _lock = false);
}
}
VoidCallback _listenerSpawner(index) {
return () {
int dur = _controller(index).value.duration.inMilliseconds;
int pos = _controller(index).value.position.inMilliseconds;
int buf = _controller(index).value.buffered.last.end.inMilliseconds;
setState(() {
if (dur <= pos) {
_position = 0;
return;
}
_position = pos / dur;
_buffer = buf / dur;
});
if (dur - pos < 1) {
if (index < _files.length - 1) {
_nextVideo();
}
}
};
}
VideoPlayerController _controller(int index) {
final path = _files.elementAt(index).path;
return _controllers[path]!;
}
Future<void> _initController(int index) async {
final file = _files.elementAt(index);
var controller = VideoPlayerController.file(file);
final path = _files.elementAt(index).path;
_controllers[path] = controller;
await controller.initialize();
}
void _removeController(int index) {
_controller(index).dispose();
final path = _files.elementAt(index).path;
_controllers.remove(path);
_listeners.remove(index);
}
void _stopController(int index) {
_controller(index).removeListener(_listeners[index]!);
_controller(index).pause();
_controller(index).seekTo(const Duration(milliseconds: 0));
}
void _playController(int index) async {
if (!_listeners.keys.contains(index)) {
_listeners[index] = _listenerSpawner(index);
}
_controller(index).addListener(_listeners[index]!);
await _controller(index).play();
setState(() {});
}
void _previousVideo() {
if (_lock || index == 0) {
return;
}
_lock = true;
_stopController(index);
if (index + 1 < _files.length) {
_removeController(index + 1);
}
_playController(--index);
if (index == 0) {
_lock = false;
} else {
_initController(index - 1).whenComplete(() => _lock = false);
}
}
void _nextVideo() async {
if (_lock || index == _files.length - 1) {
return;
}
_lock = true;
_stopController(index);
if (index - 1 >= 0) {
_removeController(index - 1);
}
_playController(++index);
if (index == _files.length - 1) {
_lock = false;
} else {
_initController(index + 1).whenComplete(() => _lock = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Playing ${index + 1} of ${_files.length}"),
),
body: Stack(
children: <Widget>[
GestureDetector(
onLongPressStart: (_) => _controller(index).pause(),
onLongPressEnd: (_) => _controller(index).play(),
child: Center(
child: AspectRatio(
aspectRatio: _controller(index).value.aspectRatio,
child: Center(child: VideoPlayer(_controller(index))),
),
),
),
Positioned(
child: Container(
height: 10,
width: MediaQuery.of(context).size.width * _buffer,
color: Colors.grey,
),
),
Positioned(
child: Container(
height: 10,
width: MediaQuery.of(context).size.width * _position,
color: Colors.greenAccent,
),
),
],
),
floatingActionButton: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
FloatingActionButton(
onPressed: _previousVideo,
child: const Icon(Icons.arrow_back),
),
const SizedBox(width: 24),
FloatingActionButton(
onPressed: _nextVideo,
child: const Icon(Icons.arrow_forward),
),
],
),
);
}
}
当我在 Android 模拟器(在 macOS 上运行的 Android API 34 x86_64)上运行代码时,我得到以下日志输出(由于 Stack Overflow 问题长度限制而被截断),在第二个视频播放完毕后,该输出以
OutOfMemoryError
结尾:
Launching lib/main.dart on sdk gphone64 x86 64 in debug mode...
✓ Built build/app/outputs/flutter-apk/app-debug.apk.
Connecting to VM Service at ws://127.0.0.1:65153/ezV68AQSK94=/ws
I/flutter (13128): loading assets/video01.mp4
I/flutter (13128): saved video01.mp4
I/flutter (13128): loading assets/video02.mp4
D/EGL_emulation(13128): app_time_stats: avg=350.42ms min=10.67ms max=2356.31ms count=13
I/flutter (13128): saved video02.mp4
I/flutter (13128): loading assets/video03.mp4
D/EGL_emulation(13128): app_time_stats: avg=50.98ms min=15.60ms max=678.09ms count=23
I/flutter (13128): saved video03.mp4
I/flutter (13128): loading assets/video04.mp4
I/flutter (13128): saved video04.mp4
I/flutter (13128): /data/user/0/dev.suragch.flutter_playground/files/video01.mp4
I/flutter (13128): /data/user/0/dev.suragch.flutter_playground/files/video02.mp4
I/flutter (13128): /data/user/0/dev.suragch.flutter_playground/files/video03.mp4
I/flutter (13128): /data/user/0/dev.suragch.flutter_playground/files/video04.mp4
I/ExoPlayerImpl(13128): Init 4769990 [ExoPlayerLib/2.18.7] [emu64xa, sdk_gphone64_x86_64, Google, 34]
W/tter_playground(13128): Accessing hidden method Landroid/media/AudioTrack;->getLatency()I (unsupported, reflection, allowed)
I/Surface (13128): Surface::setFrameRate is deprecated, setFrameRate hint is dropped as destination is not SurfaceFlinger
I/ExoPlayerImpl(13128): Init 7570ff9 [ExoPlayerLib/2.18.7] [emu64xa, sdk_gphone64_x86_64, Google, 34]
...
D/CCodec (13128): encoding statistics level = 0
D/CCodec (13128): setup formats input: AMessage(what = 0x00000000) = {
D/CCodec (13128): int32_t height = 2160
D/CCodec (13128): int32_t level = 65536
D/CCodec (13128): int32_t max-input-size = 6291456
D/CCodec (13128): string mime = "video/avc"
D/CCodec (13128): int32_t profile = 65536
D/CCodec (13128): int32_t width = 3840
D/CCodec (13128): Rect crop(0, 0, 3839, 2159)
D/CCodec (13128): }
D/CCodec (13128): setup formats output: AMessage(what = 0x00000000) = {
D/CCodec (13128): int32_t android._color-format = 2135033992
D/CCodec (13128): int32_t android._video-scaling = 1
D/CCodec (13128): int32_t rotation-degrees = 0
D/CCodec (13128): int32_t color-standard = 6
D/CCodec (13128): int32_t color-range = 2
D/CCodec (13128): int32_t color-transfer = 3
D/CCodec (13128): int32_t sar-height = 1
D/CCodec (13128): int32_t sar-width = 1
D/CCodec (13128): Rect crop(0, 0, 3839, 2159)
D/CCodec (13128): int32_t width = 3840
D/CCodec (13128): int32_t height = 2160
D/CCodec (13128): int32_t max-height = 2160
D/CCodec (13128): int32_t max-width = 3840
D/CCodec (13128): string mime = "video/raw"
D/CCodec (13128): int32_t android._dataspace = 281411584
D/CCodec (13128): int32_t color-format = 2130708361
D/CCodec (13128): }
I/CCodecConfig(13128): query failed after returning 12 values (BAD_INDEX)
W/Codec2Client(13128): query -- param skipped: index = 1342179345.
W/Codec2Client(13128): query -- param skipped: index = 2415921170.
W/Codec2Client(13128): query -- param skipped: index = 1610614798.
W/Codec2Client(13128): query -- param skipped: index = 2684356609.
D/C2Store (13128): Using DMABUF Heaps
D/MediaCodec(13128): keep callback message for reclaim
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#392] Created input block pool with allocatorID 16 => poolID 17 - OK (0)
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#392] Query output surface allocator returned 0 params => BAD_INDEX (6)
W/Codec2Client(13128): query -- param skipped: index = 1342179345.
W/Codec2Client(13128): query -- param skipped: index = 2415921170.
W/Codec2Client(13128): query -- param skipped: index = 1610614798.
W/Codec2Client(13128): query -- param skipped: index = 2684356609.
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#334] Created input block pool with allocatorID 16 => poolID 18 - OK (0)
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#334] Query output surface allocator returned 0 params => BAD_INDEX (6)
I/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#334] Created output block pool with allocatorID 18 => poolID 57 - OK
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#334] Configured output block pool ids 57 => OK
D/Codec2-OutputBufferQueue(13128): remote graphic buffer migration 0/0
D/Codec2Client(13128): setOutputSurface -- failed to set consumer usage (6/BAD_INDEX)
D/Codec2Client(13128): setOutputSurface -- generation=13443074 consumer usage=0x900
D/Codec2Client(13128): Surface configure completed
I/DMABUFHEAPS(13128): Using DMA-BUF heap named: system
I/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#392] Created output block pool with allocatorID 18 => poolID 56 - OK
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#392] Configured output block pool ids 56 => OK
D/Codec2-OutputBufferQueue(13128): remote graphic buffer migration 0/0
D/Codec2Client(13128): setOutputSurface -- failed to set consumer usage (6/BAD_INDEX)
D/Codec2Client(13128): setOutputSurface -- generation=13443073 consumer usage=0x900
D/Codec2Client(13128): Surface configure completed
D/MediaCodecInfo(13128): NoSupport [sizeAndRate.cover, [email protected]] [c2.goldfish.h264.decoder, video/avc] [emu64xa, sdk_gphone64_x86_64, Google, 34]
W/MediaCodecRenderer(13128): Format exceeds selected codec's capabilities [id=1, mimeType=video/avc, codecs=avc1.640034, res=3840x2160, fps=59.996605, c2.goldfish.h264.decoder]
D/CCodecBufferChannel(13128): [c2.goldfish.h264.decoder#334] Ignoring stale input buffer done callback: last flush index = 0, frameIndex = 0
I/tter_playground(13128): Background concurrent copying GC freed 30030(2285KB) AllocSpace objects, 13(548KB) LOS objects, 49% free, 10MB/20MB, paused 902us,33us total 615.902ms
D/CCodecConfig(13128): c2 config diff is c2::u32 raw.color.matrix = 1
D/CCodecConfig(13128): c2::u32 raw.color.primaries = 1
D/CCodecConfig(13128): c2::u32 raw.color.transfer = 3
D/CCodecConfig(13128): c2::u32 raw.crop.height = 2160
D/CCodecConfig(13128): c2::u32 raw.crop.left = 0
D/CCodecConfig(13128): c2::u32 raw.crop.top = 0
D/CCodecConfig(13128): c2::u32 raw.crop.width = 3840
D/CCodecBuffers(13128): [c2.goldfish.h264.decoder#392:2D-Output] popFromStashAndRegister: at 1000000000000us, output format changed to AMessage(what = 0x00000000) = {
D/CCodecBuffers(13128): int32_t android._color-format = 2135033992
D/CCodecBuffers(13128): int32_t android._video-scaling = 1
D/CCodecBuffers(13128): int32_t rotation-degrees = 0
D/CCodecBuffers(13128): int32_t color-standard = 1
D/CCodecBuffers(13128): int32_t color-range = 2
D/CCodecBuffers(13128): int32_t color-transfer = 3
D/CCodecBuffers(13128): int32_t sar-height = 1
D/CCodecBuffers(13128): int32_t sar-width = 1
D/CCodecBuffers(13128): Rect crop(0, 0, 3839, 2159)
D/CCodecBuffers(13128): int32_t width = 3840
D/CCodecBuffers(13128): int32_t height = 2160
D/CCodecBuffers(13128): int32_t max-height = 2160
D/CCodecBuffers(13128): int32_t max-width = 3840
D/CCodecBuffers(13128): string mime = "video/raw"
D/CCodecBuffers(13128): int32_t android._dataspace = 260
D/CCodecBuffers(13128): int32_t color-format = 2130708361
D/CCodecBuffers(13128): }
D/CCodecConfig(13128): c2 config diff is c2::u32 raw.crop.height = 2160
D/CCodecConfig(13128): c2::u32 raw.crop.left = 0
D/CCodecConfig(13128): c2::u32 raw.crop.top = 0
D/CCodecConfig(13128): c2::u32 raw.crop.width = 3840
W/tter_playground(13128): Long monitor contention with owner ExoPlayer:Loader:ProgressiveMediaPeriod (13186) at com.google.android.exoplayer2.upstream.Allocation com.google.android.exoplayer2.upstream.DefaultAllocator.allocate()(DefaultAllocator.java:110) waiters=0 in int com.google.android.exoplayer2.upstream.DefaultAllocator.getTotalBytesAllocated() for 262ms
I/tter_playground(13128): Background young concurrent copying GC freed 382(192KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 24MB/24MB, paused 79us,11us total 485.850ms
...
I/tter_playground(13128): Background concurrent copying GC freed 474(99KB) AllocSpace objects, 0(0B) LOS objects, 18% free, 109MB/133MB, paused 8.636ms,143us total 160.927ms
════════ Exception caught by foundation library ════════════════════════════════
The following StateError was thrown while dispatching notifications for VideoPlayerController:
Bad state: No element
When the exception was thrown, this was the stack
#0 _Array.last (dart:core-patch/array.dart:56:5)
#1 _VideoPlayerDemoState._listenerSpawner.<anonymous closure>
#2 ChangeNotifier.notifyListeners
#3 ValueNotifier.value=
#4 VideoPlayerController.play
#5 _VideoPlayerDemoState._playController
#6 _VideoPlayerDemoState.initState.<anonymous closure>
<asynchronous suspension>
The VideoPlayerController sending notification was: VideoPlayerController#94dc3(VideoPlayerValue(duration: 0:00:12.713000, size: Size(3840.0, 2160.0), position: 0:00:00.000000, caption: Caption(number: 0, start: 0:00:00.000000, end: 0:00:00.000000, text: ), captionOffset: 0:00:00.000000, buffered: [], isInitialized: true, isPlaying: true, isLooping: false, isBuffering: false, volume: 1.0, playbackSpeed: 1.0, errorDescription: null))
════════════════════════════════════════════════════════════════════════════════
I/Surface (13128): Surface::setFrameRate is deprecated, setFrameRate hint is dropped as destination is not SurfaceFlinger
D/BufferPoolAccessor2.0(13128): bufferpool2 0x7fa797d7a9b8 : 5(31457280 size) total buffers - 5(31457280 size) used buffers - 20/25 (recycle/alloc) - 5/24 (fetch/transfer)
I/tter_playground(13128): Background young concurrent copying GC freed 514(115KB) AllocSpace objects, 0(0B) LOS objects, 0% free, 135MB/135MB, paused 792us,11us total 152.842ms
...
W/tter_playground(13128): native: #151 pc 003784c4 /apex/com.android.art/lib64/libart.so (art_quick_invoke_stub+756) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128): native: #152 pc 003c535c /apex/com.android.art/lib64/libart.so (art::ArtMethod::Invoke+204) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128): native: #153 pc 0056ddb0 /apex/com.android.art/lib64/libart.so (bool art::interpreter::DoCall<false>+2080) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128): native: #154 pc 0039ae32 /apex/com.android.art/lib64/libart.so (void art::interpreter::ExecuteSwitchImplCpp<false>+16546) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128): native: #155 pc 003937a5 /apex/com.android.art/lib64/libart.so (ExecuteSwitchImplAsm+5) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128): native: #156 pc 0050c774 /system/framework/framework.jar (com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run)
W/tter_playground(13128): native: #157 pc 00565ff7 /apex/com.android.art/lib64/libart.so (art::interpreter::Execute +647) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128): native: #158 pc 0091efc5 /apex/com.android.art/lib64/libart.so (artQuickToInterpreterBridge+901) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128): native: #159 pc 00391bdc /apex/com.android.art/lib64/libart.so (art_quick_to_interpreter_bridge+140) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128): native: #160 pc 00d1578e /data/misc/apexdata/com.android.art/dalvik-cache/x86_64/boot.oat (com.android.internal.os.ZygoteInit.main+3038)
W/tter_playground(13128): native: #161 pc 00378826 /apex/com.android.art/lib64/libart.so (art_quick_invoke_static_stub+806) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128): native: #162 pc 003c538f /apex/com.android.art/lib64/libart.so (art::ArtMethod::Invoke+255) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128): native: #163 pc 007f123f /apex/com.android.art/lib64/libart.so (art::JValue art::InvokeWithVarArgs<art::ArtMethod*>+399) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128): native: #164 pc 006a7e9c /apex/com.android.art/lib64/libart.so (art::JNI<true>::CallStaticVoidMethodV+668) (BuildId: 7bb8b7e51aa2973cf22167cc6c483bef)
W/tter_playground(13128): native: #165 pc 000de888 /system/lib64/libandroid_runtime.so (_JNIEnv::CallStaticVoidMethod+136) (BuildId: 140296c7da6bfd8e3c406c87d6aeb4e4)
W/tter_playground(13128): native: #166 pc 000eb330 /system/lib64/libandroid_runtime.so (android::AndroidRuntime::start+896) (BuildId: 140296c7da6bfd8e3c406c87d6aeb4e4)
W/tter_playground(13128): native: #167 pc 00002fa6 /system/bin/app_process64 (main+1622) (BuildId: f11cda2b6bb6bff1e502077a2f3e6cf7)
W/tter_playground(13128): native: #168 pc 00052a3f /apex/com.android.runtime/lib64/bionic/libc.so (__libc_init+95) (BuildId: fdd97bce94d2a5e6408dc0732250fe4c)
W/tter_playground(13128): at java.lang.Throwable.nativeFillInStackTrace(Native method)
W/tter_playground(13128): at java.lang.Throwable.fillInStackTrace(Throwable.java:819)
W/tter_playground(13128): at java.lang.Throwable.<init>(Throwable.java:286)
W/tter_playground(13128): at java.lang.Error.<init>(Error.java:71)
W/tter_playground(13128): at java.lang.VirtualMachineError.<init>(VirtualMachineError.java:54)
W/tter_playground(13128): at java.lang.OutOfMemoryError.<init>(OutOfMemoryError.java:58)
W/tter_playground(13128): at java.lang.Throwable.nativeGetStackTrace(Native method)
W/tter_playground(13128): at java.lang.Throwable.getOurStackTrace(Throwable.java:869)
W/tter_playground(13128): at java.lang.Throwable.printStackTrace(Throwable.java:686)
W/tter_playground(13128): at java.lang.Throwable.printStackTrace(Throwable.java:753)
W/tter_playground(13128): at android.util.Log.getStackTraceString(Log.java:388)
W/tter_playground(13128): at io.flutter.plugins.videoplayer.Messages.wrapError(Messages.java:53)
W/tter_playground(13128): at io.flutter.plugins.videoplayer.Messages$AndroidVideoPlayerApi.lambda$setup$8(Messages.java:890)
W/tter_playground(13128): at io.flutter.plugins.videoplayer.Messages$AndroidVideoPlayerApi$$ExternalSyntheticLambda10.onMessage(unavailable:2)
W/tter_playground(13128): at io.flutter.plugin.common.BasicMessageChannel$IncomingMessageHandler.onMessage(BasicMessageChannel.java:219)
W/tter_playground(13128): at io.flutter.embedding.engine.dart.DartMessenger.invokeHandler(DartMessenger.java:295)
W/tter_playground(13128): at io.flutter.embedding.engine.dart.DartMessenger.lambda$dispatchMessageToQueue$0$io-flutter-embedding-engine-dart-DartMessenger(DartMessenger.java:322)
W/tter_playground(13128): at io.flutter.embedding.engine.dart.DartMessenger$$ExternalSyntheticLambda0.run(unavailable:12)
W/tter_playground(13128): at android.os.Handler.handleCallback(Handler.java:958)
W/tter_playground(13128): at android.os.Handler.dispatchMessage(Handler.java:99)
W/tter_playground(13128): at android.os.Looper.loopOnce(Looper.java:205)
W/tter_playground(13128): at android.os.Looper.loop(Looper.java:294)
W/tter_playground(13128): at android.app.ActivityThread.main(ActivityThread.java:8176)
W/tter_playground(13128): at java.lang.reflect.Method.invoke(Native method)
W/tter_playground(13128): at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
W/tter_playground(13128): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
I/tter_playground(13128): Waiting for a blocking GC Alloc
I/tter_playground(13128): Alloc young concurrent copying GC freed 0(0B) AllocSpace objects, 0(0B) LOS objects, 0% free, 191MB/192MB, paused 1.478ms,13us total 1.490s
I/tter_playground(13128): WaitForGcToComplete blocked Background on Alloc for 1.551s
I/tter_playground(13128): Forcing collection of SoftReferences for 1408B allocation
I/tter_playground(13128): Starting a blocking GC Alloc
I/tter_playground(13128): Waiting for a blocking GC Alloc
I/tter_playground(13128): Waiting for a blocking GC Alloc
I/tter_playground(13128): Clamp target GC heap from 215MB to 192MB
I/tter_playground(13128): Alloc concurrent copying GC freed 114(7144B) AllocSpace objects, 0(0B) LOS objects, 0% free, 191MB/192MB, paused 71us,16us total 77.611ms
I/tter_playground(13128): WaitForGcToComplete blocked Alloc on Alloc for 1.619s
I/tter_playground(13128): Starting a blocking GC Alloc
I/tter_playground(13128): Starting a blocking GC Alloc
I/tter_playground(13128): Starting a blocking GC Alloc
I/tter_playground(13128): Forcing collection of SoftReferences for 32B allocation
I/tter_playground(13128): Starting a blocking GC Alloc
I/tter_playground(13128): Waiting for a blocking GC Alloc
W/tter_playground(13128): Throwing OutOfMemoryError "Failed to allocate a 1408 byte allocation with 3696 free bytes and 3696B until OOM, target footprint 201326592, growth limit 201326592; giving up on allocation because <1% of heap free after GC." (VmSize 19629484 kB)
I/Process (13128): Sending signal. PID: 13128 SIG: 9
Lost connection to device.
Exited
以下是模拟器内存设置:
有人能够重现这个问题吗?有什么解决办法吗?
我在 Android 模拟器中运行了相同的代码,它按预期工作。我只是想说你不必依赖模拟器/模拟器。它们不是实际的设备。在物理设备上尝试一下,因为仿真器/模拟器和真实设备上的结果可能会有所不同。
仍然面临同样的问题吗?这是另一个解决方案:
打开AndroidManifest.xml并设置属性
android:largeHeap="true"
在
<application>
标签中。
那么,让我解释一下大堆的概念:
应用程序的进程是否是使用大型 Dalvik 堆创建的。这适用于为应用程序创建的所有进程。它仅适用于加载到进程中的第一个应用程序。如果您使用共享用户 ID 来让多个应用程序使用一个进程,则它们都必须一致地使用此选项,以避免出现不可预测的结果。 大多数应用程序不需要这样做,而是专注于减少总体内存使用量以提高性能。启用此功能也不能保证可用内存的固定增加,因为某些设备受到其总可用内存的限制。
参考链接:https://developer.android.com/guide/topics/manifest/application-element