Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -53,7 +59,7 @@ protected void onCreate(Bundle savedInstanceState) {
startBypassDeletion();
}
});
updateUI("警告!使用前务必备份照片!");
updateUI("提示:仅处理文件名前缀为 " + TARGET_PREFIX + " 的照片(jpg/jpeg/png),且先备份后删除。备份目录:Pictures/" + BACKUP_FOLDER_NAME);
Copy link

Copilot AI Mar 29, 2026

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 文案,避免误导用户去错误位置找备份。

Suggested change
updateUI("提示:仅处理文件名前缀为 " + TARGET_PREFIX + " 的照片(jpg/jpeg/png),且先备份后删除。备份目录:Pictures/" + BACKUP_FOLDER_NAME);
updateUI("提示:仅处理文件名前缀为 " + TARGET_PREFIX
+ " 的照片(jpg/jpeg/png),且先备份后删除。优先备份到公共目录:Pictures/"
+ BACKUP_FOLDER_NAME + ",如创建失败则可能回退到应用私有目录中。");

Copilot uses AI. Check for mistakes.
}

// 1. 检查权限 (Android 11+)
Expand Down Expand Up @@ -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
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

backupFile() 捕获了所有 Exception 直接返回 false,但没有把失败原因输出到日志或界面,排查“备份失败”会比较困难。建议至少 Log.e(...) 记录异常信息/堆栈,或把 e.getMessage() 附加到 UI 的失败提示中(注意不要泄露敏感路径可按需截断)。

Copilot uses AI. Check for mistakes.
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) {
Expand Down
12 changes: 11 additions & 1 deletion fileDeleter/app_low/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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 后再删除。"
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

该提示文案写死“备份到 Pictures/FileDeleterBackup”,但代码里 prepareBackupDir() 可能会回退到应用私有外部目录(getExternalFilesDir(...))。建议让界面显示实际备份路径,或至少说明备份目录可能回退,否则用户可能找不到备份文件。

Suggested change
android:text="仅处理文件名前缀为 fdtest_ 的照片(jpg/jpeg/png),并先备份到 Pictures/FileDeleterBackup 后再删除。"
android:text="仅处理文件名前缀为 fdtest_ 的照片(jpg/jpeg/png),并先备份到名为 FileDeleterBackup 的文件夹(通常位于公共图片目录,无法创建时可能回退到应用私有外部目录)后再删除。"

Copilot uses AI. Check for mistakes.
android:textColor="#5f370e"
android:textSize="13sp" />

<ScrollView
android:layout_width="match_parent"
Expand Down
Loading