From 8a698ddf0f28cabdbccabba9ca6eaa124927f9ef Mon Sep 17 00:00:00 2001
From: Bob Yue <85574806+BobYue-01@users.noreply.github.com>
Date: Sun, 29 Mar 2026 23:47:21 +0800
Subject: [PATCH] fix(app_low): backup targeted photos before delete test
Limit deletion scope to test photos only and require backup first.
- Replace recursive direct delete flow with backup-then-delete
processing in `MainActivity`.
- Only process files with prefix `fdtest_` and image extensions
`jpg/jpeg/png`; skip all other media.
- Add backup directory creation with fallback path and duplicate
filename handling to avoid overwrite.
- Update UI text to clearly warn users that only prefixed test photos
are affected and backups are saved to `Pictures/FileDeleterBackup`.
---
.../com/epcdiy/filedeleter/MainActivity.java | 99 +++++++++++++++----
.../src/main/res/layout/activity_main.xml | 12 ++-
2 files changed, 93 insertions(+), 18 deletions(-)
diff --git a/fileDeleter/app_low/src/main/java/com/epcdiy/filedeleter/MainActivity.java b/fileDeleter/app_low/src/main/java/com/epcdiy/filedeleter/MainActivity.java
index a264318..c49c06e 100644
--- a/fileDeleter/app_low/src/main/java/com/epcdiy/filedeleter/MainActivity.java
+++ b/fileDeleter/app_low/src/main/java/com/epcdiy/filedeleter/MainActivity.java
@@ -18,9 +18,15 @@
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
public class MainActivity extends AppCompatActivity {
+ private static final String TARGET_PREFIX = "fdtest_";
+ private static final String BACKUP_FOLDER_NAME = "FileDeleterBackup";
private TextView tvStatus;
private StringBuilder logBuilder = new StringBuilder();
private static final int REQUEST_CODE = 100;
@@ -53,7 +59,7 @@ protected void onCreate(Bundle savedInstanceState) {
startBypassDeletion();
}
});
- updateUI("警告!使用前务必备份照片!");
+ updateUI("提示:仅处理文件名前缀为 " + TARGET_PREFIX + " 的照片(jpg/jpeg/png),且先备份后删除。备份目录:Pictures/" + BACKUP_FOLDER_NAME);
}
// 1. 检查权限 (Android 11+)
@@ -87,43 +93,102 @@ private void startBypassDeletion() {
updateUI("未找到 DCIM 目录");
return;
}
+ File backupDir = prepareBackupDir();
+ if (backupDir == null) {
+ updateUI("备份目录创建失败,已取消处理");
+ return;
+ }
new Thread(() -> {
- traverseAndDelete(dcimDir);
- runOnUiThread(() -> Toast.makeText(this, "删除模拟完成", Toast.LENGTH_SHORT).show());
+ traverseAndBackupDelete(dcimDir, backupDir);
+ runOnUiThread(() -> Toast.makeText(this, "处理完成(仅特定命名照片)", Toast.LENGTH_SHORT).show());
}).start();
}
- // 4. 递归枚举并使用底层接口删除
- private void traverseAndDelete(File dir) {
+ private void traverseAndBackupDelete(File dir, File backupDir) {
File[] files = dir.listFiles();
if (files == null) return;
for (File file : files) {
if (file.isDirectory()) {
- traverseAndDelete(file); // 递归进入子文件夹
+ traverseAndBackupDelete(file, backupDir);
} else {
String fileName = file.getName();
- // 仅模拟删除图片和视频
- if (isMediaFile(fileName)) {
- // 核心操作:直接调用 java.io.File.delete()
- // 这不会触发 MediaStore 的数据库更新
- boolean deleted = file.delete();
-
- String msg = (deleted ? "[已抹除] " : "[失败] ") + file.getAbsolutePath();
+ if (isTargetPhoto(fileName)) {
+ boolean backedUp = backupFile(file, backupDir);
+ boolean deleted = backedUp && file.delete();
+ String msg;
+ if (!backedUp) {
+ msg = "[跳过-备份失败] " + file.getAbsolutePath();
+ } else {
+ msg = (deleted ? "[已备份并删除] " : "[删除失败] ") + file.getAbsolutePath();
+ }
updateUI(msg);
}
}
}
}
- private boolean isMediaFile(String name) {
+ private boolean isTargetPhoto(String name) {
+ if (name == null) return false;
String n = name.toLowerCase();
- if(n.contains("screenrecorder"))
- {
+ return n.startsWith(TARGET_PREFIX)
+ && (n.endsWith(".jpg") || n.endsWith(".jpeg") || n.endsWith(".png"));
+ }
+
+ private boolean backupFile(File source, File backupDir) {
+ File outFile = buildUniqueBackupFile(backupDir, source.getName());
+ try (InputStream inputStream = new FileInputStream(source);
+ OutputStream outputStream = new FileOutputStream(outFile)) {
+ copyStream(inputStream, outputStream);
+ return true;
+ } catch (Exception e) {
return false;
}
- return n.endsWith(".jpg") || n.endsWith(".jpeg") || n.endsWith(".png") || n.endsWith(".mp4");
+ }
+
+ private File prepareBackupDir() {
+ File picturesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
+ File backupDir = new File(picturesDir, BACKUP_FOLDER_NAME);
+ if (backupDir.exists() || backupDir.mkdirs()) {
+ return backupDir;
+ }
+
+ File appExternal = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
+ if (appExternal == null) {
+ return null;
+ }
+ File fallbackDir = new File(appExternal, BACKUP_FOLDER_NAME);
+ if (fallbackDir.exists() || fallbackDir.mkdirs()) {
+ return fallbackDir;
+ }
+ return null;
+ }
+
+ private File buildUniqueBackupFile(File backupDir, String fileName) {
+ File target = new File(backupDir, fileName);
+ if (!target.exists()) {
+ return target;
+ }
+
+ int dot = fileName.lastIndexOf('.');
+ String baseName = dot > 0 ? fileName.substring(0, dot) : fileName;
+ String extension = dot > 0 ? fileName.substring(dot) : "";
+ int index = 1;
+ while (target.exists()) {
+ target = new File(backupDir, baseName + "_" + index + extension);
+ index++;
+ }
+ return target;
+ }
+
+ private void copyStream(InputStream inputStream, OutputStream outputStream) throws Exception {
+ byte[] buffer = new byte[8192];
+ int len;
+ while ((len = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, len);
+ }
+ outputStream.flush();
}
private void updateUI(String msg) {
diff --git a/fileDeleter/app_low/src/main/res/layout/activity_main.xml b/fileDeleter/app_low/src/main/res/layout/activity_main.xml
index 5215f10..5f216e7 100644
--- a/fileDeleter/app_low/src/main/res/layout/activity_main.xml
+++ b/fileDeleter/app_low/src/main/res/layout/activity_main.xml
@@ -17,7 +17,17 @@
style="@android:style/Widget.Button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="开始删除照片" />
+ android:text="开始备份后删除测试" />
+
+