前言
官方文档
官方推荐 - 前台服务、后台服务都可以使用WorkManger来实现
案例一
语言:JAVA
实现要求
一步步实现一个图片压缩APP
创建项目
添加WorkManager依赖
参考文章
添加到builder.gradle, sync一下
val workVersion = "2.9.0"
implementation("androidx.work:work-runtime:$workVersion")
接收share来的图片数据
要实现这种效果,需要在AndroidManifest.xml声明标签,过滤intent
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
将Activity改为singleTop
运行APP。打开手机相册,分享一张图片,会重新使用这个Activity
android:launchMode="singleTop"
在onNewIntent接收数据
定义Worker
你需要做什么事情,你就定义一个Worker,指派它做事,做什么事,就在dowork里定义
dowork有三个返回,见图
传入Uri到Worker
参考这里
通过inputdata传入
Uri -> Bitmap
若有爆红位置
压缩图片直到图片的大小不超过XKB
传入图片的大小阀值
不断循环压缩,一直到图片的大小不超过20KB
生成文件
返回图片地址数据
构建Data,传值回去
监听Worker结果
在获取到WorkManager这个实例后
通过getWorkInfoByIdLiveData方法来监听workerrequest状态及结果返回
显示结果
在布局中,加入一张图片
Android版本 兼容问题
兼容低版本的Android系统
inputStream.readAllBytes() 需要在API 33之后使用
所以需要更改写法,来使低版本的Android系统使用
bytes = new byte[inputStream.available()];
inputStream.read(bytes);
运行项目
完整代码
// ImageCompressionWorker
package com.test.imagecompressionworkerapplication;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.work.Data;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
public class ImageCompressionWorker extends Worker {
private final String TAG = "worker - log";
public static final String KEY_CONTENT_URI = "KEY_CONTENT_URI";
public static final String KEY_COMPRESSION_THRESHOLD = "KEY_COMPRESSION_THRESHOLD";
public static final String KEY_RESULT_PATH = "KEY_RESULT_PATH";
private final WorkerParameters workerParameters;
private final Context appContext;
public ImageCompressionWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
appContext = context;
workerParameters = workerParams;
}
@NonNull
@Override
public Result doWork() {
Data inputData = workerParameters.getInputData();
String uriStr = inputData.getString(KEY_CONTENT_URI);
long size = inputData.getLong(KEY_COMPRESSION_THRESHOLD, 0L);
assert uriStr != null;
Uri uri = Uri.parse(uriStr);
byte[] bytes;
try {
InputStream inputStream = appContext.getContentResolver().openInputStream(uri);
assert inputStream != null;
// byte[] bytes_ = inputStream.readAllBytes();
bytes = new byte[inputStream.available()];
inputStream.read(bytes);
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
inputStream.close();
int quality = 100;
byte[] byteArray;
do {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
byteArray = byteArrayOutputStream.toByteArray();
quality -= Math.round(quality * 0.1);
} while (byteArray.length > size && quality > 5);
File file = new File(appContext.getCacheDir(), workerParameters.getId() + ".jpg");
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(byteArray);
fileOutputStream.close();
String absolutePath = file.getAbsolutePath();
Data outputData = new Data.Builder()
.putString(KEY_RESULT_PATH, absolutePath)
.build();
return Result.success(outputData);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// MainActivity.java
package com.test.imagecompressionworkerapplication;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.Observer;
import androidx.work.Data;
import androidx.work.OneTimeWorkRequest;
import androidx.work.OutOfQuotaPolicy;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.widget.ImageView;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private final String TAG = "mainActivity - log";
private WorkManager workManager;
private ImageView sharedImage;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
workManager = WorkManager.getInstance(this);
bindView();
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Uri uri;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
uri = intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri.class);
} else {
uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
}
assert uri != null;
long size = 1024 * 20L;
Data inputData = new Data.Builder()
.putString(ImageCompressionWorker.KEY_CONTENT_URI, uri.toString())
.putLong(ImageCompressionWorker.KEY_COMPRESSION_THRESHOLD, size)
.build();
OneTimeWorkRequest oneTimeWorkRequest = new OneTimeWorkRequest.Builder(ImageCompressionWorker.class)
.setInputData(inputData)
// .setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
.build();
workManager.enqueue(oneTimeWorkRequest);
UUID id = oneTimeWorkRequest.getId();
workManager.getWorkInfoByIdLiveData(id).observe(this, workInfo -> {
if (workInfo.getState() == WorkInfo.State.SUCCEEDED) {
Data outputData = workInfo.getOutputData();
String filePath = outputData.getString(ImageCompressionWorker.KEY_RESULT_PATH);
Bitmap bitmap = BitmapFactory.decodeFile(filePath);
sharedImage.setImageBitmap(bitmap);
}
});
}
private void bindView() {
sharedImage = findViewById(R.id.sharedImage);
}
}
更多内容
这一节,有些流水账,看看就好 可以直接看官方文档吧 官方文档
执行加急工作
配额策略
加急工作 + CoroutineWorker + 通知
加急工作需要配合通知使用,否则会报错
将之前的继承Worker改为CoroutineWorker
重写方法getForegroundInfo
@Nullable
@Override
public Object getForegroundInfo(@NonNull Continuation<? super ForegroundInfo> $completion) {
return new ForegroundInfo(NOTIFICATION_ID, createNotification());
}
private Notification createNotification() {
String CHANNEL_ID = "compressor_channel_id";
String CHANNEL_NAME = "压缩图片通知通道";
String NOTIFICATION_TITLE = "你有一个程序在压缩图片";
int importance = NotificationManager.IMPORTANCE_HIGH;
NotificationChannel notificationChannel;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notificationChannel = new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, importance);
NotificationManager notificationManager = getSystemService(appContext, NotificationManager.class);
assert notificationManager != null;
notificationManager.createNotificationChannel(notificationChannel);
}
String NOTIFICATION_TEXT = "压缩中...";
Intent intent = new Intent(appContext, ImageCompressionWorker.class);
PendingIntent pendingIntent = PendingIntent.getActivity(appContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
return new NotificationCompat.Builder(appContext, CHANNEL_ID)
.setContentTitle(NOTIFICATION_TITLE)
.setContentText(NOTIFICATION_TEXT)
.setSmallIcon(R.drawable.ic_launcher_background)
.setContentIntent(pendingIntent)
.build();
}
通知
在Android 12 之前的版本运行,会有通知显示;
通知需要申请权限
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
private static final String[] PERMISSION_REQUIRED = new String[]{
Manifest.permission.POST_NOTIFICATIONS
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
workManager = WorkManager.getInstance(this);
bindView();
if (!checkAllPermissions()) {
requestPermissions(PERMISSION_REQUIRED, REQUEST_CODE);
}
}
private boolean checkAllPermissions() {
for (String permission : PERMISSION_REQUIRED) {
int permissionCheck = ContextCompat.checkSelfPermission(this, permission);
if (permissionCheck == PackageManager.PERMISSION_DENIED) {
return false;
}
}
return true;
}
运行项目
压缩过程很快,压缩完成之后,通知关闭了
调度定期工作
每间隔一小时的最后15分钟工作一次
为了方便测试,这里使用15分钟一次
WorkRequest uploadRequest = new PeriodicWorkRequest
.Builder(PeriodicUploadLogWorker.class, 15, TimeUnit.MINUTES, 15, TimeUnit.MINUTES)
.build();
WorkManager workManager = WorkManager.getInstance(this);
workManager.enqueue(uploadRequest);
public class PeriodicUploadLogWorker extends Worker {
private final String TAG = "periodic_upload_log";
public PeriodicUploadLogWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
uploadLog();
return Result.success();
}
private void uploadLog() {
Log.i(TAG, String.valueOf(System.currentTimeMillis()));
}
}
工作约束
将工作延迟到满足最佳条件时运行
延迟工作
重试和退避政策
标记工作
分配输入数据
setInputData 传入数据
getInputData 获取数据
唯一工作
查询工作
按id、name、tag查询
WorkQuery
取消工作
链接工作
将每个Worker链接起来,按顺序执行。
还可以定义合并器
默认合并器,变量名一致的,值采用最新的覆盖前者
第二种,会保留返回的结果,会合并相同变量名到一个数组中
案例二
实现下载器,并监听下载进度
界面
定义Worker
在官方案例的前提下,进行完善
下载download
下载进度
授予权限
开始工作并监听
完整代码
MainActivity.java
package com.test.downloadworkerapplication;
import static android.content.Context.NOTIFICATION_SERVICE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
import static com.test.downloadworkerapplication.MainActivity.MY_LOG_TAG;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.work.Data;
import androidx.work.ForegroundInfo;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class DownloadWorker extends Worker {
public static final String KEY_INPUT_URL = "KEY_INPUT_URL";
public static final String KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME";
public static final String KEY_FILE_URI = "KEY_FILE_URI";
public static final String PROGRESS = "PROGRESS";
private final NotificationManager notificationManager;
public DownloadWorker(
@NonNull Context context,
@NonNull WorkerParameters parameters) {
super(context, parameters);
notificationManager = (NotificationManager)
context.getSystemService(NOTIFICATION_SERVICE);
Data data = new Data.Builder().putInt(PROGRESS, 0).build();
setProgressAsync(data);
}
@NonNull
@Override
public Result doWork() {
Data inputData = getInputData();
String inputUrl = inputData.getString(KEY_INPUT_URL);
String outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME);
String progress = "Starting Download";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
setForegroundAsync(createForegroundInfo(progress));
}
Uri uri = download(inputUrl, outputFile);
Data progressData2 = new Data.Builder().putInt(PROGRESS, 100).build();
setProgressAsync(progressData2);
Data data = new Data.Builder()
.putString(KEY_FILE_URI, uri.toString())
.build();
return Result.success(data);
}
private Uri download(String inputUrl, String outputFile) {
// Downloads a file and updates bytes read
// Calls setForegroundAsync(createForegroundInfo(myProgress))
// periodically when it needs to update the ongoing Notification.
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
String myProgress = "downloading...";
setForegroundAsync(createForegroundInfo(myProgress));
}
long fileSize = FileSizeFetcher.getFileSize(inputUrl);
URL url = new URL(inputUrl);
InputStream inputStream = url.openStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
int downloadLen = 0;
while ((len = inputStream.read(buffer)) != -1) {
downloadLen += len;
byteArrayOutputStream.write(buffer, 0, len);
/// 下载进度
double progress = 1.0 * downloadLen / fileSize;
Data data = new Data.Builder().putDouble(PROGRESS, progress).build();
setProgressAsync(data);
}
byte[] byteArray = byteArrayOutputStream.toByteArray();
inputStream.close();
File file = new File(getApplicationContext().getCacheDir(), outputFile + ".pdf");
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(byteArray);
fileOutputStream.close();
String absolutePath = file.getAbsolutePath();
Log.i(MY_LOG_TAG, absolutePath);
return Uri.fromFile(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@RequiresApi(api = Build.VERSION_CODES.Q)
@NonNull
private ForegroundInfo createForegroundInfo(@NonNull String progress) {
// Build a notification using bytesRead and contentLength
Context context = getApplicationContext();
String id = context.getString(R.string.notification_channel_id);
String title = context.getString(R.string.notification_title);
String cancel = context.getString(R.string.cancel_download);
// This PendingIntent can be used to cancel the worker
PendingIntent intent = WorkManager.getInstance(context)
.createCancelPendingIntent(getId());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel(id);
}
Notification notification = new NotificationCompat.Builder(context, id)
.setContentTitle(title)
.setTicker(title)
.setContentText(progress)
.setSmallIcon(R.drawable.ic_work_notification)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent)
.build();
int notificationId = 1000;
return new ForegroundInfo(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC);
}
@RequiresApi(Build.VERSION_CODES.O)
private void createChannel(String id) {
// Create a Notification channel
NotificationChannel channel = new NotificationChannel(id, "download channel", NotificationManager.IMPORTANCE_NONE);
notificationManager.createNotificationChannel(channel);
}
}
DownloadWorker.java
package com.test.downloadworkerapplication;
import static android.content.Context.NOTIFICATION_SERVICE;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC;
import static com.test.downloadworkerapplication.MainActivity.MY_LOG_TAG;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.work.Data;
import androidx.work.ForegroundInfo;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
public class DownloadWorker extends Worker {
public static final String KEY_INPUT_URL = "KEY_INPUT_URL";
public static final String KEY_OUTPUT_FILE_NAME = "KEY_OUTPUT_FILE_NAME";
public static final String KEY_FILE_URI = "KEY_FILE_URI";
public static final String PROGRESS = "PROGRESS";
private final NotificationManager notificationManager;
public DownloadWorker(
@NonNull Context context,
@NonNull WorkerParameters parameters) {
super(context, parameters);
notificationManager = (NotificationManager)
context.getSystemService(NOTIFICATION_SERVICE);
Data data = new Data.Builder().putInt(PROGRESS, 0).build();
setProgressAsync(data);
}
@NonNull
@Override
public Result doWork() {
Data inputData = getInputData();
String inputUrl = inputData.getString(KEY_INPUT_URL);
String outputFile = inputData.getString(KEY_OUTPUT_FILE_NAME);
String progress = "Starting Download";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
setForegroundAsync(createForegroundInfo(progress));
}
Uri uri = download(inputUrl, outputFile);
Data progressData2 = new Data.Builder().putInt(PROGRESS, 100).build();
setProgressAsync(progressData2);
Data data = new Data.Builder()
.putString(KEY_FILE_URI, uri.toString())
.build();
return Result.success(data);
}
private Uri download(String inputUrl, String outputFile) {
// Downloads a file and updates bytes read
// Calls setForegroundAsync(createForegroundInfo(myProgress))
// periodically when it needs to update the ongoing Notification.
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
String myProgress = "downloading...";
setForegroundAsync(createForegroundInfo(myProgress));
}
long fileSize = FileSizeFetcher.getFileSize(inputUrl);
URL url = new URL(inputUrl);
InputStream inputStream = url.openStream();
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len = 0;
int downloadLen = 0;
while ((len = inputStream.read(buffer)) != -1) {
downloadLen += len;
byteArrayOutputStream.write(buffer, 0, len);
/// 下载进度
double progress = 1.0 * downloadLen / fileSize;
Data data = new Data.Builder().putDouble(PROGRESS, progress).build();
setProgressAsync(data);
}
byte[] byteArray = byteArrayOutputStream.toByteArray();
inputStream.close();
File file = new File(getApplicationContext().getCacheDir(), outputFile + ".pdf");
FileOutputStream fileOutputStream = new FileOutputStream(file);
fileOutputStream.write(byteArray);
fileOutputStream.close();
String absolutePath = file.getAbsolutePath();
Log.i(MY_LOG_TAG, absolutePath);
return Uri.fromFile(file);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@RequiresApi(api = Build.VERSION_CODES.Q)
@NonNull
private ForegroundInfo createForegroundInfo(@NonNull String progress) {
// Build a notification using bytesRead and contentLength
Context context = getApplicationContext();
String id = context.getString(R.string.notification_channel_id);
String title = context.getString(R.string.notification_title);
String cancel = context.getString(R.string.cancel_download);
// This PendingIntent can be used to cancel the worker
PendingIntent intent = WorkManager.getInstance(context)
.createCancelPendingIntent(getId());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel(id);
}
Notification notification = new NotificationCompat.Builder(context, id)
.setContentTitle(title)
.setTicker(title)
.setContentText(progress)
.setSmallIcon(R.drawable.ic_work_notification)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent)
.build();
int notificationId = 1000;
return new ForegroundInfo(notificationId, notification, FOREGROUND_SERVICE_TYPE_DATA_SYNC);
}
@RequiresApi(Build.VERSION_CODES.O)
private void createChannel(String id) {
// Create a Notification channel
NotificationChannel channel = new NotificationChannel(id, "download channel", NotificationManager.IMPORTANCE_NONE);
notificationManager.createNotificationChannel(channel);
}
}
FileSizeFetch.java
package com.test.downloadworkerapplication;
import java.net.HttpURLConnection;
import java.net.URL;
public class FileSizeFetcher {
public static long getFileSize(String urlString) {
long fileSize = 0;
HttpURLConnection connection = null;
try {
URL url = new URL(urlString);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("HEAD"); // 使用HEAD请求以节省带宽
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
String contentLength = connection.getHeaderField("Content-Length");
fileSize = Long.parseLong(contentLength);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
return fileSize;
}
}
运行