株式会社Neverのshoheiです。
株式会社Neverは「NEVER STOP CREATE 作りつづけること」をビジョンに掲げ、理想を実現するためにプロダクトを作り続ける組織です。モバイルアプリケーションの受託開発、技術支援、コンサルティングを行っております。アプリ開発のご依頼や開発面でのお困りの際はお気楽にお問合せください。
概要
Flutterでカメラや写真から画像を扱う際は、image_pickerを使うかと思います。image_pickerはとても簡単に使えて便利なパッケージですが、画像のキャッシュが端末内に残ってしまい、ストレージを圧迫することがあったので、その対策をまとめます。
image_picker
で選択した画像の保存先
iOSとAndroidではそれぞれ異なります。
OS | 保存先 | 保存先の取得方法 |
---|---|---|
iOS | tmp領域/Users/xxx/Library/Developer/CoreSimulator/Devices/8453C0AB-E640-4044-87E3-7A891199062E/data/Containers/Data/Application/1B2DA1B2-CFEA-4070-8F23-F83A7B8DF6CE/tmp/
|
Directory.systemTemp |
Android | cache領域/data/user/0/com.example.flutter_sample/cache/
|
getTemporaryDirectory() ※path_provider を利用 |
これらの領域はアプリを再起動してもファイルは残り続けます。
定期的に削除される領域のようですが12、削除されるまでファイルは残り続けるので、知らないうちにストレージを圧迫してしまいます。
対策
image_picker
で画像バイナリを取得後にキャッシュを削除する
image_picker
で画像を取得すると、XFile
形式で取得できます。これをFile
形式に変換してImage Widget
にセットして扱うことがあると思いますが、File
ではなく、Uint8List
で扱った後にFile(xxx).deleteSync()
でキャッシュを削除します。
Future<void> onPickImage() async {
final xFile = await ImagePicker().pickImage(source: ImageSource.gallery);
if (xFile == null) {
return;
}
final bytes = await xFile.readAsBytes(); // 👈 画像バイナリを取得
setState(() {
_imageBytes = bytes; // 👈 画像バイナリをセット
});
File(xFile.path).deleteSync(); // 👈 キャッシュを削除
}
@override
Widget build(BuildContext context) {
final imageBytes = _imageBytes;
return Scaffold(
...
body: Center(
child: imageBytes != null
? Image.memory(
imageBytes,
width: 300,
fit: BoxFit.contain,
)
: const SizedBox.shrink(),
),
...
}
ただし、画像バイナリをメモリ上に展開するので、画像を複数個扱う場合はメモリを圧迫する可能性があり注意が必要です。
キャッシュ削除を実装
キャッシュ削除を実装し、アプリ起動時やユーザーからの操作で実行します。
void deleteFiles() {
final list = _dir
.listSync(recursive: true, followLinks: false)
.whereType<File>()
.toList();
for (final file in list) {
file.deleteSync();
}
}
_dir
にiOSならDirectory.systemTemp
、AndroidならgetTemporaryDirectory
をセットします。
これらの処理をまとめたTmpCacheDirectory
は以下の通りです。なお、ファイル数が多くなると処理が重たくなるのでcompute
を使って新しいIsolateでキャッシュを削除します。
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:path_provider/path_provider.dart';
class TmpCacheDirectory {
TmpCacheDirectory._();
static late final Directory _dir;
static Future<void> configure() async {
if (Platform.isIOS) {
_dir = Directory.systemTemp;
} else {
_dir = await getTemporaryDirectory();
}
}
static Future<void> deleteFiles() async {
await compute<Directory, void>(
(dir) {
final list = dir
.listSync(recursive: true, followLinks: false)
.whereType<File>()
.toList();
for (final file in list) {
file.deleteSync();
debugPrint('delete: ${file.path}');
}
},
_dir,
);
}
}
void main() {
...
/// Delete Cache
Future(() async {
await TmpCacheDirectory.configure();
await TmpCacheDirectory.deleteFiles();
});
runApp(const App());
}
サンプルコードはこちら。
tmp
cache
領域には他のファイルも含まれる場合もあるので、画像だけを削除したい場合は、Fileパスの拡張子等から該当するファイル名だけをフィルタリングして削除してください。
もっと簡潔に消去する術はないのかな🤔