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

Android Download Manager请求自动启停及界面卡顿问题求助

批量PDF下载问题解决方案

问题梳理

你用Service结合DownloadManager做批量PDF下载,每批6个任务,完成一批再启下一批,但现在遇到两个问题:

  1. 下载时打开已完成的PDF,界面卡顿,要点击2-3次才响应
  2. 下载会自动停止,1-2分钟后又自动恢复

问题原因及修复方案

问题1:界面卡顿

核心原因

  • AsyncTask执行方式错误:直接调用downloadAllExecutor.doInBackground()会把下载初始化逻辑跑在主线程(Service的onStartCommand本身在主线程),文件数量多的时候,遍历、文件存在性判断这些操作会阻塞主线程,导致UI卡顿。
  • 数据填充逻辑缺失:新建空的toDownloadList后,没把从DocumentHelper获取的list数据存入,导致hashMap为空,后续下载逻辑混乱,间接引发UI线程异常。

修复步骤

  1. 修正AsyncTask启动方式:
    把直接调用doInBackground()改成execute(),让AsyncTask在后台线程正确执行:

    // 原错误代码
    // downloadAllExecutor.doInBackground();
    // 修改为
    downloadAllExecutor.execute();
    
  2. 补全数据填充逻辑:
    获取到list后,把数据存入toDownloadList,否则后续的positionList和documentList都是空的:

    if(isFromFragment){
        list = new ArrayList<>(DocumentHelper.getInstance(this).getDocumentHashmap().values());
    }
    else{
        list = new ArrayList<>(DocumentHelper.getInstance(this).getDocumentsOfParticularGroup().getValues());
    }
    
    // 新增:将list数据存入toDownloadList
    for (int i = 0; i < list.size(); i++) {
        toDownloadList.put(i, list.get(i));
    }
    
    hashMap = toDownloadList;
    positionList = new ArrayList<>(hashMap.keySet());
    documentList = new ArrayList<>(hashMap.values());
    
  3. 确保IO操作在后台执行:
    文件存在性判断属于IO操作,要保证它始终在后台线程执行(避免在主线程调用downloadFile方法)。


问题2:下载自动停止又恢复

核心原因

  • 分批逻辑未真正实现:只发送了第一批下载任务,但没有监听任务完成状态,后续批次无法启动;系统会限制后台下载任务数量,队列拥堵后会暂停,资源释放后又恢复。
  • 后台Service易被系统回收:Android 8.0+对后台Service限制严格,你的Service容易被系统杀死,之后系统根据START_REDELIVER_INTENT重启Service,导致下载看起来“停止后恢复”。
  • 变量逻辑混乱:downloadCount、itemSentCount等变量没有正确更新,无法触发下一批任务。

修复步骤

  1. 实现下载完成监听,真正分批执行:
    注册DownloadManager的下载完成广播,每完成一批任务再启动下一批:

    private BroadcastReceiver downloadCompleteReceiver;
    private DownloadManager downloadManager;
    private int currentBatchSize = 6; // 按需求设置每批数量
    private int totalDownloaded = 0;
    private int totalToDownload = 0;
    private int itemSentCount = 0;
    
    @Override
    public void onCreate() {
        super.onCreate();
        downloadManager = (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE);
    
        // 注册下载完成广播
        downloadCompleteReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                long downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
                if (downloadId != -1) {
                    totalDownloaded++;
                    // 检查当前批次是否完成,或所有任务是否完成
                    if (totalDownloaded % currentBatchSize == 0 || totalDownloaded == totalToDownload) {
                        sendNextBatch();
                    }
                }
            }
        };
        registerReceiver(downloadCompleteReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
    }
    
    // 初始化下载任务,发送第一批
    private void initDownloadBatch() {
        totalToDownload = documentList.size();
        totalDownloaded = 0;
        itemSentCount = 0;
        sendNextBatch();
    }
    
    // 发送下一批任务
    private void sendNextBatch() {
        if (itemSentCount >= totalToDownload) {
            // 所有任务完成,停止前台服务并销毁Service
            stopForeground(true);
            stopSelf();
            return;
        }
        int remaining = totalToDownload - itemSentCount;
        int batchSize = Math.min(currentBatchSize, remaining);
        for (int i = 0; i < batchSize; i++) {
            int index = itemSentCount + i;
            initiateDownload(documentList.get(index), positionList.get(index), totalToDownload);
        }
        itemSentCount += batchSize;
    }
    

    修改DownloadAllExecutor的doInBackground方法,调用初始化逻辑:

    private class DownloadAllExecutor extends AsyncTask<Void, Void,Void> {
        @Override
        protected Void doInBackground(Void...voids) {
            initDownloadBatch();
            return null;
        }
    }
    
  2. 将Service改为前台服务:
    避免系统杀死后台Service,添加前台通知:

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if(intent != null){
            // ... 原有数据处理逻辑 ...
            DownloadAllExecutor downloadAllExecutor = new DownloadAllExecutor();
            downloadAllExecutor.execute();
        }
    
        // 启动前台服务
        createForegroundNotification();
        return START_STICKY; // 或保留START_REDELIVER_INTENT,按需选择
    }
    
    private void createForegroundNotification() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel channel = new NotificationChannel("download_channel", "批量下载", NotificationManager.IMPORTANCE_LOW);
            NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            manager.createNotificationChannel(channel);
        }
    
        Notification notification = new NotificationCompat.Builder(this, "download_channel")
                .setContentTitle("PDF批量下载")
                .setContentText("已完成 " + totalDownloaded + "/" + totalToDownload)
                .setSmallIcon(R.drawable.ic_download) // 替换成你的应用图标
                .setPriority(NotificationCompat.PRIORITY_LOW)
                .build();
    
        startForeground(1, notification);
    }
    
  3. 优化DownloadManager请求配置:
    允许后台下载,避免系统因流量限制暂停:

    // 在downloadFile方法的Request中添加
    request.setAllowedOverMetered(true); // 允许计量网络下载
    request.setAllowedOverRoaming(true); // 允许漫游下载(按需开启)
    
  4. 清理广播接收器:
    在Service销毁时注销广播,避免内存泄漏:

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (downloadCompleteReceiver != null) {
            unregisterReceiver(downloadCompleteReceiver);
        }
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 07:39:22