-
Notifications
You must be signed in to change notification settings - Fork 5
fix: 改为先备份再定向删除,降低误删风险 #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
| } | ||
|
Comment on lines
+139
to
147
|
||
| 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) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -17,7 +17,17 @@ | |||||
| style="@android:style/Widget.Button" | ||||||
| android:layout_width="match_parent" | ||||||
| android:layout_height="wrap_content" | ||||||
| android:text="开始删除照片" /> | ||||||
| android:text="开始备份后删除测试" /> | ||||||
|
|
||||||
| <TextView | ||||||
| android:layout_width="match_parent" | ||||||
| android:layout_height="wrap_content" | ||||||
| android:layout_marginTop="8dp" | ||||||
| android:background="#fff3cd" | ||||||
| android:padding="8dp" | ||||||
| android:text="仅处理文件名前缀为 fdtest_ 的照片(jpg/jpeg/png),并先备份到 Pictures/FileDeleterBackup 后再删除。" | ||||||
|
||||||
| android:text="仅处理文件名前缀为 fdtest_ 的照片(jpg/jpeg/png),并先备份到 Pictures/FileDeleterBackup 后再删除。" | |
| android:text="仅处理文件名前缀为 fdtest_ 的照片(jpg/jpeg/png),并先备份到名为 FileDeleterBackup 的文件夹(通常位于公共图片目录,无法创建时可能回退到应用私有外部目录)后再删除。" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
这里提示文案固定写死备份目录为
Pictures/FileDeleterBackup,但prepareBackupDir()在创建公共 Pictures 目录失败时会回退到getExternalFilesDir(...)(app 私有目录)。建议在拿到backupDir后用实际路径(或明确“可能回退到应用私有目录”)更新 UI 文案,避免误导用户去错误位置找备份。