如何用Android WorkManager在网络稳定时重发未成功发送的邮件?
解决WorkManager调度邮件重发的问题
看起来你已经找对了方向——用WorkManager来处理网络恢复后的邮件补发,但当前代码存在几个关键问题需要调整:比如Worker里直接调用和UI绑定的sendPortraitImage(里面有UI操作,Worker是后台线程不能操作UI)、没有持久化未发送任务的必要信息、邮件发送逻辑和UI逻辑耦合在一起。下面是具体的解决方案:
核心思路拆解
- 分离UI与业务逻辑:把邮件发送的核心逻辑从
sendPortraitImage中抽离出来,避免Worker触碰UI操作 - 持久化未发送任务:将邮件所需的参数(图片路径、收件人、主题等)保存到本地,Worker从本地读取这些信息执行发送
- 改造Worker执行逻辑:让Worker读取本地存储的待发送任务,调用独立的邮件发送方法,并处理发送结果(成功则删除记录,失败则触发重试)
具体代码实现
第一步:抽离邮件发送核心逻辑到工具类
创建一个独立的邮件发送工具类,用同步方式执行发送(因为Worker的doWork是同步线程,不能用AsyncTask):
public class EmailSender { // 同步发送邮件的方法,返回发送结果 public static boolean sendEmail(Context context, String recipient, String subject, String message, String imagePath) { try { // 这里实现你的邮件发送逻辑: // 1. 构建邮件内容(包含图片附件) // 2. 使用JavaMail或你的自定义mail类的同步版本执行发送 // 注意:替换成你原来mail.execute()里的核心代码,改成同步调用 // 示例伪代码: MailSync mailSync = new MailSync(context, recipient, subject, message, imagePath); return mailSync.sendSynchronously(); } catch (Exception e) { e.printStackTrace(); return false; } } }
第二步:改造sendPortraitImage方法
分离UI操作和邮件发送逻辑,发送失败时持久化任务并调度WorkManager:
public void sendPortraitImage(View vw){ // --- UI操作部分(仅在Activity中执行)--- send.setVisibility(View.GONE); buttonCamera.setVisibility(View.GONE); bitmap_view = ScreenShott.getInstance().takeScreenShotOfRootView(vw, null); saveImageToExternalStorage(bitmap_view); Toast.makeText(getApplicationContext(), "Saved successfully, Check gallery", Toast.LENGTH_SHORT).show(); // --- 邮件参数准备 --- String email = email_auth.EMAIL_FROM; String sub = TypeHeader + " - " + TypeNo + " - " + ImageNo; String msg = "Test AlmaPix"; String fileLoc = file.getAbsolutePath(); // --- 尝试立即发送,失败则保存任务并调度WorkManager --- boolean sendSuccess = EmailSender.sendEmail(this, email, sub, msg, fileLoc); if (!sendSuccess) { // 用SharedPreferences持久化任务(如果有多任务建议用Room数据库) savePendingEmailTask(email, sub, msg, fileLoc); // 调度WorkManager scheduleEmailSync(); } } // 保存待发送任务到本地 private void savePendingEmailTask(String email, String subject, String message, String imagePath) { SharedPreferences pendingPrefs = getSharedPreferences("pending_emails", MODE_PRIVATE); // 用时间戳作为唯一键,避免重复 String taskKey = "pending_" + System.currentTimeMillis(); Gson gson = new Gson(); PendingEmailTask task = new PendingEmailTask(email, subject, message, imagePath); pendingPrefs.edit().putString(taskKey, gson.toJson(task)).apply(); } // 定义任务实体类,用于序列化存储 static class PendingEmailTask { String email; String subject; String message; String imagePath; public PendingEmailTask(String email, String subject, String message, String imagePath) { this.email = email; this.subject = subject; this.message = message; this.imagePath = imagePath; } // 空构造函数用于Gson反序列化 public PendingEmailTask() {} }
第三步:改造WorkManager调度方法
设置合理的约束和重试策略:
private void scheduleEmailSync() { // 设置网络约束:仅当网络连接时执行 Constraints constraints = new Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build(); // 设置重试策略:指数退避,避免频繁重试 OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(MyWorker.class) .setConstraints(constraints) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, OneTimeWorkRequest.MIN_BACKOFF_MILLIS, TimeUnit.MILLISECONDS) .build(); WorkManager.getInstance(this).enqueue(workRequest); }
第四步:改造MyWorker类
让Worker读取本地待发送任务,执行发送并处理结果:
public class MyWorker extends Worker { public MyWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) { super(context, workerParams); } @NonNull @Override public Result doWork() { SharedPreferences pendingPrefs = getApplicationContext().getSharedPreferences("pending_emails", MODE_PRIVATE); Map<String, ?> pendingTasks = pendingPrefs.getAll(); Gson gson = new Gson(); boolean allTasksSuccess = true; // 遍历所有待发送任务 for (Map.Entry<String, ?> entry : pendingTasks.entrySet()) { String taskJson = (String) entry.getValue(); PendingEmailTask task = gson.fromJson(taskJson, PendingEmailTask.class); // 执行邮件发送 boolean sendSuccess = EmailSender.sendEmail(getApplicationContext(), task.email, task.subject, task.message, task.imagePath); if (sendSuccess) { // 发送成功,删除本地任务记录 pendingPrefs.edit().remove(entry.getKey()).apply(); } else { allTasksSuccess = false; // 发送失败,保留任务等待下次重试 } } // 发送通知告知用户结果 if (allTasksSuccess) { displayNotification("邮件同步完成", "所有未发送的邮件已成功发送"); return Result.SUCCESS; } else { displayNotification("邮件同步失败", "部分邮件发送失败,将在网络恢复后重试"); return Result.RETRY; } } private void displayNotification(String title, String content) { NotificationManager notificationManager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel("email_sync", "邮件同步", NotificationManager.IMPORTANCE_DEFAULT); notificationManager.createNotificationChannel(channel); } NotificationCompat.Builder notification = new NotificationCompat.Builder(getApplicationContext(), "email_sync") .setContentTitle(title) .setContentText(content) .setSmallIcon(R.mipmap.ic_launcher); notificationManager.notify(2, notification.build()); } // 内部静态类,用于反序列化任务 static class PendingEmailTask { String email; String subject; String message; String imagePath; public PendingEmailTask() {} } }
关键注意事项
- 替换邮件发送逻辑:
EmailSender里的sendSynchronously需要你实现同步的邮件发送逻辑,因为Worker不能用AsyncTask(Android 11+已废弃)。 - 多任务场景优化:如果有大量待发送任务,建议用Room数据库替代SharedPreferences,更适合存储结构化数据。
- 权限处理:确保应用拥有
INTERNET、READ_EXTERNAL_STORAGE(如果图片存在外部存储)等权限,Android 13+还需要处理POST_NOTIFICATIONS权限。 - 图片文件持久化:确保图片文件在Worker运行时仍然存在,建议将图片保存到应用私有目录,避免被系统清理。
内容的提问来源于stack exchange,提问作者TheAlmac2




