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

Android下载通知取消按钮异常:首次可用后续失效求助

Android下载通知取消按钮异常排查与解决

问题描述

开发的Android应用包含视频下载功能,下载时状态栏会展示带取消操作按钮的通知,但存在异常:首个通知的取消按钮可正常使用,后续通知的取消按钮处于禁用状态。

相关代码

DownloadMovie方法

private void DownloadMovie(String _link, String fileName)
{
   BottomSheetDialog bottomSheetDialog = new BottomSheetDialog(ProductDetails.this);
    bottomSheetDialog.setContentView(R.layout.dialog_startingdownload);
    bottomSheetDialog.setCancelable(true);

    MaterialButton reject_btn = bottomSheetDialog.findViewById(R.id.canceldw_btn);
    MaterialButton close_btn = bottomSheetDialog.findViewById(R.id.closeWindow);
    ProgressBar progressBar = bottomSheetDialog.findViewById(R.id.progressbar);
    TextView downloadingtxt = bottomSheetDialog.findViewById(R.id.txt);
    TextView progresstext = bottomSheetDialog.findViewById(R.id.progresstxt);
    TextView TVSizeDownload = bottomSheetDialog.findViewById(R.id.TVSizeDownload);
    TextView TVSizeTypeDownload = bottomSheetDialog.findViewById(R.id.TVSizeTypeDownload);

    TextView fileSizetxt = bottomSheetDialog.findViewById(R.id.fileSize);

    assert TVSizeDownload != null;
    TVSizeDownload.setVisibility(VISIBLE);
    assert TVSizeTypeDownload != null;
    TVSizeTypeDownload.setVisibility(VISIBLE);
    assert progresstext != null;
    progresstext.setVisibility(VISIBLE);
    assert downloadingtxt != null;
    downloadingtxt.setVisibility(VISIBLE);
    assert progressBar != null;
    progressBar.setVisibility(VISIBLE);
    assert reject_btn != null;
    reject_btn.setVisibility(VISIBLE);
    assert fileSizetxt != null;
    fileSizetxt.setVisibility(VISIBLE);
    assert close_btn != null;
    close_btn.setVisibility(VISIBLE);
      
    downloadManager = new ThinDownloadManager(1);
    retryPolicy = new DefaultRetryPolicy();
    NewDownloadClass myDownloadStatusListener = new 
    NewDownloadClass(downloadManager, progresstext, downloadingtxt, 
    TVSizeDownload, TVSizeTypeDownload, pathdata, fileName,
            bottomSheetDialog, progressBar, "film", titleName, 
            ProductDetails.this,lat,lng,product_id,user_id,token,os_ver,deviceName);

    Toast.makeText(this, "released"+"-"+downloadManager.isReleased(), Toast.LENGTH_SHORT).show();
    myDownloadStatusListener.delegate = this;

    isDownloadCompleted = false;

    Uri downloadUri = Uri.parse(_link);
    Uri destinationUri = Uri.parse(pathdata + fileName);
    retryPolicy = new DefaultRetryPolicy(5000, 3, 2f);
    downloadingtxt.setText("downloading");

    DownloadRequest downloadRequest = new DownloadRequest(downloadUri)
        .setRetryPolicy(retryPolicy)
        .setDestinationURI(destinationUri).setPriority(DownloadRequest.Priority.HIGH)
        .setDownloadContext("first")//Optional
        .setDeleteDestinationFileOnFailure(true)
        .setDownloadResumable(true)
        .setStatusListener(myDownloadStatusListener);

    if (downloadManager.query(downloadId) == DownloadManager.STATUS_NOT_FOUND) {
        downloadId = downloadManager.add(downloadRequest);
    }

    close_btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            bottomSheetDialog.dismiss();
        }
    });
    reject_btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            bottomSheetDialog.dismiss();
            downloadManager.cancelAll();

            if (new File(pathdata + fileName).exists())
                if (new File(pathdata + fileName).delete()) {
                    Log.e("DEL FILE FROM SD", "--ok");
                }

            if (notificationId != 0)
                if (mNotifyManager != null)
                    mNotifyManager.cancel(notificationId);

            isDownloadCompleted=true;
        }
    });

    bottomSheetDialog.show();
}

NewDownloadClass类

public class NewDownloadClass implements DownloadStatusListenerV1 {
    String type;
    BottomSheetDialog bottomSheetDialog;
    TextView downloadingtxt;
    TextView progresstext;
    TextView tvSizeDownload;
    TextView tvSizeTypeDownload;
    ProgressBar progressBar;
    public ThinDownloadManager downloadManager;
    public AsyncResponse delegate = null;

    ProgressDialog bar;
    static int i = 0;
    Activity activity;
    String link, id, name, path, unziplocate, state, notifName;

    private static String TAG = "MainActivity";
    private NotificationManager mNotifyManager;
    private NotificationCompat.Builder mBuilder;
    DownloadClass downloadClass;

    String NOTIFICATION_CHANNEL_ID = "my_channel_id_01";
    PendingIntent pendingIntent, dismissNotif;
    int notificationId = 0;
    String lat;
    String lng;
    String product_id;
    String user_id;
    String token;
    String os_ver;
    String deviceName;

    public NewDownloadClass(ThinDownloadManager downloadManager, TextView 
    progresstext, TextView downloadingtxt, TextView tvSizeDownload, 
    TextView tvSizeTypeDownload,
                        String pathdata, String fileName, 
    BottomSheetDialog bottomSheetDialog, ProgressBar progressBar, 
    String film, String titleName,
                        ProductDetails productDetails, String lat, 
    String lng, String product_id, String user_id, String token, 
    String os_ver, String deviceName) {
        this.downloadManager = downloadManager;

        this.bottomSheetDialog = bottomSheetDialog;
        this.downloadingtxt = downloadingtxt;
        this.progresstext = progresstext;
        this.progressBar = progressBar;
        this.tvSizeDownload = tvSizeDownload;
        this.tvSizeTypeDownload = tvSizeTypeDownload;
        this.path = pathdata;
        this.name = fileName;

        this.state = film;
        this.notifName = titleName;
        this.activity = productDetails;
        this.lat = lat;
        this.lng = lng;
        this.product_id = product_id;
        this.user_id = user_id;
        this.token = token;
        this.os_ver = os_ver;
        this.deviceName = deviceName;

        preparAndShowDownload();
    }

    private void preparAndShowDownload() {
        notificationId = new Random().nextInt();

        Intent intent = new Intent(activity, ProductDetails.class);
        intent.putExtra("lat", lat);
        intent.putExtra("lng", lng);
        intent.putExtra("product_id", product_id);
        intent.putExtra("user_id", user_id);
        intent.putExtra("token", token);
        intent.putExtra("os_ver", os_ver);
        intent.putExtra("deviceName", deviceName);

        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | 
        Intent.FLAG_ACTIVITY_CLEAR_TASK);
        if (android.os.Build.VERSION.SDK_INT >= 
        android.os.Build.VERSION_CODES.M) {
            pendingIntent = PendingIntent.getActivity(activity, 0, 
            intent, PendingIntent.FLAG_UPDATE_CURRENT);
        }

        mNotifyManager = (NotificationManager) 
        activity.getSystemService(Context.NOTIFICATION_SERVICE);
        mBuilder = null;
        if (android.os.Build.VERSION.SDK_INT >= 
            android.os.Build.VERSION_CODES.O) {
            int importance = NotificationManager.IMPORTANCE_DEFAULT;
            NotificationChannel notificationChannel = new 
            NotificationChannel(NOTIFICATION_CHANNEL_ID, notifName, 
            importance);
            
            mNotifyManager.createNotificationChannel(notificationChannel);
            mBuilder = new NotificationCompat.Builder(activity, 
            notificationChannel.getId());
        } else {
            mBuilder = new NotificationCompat.Builder(activity);
        }

        ButtonReceiver.downloadManager = downloadManager;

        Intent buttonIntent = new Intent(activity, ButtonReceiver.class);
        buttonIntent.putExtra("notificationId", notificationId);
        buttonIntent.putExtra("file", path + name);
        dismissNotif = PendingIntent.getBroadcast(activity, 0, 
        buttonIntent, PendingIntent.FLAG_CANCEL_CURRENT);

        mBuilder = mBuilder
                .setSmallIcon(R.drawable.ic_comica_about_logo)
                .setColor(ContextCompat.getColor(activity, 
                R.color.ocher))
                .setContentTitle("downloading")
                .setOnlyAlertOnce(true)
                .setProgress(0, 0, false)
                .setDefaults(Notification.DEFAULT_ALL)
                .setPriority(Notification.PRIORITY_DEFAULT)
                .setAutoCancel(true)
                .setContentIntent(pendingIntent)
                .addAction(R.drawable.ic_close_notif, "انصراف", 
                dismissNotif)
                .setLargeIcon(BitmapFactory.decodeResource(activity.getResources(), 
                R.drawable.ic_comica_about_logo));

        mNotifyManager.notify(notificationId, mBuilder.build());
    }

    @Override
    public void onDownloadComplete(DownloadRequest downloadRequest) {
        progressBar.setVisibility(View.GONE);
        downloadManager.release();
        bottomSheetDialog.dismiss();
        delegate.processFinish(true);

        downloadingtxt.setText("دانلود کامل شد.");

        mBuilder
                .setContentTitle("دانلود تمام شد")
                .setContentText("")
                .setProgress(0, 0, false);
        mNotifyManager.notify(notificationId, mBuilder.build());

        if (notificationId != 0)
            mNotifyManager.cancel(notificationId);
    }

    @Override
    public void onDownloadFailed(DownloadRequest downloadRequest, int 
    errorCode, String errorMessage) {
        progressBar.setProgress(0);
        if (activity != null && !activity.isFinishing())
            bottomSheetDialog.dismiss();
        downloadManager.cancelAll();

        File file = new File(path + name);
        if (file.exists())
            if (file.delete())
                Log.e("failed del  sd", "--ok--" + path + name);
            else
                Log.e("failed del  sd", "--NOT ok--" + path + name);

        if (notificationId != 0)
            mNotifyManager.cancel(notificationId);

        delegate.allowDownload(true);
    }

    @Override
    public void onProgress(DownloadRequest downloadRequest, long 
    totalBytes, long downloadedBytes, int progress) {
        progressBar.setProgress(progress);
        progresstext.setText(progress + "%");

        int size = (int) ((totalBytes / 1024) / 1024);
        String type;
        if (size < 1) {
            size = (int) (totalBytes / (1024));
            type = " KB";
        } else {
            type = " MB";
        }
        tvSizeDownload.setText(String.valueOf(size));
        tvSizeTypeDownload.setText(type);

        mBuilder
                .setSubText(progress + "%")
                .setProgress(100, progress, false);

        mNotifyManager.notify(notificationId, mBuilder.build());
    }
}

问题根源与修复方案

1. 核心问题:PendingIntent请求码重复

创建取消按钮的PendingIntent时,请求码固定为0,导致系统复用旧的PendingIntent实例,后续通知的按钮无法关联新的任务逻辑。

修复:使用唯一的notificationId作为请求码,确保每个通知的PendingIntent独立:

// 替换原dismissNotif创建代码
dismissNotif = PendingIntent.getBroadcast(activity, notificationId, buttonIntent, 
    PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);

Android 12+需添加FLAG_IMMUTABLE,避免安全风险。

2. 次要问题:重复创建NotificationChannel

每次下载都创建同名的NotificationChannel,虽系统会忽略重复操作,但可能导致通知行为异常。

修复:将Channel创建逻辑移至Application初始化阶段,仅执行一次:

// 在自定义Application的onCreate()中执行
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    String channelId = "my_channel_id_01";
    NotificationChannel channel = new NotificationChannel(channelId, "下载通知", 
        NotificationManager.IMPORTANCE_DEFAULT);
    NotificationManager notificationManager = getSystemService(NotificationManager.class);
    notificationManager.createNotificationChannel(channel);
}

之后在preparAndShowDownload()中直接使用已创建的channelId:

mBuilder = new NotificationCompat.Builder(activity, "my_channel_id_01");

3. 优化:避免全局静态DownloadManager引用

ButtonReceiver.downloadManager为静态全局变量,多任务时会被覆盖,导致后续任务无法正确取消。

修复:改用全局单例管理DownloadManager,在Application中初始化:

public class MyApplication extends Application {
    private static ThinDownloadManager downloadManager;
    
    @Override
    public void onCreate() {
        super.onCreate();
        downloadManager = new ThinDownloadManager(3); // 设置合理并发数
    }
    
    public static ThinDownloadManager getDownloadManager() {
        return downloadManager;
    }
}

然后在ButtonReceiver中获取单例取消任务:

public class ButtonReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        int notificationId = intent.getIntExtra("notificationId", 0);
        String filePath = intent.getStringExtra("file");
        
        // 取消通知
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.cancel(notificationId);
        
        // 删除文件
        File file = new File(filePath);
        if (file.exists()) file.delete();
        
        // 取消对应下载任务
        ThinDownloadManager downloadManager = MyApplication.getDownloadManager();
        if (downloadManager != null) downloadManager.cancelAll();
    }
}

4. 优化:notificationId生成逻辑

new Random().nextInt()可能生成重复ID,改用自增计数器保证唯一性:

// 在NewDownloadClass中添加静态计数器
private static int notificationCounter = 1;
// 生成notificationId时使用
notificationId = notificationCounter++;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 15:05:20