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

如何在Activity及应用销毁后保持Firebase上传任务持续执行?

实现Firebase上传任务后台持续运行(不受Activity生命周期影响)

这个需求我太熟悉了——之前做即时通讯APP的时候也碰到过类似的问题,Firebase Storage默认的上传任务绑定在UI线程的生命周期上,一旦Activity销毁或者APP被后台杀掉,上传就会中断。要实现真正的后台持续上传,咱们得把上传逻辑从Adapter里抽离出来,用Android官方推荐的WorkManager来托管任务,它能保证任务在系统重启、APP被杀后仍能继续执行。下面是具体的实现步骤:

1. 添加WorkManager依赖

首先确保你的项目已经引入了WorkManager库,在app模块的build.gradle中添加:

implementation "androidx.work:work-runtime:2.8.1"

2. 创建后台Worker类处理上传逻辑

我们需要创建一个继承自Worker的类,把上传Firebase Storage、更新Realtime Database和本地SQLite的逻辑都放在这里。Worker会在独立的后台线程运行,不受Activity生命周期影响。

public class ImageUploadWorker extends Worker {
    // 定义传递参数的键
    public static final String KEY_IMAGE_PATH = "image_path";
    public static final String KEY_THUMB_PATH = "thumb_path";
    public static final String KEY_FROM_ID = "from_id";
    public static final String KEY_RECEIVER_ID = "receiver_id";
    public static final String KEY_PUSH_KEY = "push_key";

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

    @NonNull
    @Override
    public Result doWork() {
        // 从输入参数中获取上传所需的数据
        String imagePath = getInputData().getString(KEY_IMAGE_PATH);
        String thumbPath = getInputData().getString(KEY_THUMB_PATH);
        String fromId = getInputData().getString(KEY_FROM_ID);
        String receiverId = getInputData().getString(KEY_RECEIVER_ID);
        String pushKey = getInputData().getString(KEY_PUSH_KEY);

        // 参数校验,为空则直接返回失败
        if (imagePath == null || thumbPath == null || fromId == null || receiverId == null || pushKey == null) {
            return Result.failure();
        }

        // 初始化Firebase实例
        FirebaseStorage storage = FirebaseStorage.getInstance();
        StorageReference imageRef = storage.getReference().child("messages_images").child(pushKey + ".jpg");
        StorageReference thumbRef = storage.getReference().child("messages_thumbs").child(pushKey + ".jpg");
        DatabaseReference dbRef = FirebaseDatabase.getInstance().getReference().child("Messages").child(fromId).child(receiverId).child(pushKey);

        try {
            // 1. 上传原图到Firebase Storage(同步等待完成,Worker线程中安全)
            UploadTask imageUploadTask = imageRef.putFile(Uri.fromFile(new File(imagePath)));
            UploadTask.TaskSnapshot imageSnapshot = Tasks.await(imageUploadTask);
            
            if (!imageSnapshot.isSuccessful()) {
                return Result.retry(); // 上传失败,触发重试
            }

            // 获取原图下载URL
            Uri imageDownloadUri = Tasks.await(imageRef.getDownloadUrl());
            String imageUrl = imageDownloadUri.toString();

            // 2. 上传缩略图到Firebase Storage
            UploadTask thumbUploadTask = thumbRef.putFile(Uri.fromFile(new File(thumbPath)));
            Tasks.await(thumbUploadTask);
            Uri thumbDownloadUri = Tasks.await(thumbRef.getDownloadUrl());
            String thumbUrl = thumbDownloadUri.toString();

            // 3. 更新Firebase Realtime Database
            Map<String, Object> updates = new HashMap<>();
            updates.put("imageurl", imageUrl);
            updates.put("thumburl", thumbUrl);
            Tasks.await(dbRef.updateChildren(updates));

            // 4. 更新本地SQLite数据库
            LocalDatabaseHelper dbHelper = new LocalDatabaseHelper(getApplicationContext());
            dbHelper.updateMessageMediaUrls(pushKey, imageUrl, thumbUrl);
            dbHelper.close();

            // 任务全部完成,返回成功
            return Result.success();
        } catch (ExecutionException | InterruptedException e) {
            e.printStackTrace();
            // 异常情况下重试
            return Result.retry();
        }
    }
}

3. 修改Adapter代码,提交WorkManager任务

把原来在onBindViewHolder里直接启动上传的逻辑,替换成向WorkManager提交任务的代码:

@Override
public void onBindViewHolder(@NonNull MessageViewHolder holder, int position) {
    Messages messages = messageList.get(position);
    // ... 其他UI绑定逻辑 ...

    // 替换原来的上传代码,改为提交WorkManager任务
    if (messages.getImagepath() != null && messages.getImageurl() == null) {
        // 构建任务参数
        Data inputData = new Data.Builder()
                .putString(ImageUploadWorker.KEY_IMAGE_PATH, messages.getImagepath())
                .putString(ImageUploadWorker.KEY_THUMB_PATH, messages.getThumbpath())
                .putString(ImageUploadWorker.KEY_FROM_ID, messages.getFromid())
                .putString(ImageUploadWorker.KEY_RECEIVER_ID, ReceiverID) // 确保这个变量能正确获取
                .putString(ImageUploadWorker.KEY_PUSH_KEY, messages.getPushkey())
                .build();

        // 创建一次性工作请求
        OneTimeWorkRequest uploadRequest = new OneTimeWorkRequest.Builder(ImageUploadWorker.class)
                .setInputData(inputData)
                .setConstraints(new Constraints.Builder()
                        .setRequiredNetworkType(NetworkType.CONNECTED) // 仅在有网络时执行
                        .build())
                .setBackoffCriteria(BackoffPolicy.LINEAR, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) // 线性重试策略
                .addTag("upload_" + messages.getPushkey()) // 添加标签方便后续管理
                .build();

        // 提交任务到WorkManager
        WorkManager.getInstance(holder.itemView.getContext()).enqueue(uploadRequest);
    }
}

4. 可选:监听任务状态更新UI

如果你需要在Activity/Fragment中监听上传完成的状态,从而更新UI或Adapter,可以通过WorkManager的LiveData来实现:

// 在Activity或Fragment中
WorkManager.getInstance(this).getWorkInfoByIdLiveData(uploadRequest.getId())
        .observe(this, workInfo -> {
            if (workInfo != null) {
                switch (workInfo.getState()) {
                    case SUCCEEDED:
                        // 上传成功,刷新Adapter
                        messageAdapter.notifyDataSetChanged();
                        break;
                    case FAILED:
                        // 上传失败,给用户提示
                        Toast.makeText(this, "上传失败", Toast.LENGTH_SHORT).show();
                        break;
                    case RUNNING:
                        // 上传中,可以显示进度(可选)
                        break;
                }
            }
        });

关键注意事项

  • 为什么用WorkManager?:它是Android Jetpack的核心组件之一,能自动适配不同Android版本的后台调度规则,即使APP被杀死、系统重启,任务也会在合适的时机重新执行。
  • 同步处理异步任务:在Worker中使用Tasks.await()来同步等待Firebase的异步操作,因为Worker的doWork()方法本身就在后台线程,同步调用不会阻塞主线程。
  • 参数传递:确保所有上传所需的数据都通过Data传递给Worker,避免直接持有Activity/Fragment的引用,防止内存泄漏。
  • 重试策略:通过setBackoffCriteria()配置重试策略,避免因临时网络问题导致上传失败。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:56:34