You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Executor阻塞主线程触发ANR:Android后台定时任务优化方案咨询

问题分析

你遇到的ANR问题,核心原因就是主线程执行了文件拆分、网络请求这类耗时操作:原来的onStartCommand里直接调用executeOperation(),这段代码会在Service的主线程(即UI线程)运行,而文件IO、网络请求都是慢操作,一旦阻塞主线程超过5秒,系统就会触发ANR。另外你用的scheduleAtFixedRate也有隐患——如果某次任务执行时间超过50分钟,后续任务会排队堆积,反而加重线程负担。

解决方案

1. 快速修复:把所有操作挪到后台线程

先修改当前的Service,确保首次执行的任务也进入线程池,同时把scheduleAtFixedRate换成scheduleWithFixedDelay(这个方法会在上一次任务结束后再等待50分钟执行下一次,适合你这种执行时长不确定的场景,不会出现任务堆积):

public class SendBackupData extends Service {
    private ScheduledExecutorService executorService;
    private static final WriteInLogFile wil = new WriteInLogFile();

    @Override
    public void onCreate() {
        super.onCreate();
        // 给线程命名,方便日志排查问题
        executorService = Executors.newSingleThreadScheduledExecutor(runnable -> {
            Thread thread = new Thread(runnable);
            thread.setName("BackupWorkerThread");
            thread.setDaemon(true);
            return thread;
        });
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // 首次执行也扔到后台线程,不占用主线程
        executorService.submit(this::executeOperation);
        // 用scheduleWithFixedDelay替代scheduleAtFixedRate,避免任务堆积
        executorService.scheduleWithFixedDelay(this::executeOperation, 50, 50, TimeUnit.MINUTES);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 优雅关闭线程池,防止内存泄漏
        executorService.shutdown();
        try {
            if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
                executorService.shutdownNow();
            }
        } catch (InterruptedException e) {
            executorService.shutdownNow();
        }
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    // 此方法逻辑无需修改,现在会在后台线程执行
    private void executeOperation() {
        Context context = getApplicationContext();
        try {
            SemaphoreBKP.getInstance(context).goIn();
            if (new Utilities(context).checkConnection()) {
                File dataDir = new File(new Constants(context).getArchiveFolder(false));
                File[] listOfFiles = dataDir.listFiles((dir, name) -> name.startsWith("REPORT") && name.endsWith(".zip"));
                if (listOfFiles != null) {
                    for (File listOfFile : listOfFiles) {
                        new FilesUtilities().splitBackupFiles(context, listOfFile.getName(), "arc");
                    }
                }

                File[] listOfFragments = dataDir.listFiles((dir, name) -> {
                    Pattern rexExp = Pattern.compile("(?:REPORT|\\G)(-[0-9]{2}-[0-9]{2}-[0-9]{4}-[a-zA-Z0-9]{8})(\\.zip)\\.[0-9]{3,6}\\.[0-9]{3,6}");
                    return name.startsWith("REPORT") && rexExp.matcher(name).matches();
                });

                if (listOfFragments != null) {
                    if (listOfFragments.length > 0) {
                        Arrays.sort(listOfFragments);
                        for (File currentFragment : listOfFragments) {
                            JSONObject jsonObject = new JSONObject();
                            jsonObject.put("filename", currentFragment.getName());
                            SendDataProcedure sendDataProcedure = new SendDataProcedure("1", "transmitBackupDevice", jsonObject.toString(), context);
                            boolean requestStatus = sendDataProcedure.postDataFromBackupPushDTS(currentFragment.getName().trim(), false);
                            if (requestStatus) {
                                if(currentFragment.exists()) {
                                    currentFragment.delete();
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            wil.WriteFile("SendBackupData - Exception: " + e.getMessage(), context);
        } finally {
            SemaphoreBKP.getInstance(context).goOut();
        }
    }
}

2. 长期稳定方案:改用WorkManager

从Android 8.0开始,系统对后台Service有严格限制,应用进入后台后不久Service就会被杀死。如果需要长期稳定执行每50分钟一次的任务,WorkManager是官方推荐的方案,它能自动适配系统规则,保证任务正常执行:

第一步:添加依赖

在Module级别的build.gradle中加入WorkManager依赖:

dependencies {
    implementation "androidx.work:work-runtime:2.8.1"
}

第二步:创建Worker类

把备份逻辑移到Worker中,Worker本身默认在后台线程执行:

public class BackupWorker extends Worker {
    private static final WriteInLogFile wil = new WriteInLogFile();

    public BackupWorker(@NonNull Context context, @NonNull WorkerParameters params) {
        super(context, params);
    }

    @NonNull
    @Override
    public Result doWork() {
        Context context = getApplicationContext();
        try {
            SemaphoreBKP.getInstance(context).goIn();
            if (new Utilities(context).checkConnection()) {
                File dataDir = new File(new Constants(context).getArchiveFolder(false));
                File[] listOfFiles = dataDir.listFiles((dir, name) -> name.startsWith("REPORT") && name.endsWith(".zip"));
                if (listOfFiles != null) {
                    for (File listOfFile : listOfFiles) {
                        new FilesUtilities().splitBackupFiles(context, listOfFile.getName(), "arc");
                    }
                }

                File[] listOfFragments = dataDir.listFiles((dir, name) -> {
                    Pattern rexExp = Pattern.compile("(?:REPORT|\\G)(-[0-9]{2}-[0-9]{2}-[0-9]{4}-[a-zA-Z0-9]{8})(\\.zip)\\.[0-9]{3,6}\\.[0-9]{3,6}");
                    return name.startsWith("REPORT") && rexExp.matcher(name).matches();
                });

                if (listOfFragments != null) {
                    if (listOfFragments.length > 0) {
                        Arrays.sort(listOfFragments);
                        for (File currentFragment : listOfFragments) {
                            JSONObject jsonObject = new JSONObject();
                            jsonObject.put("filename", currentFragment.getName());
                            SendDataProcedure sendDataProcedure = new SendDataProcedure("1", "transmitBackupDevice", jsonObject.toString(), context);
                            boolean requestStatus = sendDataProcedure.postDataFromBackupPushDTS(currentFragment.getName().trim(), false);
                            if (requestStatus) {
                                if(currentFragment.exists()) {
                                    currentFragment.delete();
                                }
                            }
                        }
                    }
                }
            }
            return Result.success();
        } catch (Exception e) {
            wil.WriteFile("BackupWorker - Exception: " + e.getMessage(), context);
            // 任务失败时,让WorkManager自动重试
            return Result.retry();
        } finally {
            SemaphoreBKP.getInstance(context).goOut();
        }
    }
}

第三步:调度周期性任务

在应用启动的地方(比如Application的onCreate或主Activity的onCreate)添加调度代码:

// 构建每50分钟执行一次的任务
PeriodicWorkRequest backupWorkRequest = new PeriodicWorkRequest.Builder(BackupWorker.class, 50, TimeUnit.MINUTES)
        // 可选:设置任务约束,比如仅在联网时执行
        .setConstraints(new Constraints.Builder()
                .setRequiredNetworkType(NetworkType.CONNECTED)
                .build())
        .build();

// 加入WorkManager队列,用唯一ID避免重复调度
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
        "BackupPeriodicTask",
        ExistingPeriodicWorkPolicy.KEEP,
        backupWorkRequest
);

选择WorkManager的原因

  • 自动适配各Android版本的后台限制,Android 8.0+也能稳定运行
  • 支持设置任务约束(如充电时、联网时执行),且能自动重试失败任务
  • 即使应用被杀死,系统也会在合适时机重启任务
关键注意事项
  • 所有耗时操作(文件IO、网络请求)绝对不能在主线程执行
  • 短期临时任务用线程池,长期周期性任务优先使用WorkManager
  • 线程池关闭要优雅,避免内存泄漏
  • 给网络请求设置超时时间,防止无限等待

内容的提问来源于stack exchange,提问作者Mattia

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 10:52:25