手机端发帖,多张图片上传是个问题.最近重构项目代码,正好碰到这个,这里把解决的方案整理,以备后用.
方案原理:
- 创建上传任务表, 帖子内容发布的时候将数据存放到任务表中,并传递数据到service中.
- 启动服务,遍历任务表中内容,创建上传任务.如果接收到上传任务,创建任务,加入上传任务队列中.(上传任务顺序可以自定义)
- 包含图片的上传任务.开启多线程压缩(使用线程池管理压缩线程),压缩完毕后返回压缩后临时图片位置
- 上传成功后,根据返回值处理临时目录, 删除任务表中上传成功任务
Activity 和 Service 之间的数据通信使用的是 EventBus
数据上传使用的的是retrofit + okHttp
不多说,直接上代买
上传服务:
public class UploadService extends Service {
private static final String TAG = UploadService.class.getSimpleName();
private ExecutorService executorService = Executors.newFixedThreadPool(3);
private ExecutorService singleTaskService = Executors.newSingleThreadExecutor();// 按顺序处理任务
@Override
public void onCreate() {
super.onCreate();
EventBus.getDefault().register(this);
LogUtils.e(TAG, "UploadService is onCreate");
}
/**
* 通过EventBus接受传递过来的数据
*
* @param submitPostBena
*/
@Subscribe
public void receiveUploadData(SubmitPostBena submitPostBena) {
createSubmitTask(submitPostBena);
}
/**
* 查询任务表中是否有数据 (未实现任务表)
*/
private void queryTaskTable() {
//mTaskCount = queryFromTable().size();//查询数据库
//List postsBeen = new ArrayList<>();
//for (int i = 0; i < postsBeen.size(); i++) { //循环创建提交帖子任务
// createSubmitTask(postsBeen.get(i));
//}
}
/**
* 创建提交帖子数据任务
*
* @param bean 要提交的提子数据
*/
private void createSubmitTask(SubmitPostBena bean) {
singleTaskService.execute(new TaskRunnable(bean));
}
/**
* 任务
*/
private class TaskRunnable implements Runnable {
private CountDownLatch countDownLatch;
private List newPath = Collections.synchronizedList(new ArrayList());//返回值
private List faile = Collections.synchronizedList(new ArrayList());//提交失败返回值
private SubmitPostBena bean;
TaskRunnable(SubmitPostBena bean) {
this.bean = bean;
countDownLatch = new CountDownLatch(bean.getImagePaths().size());//这地方有个小坑,countDown 的数量一定要和压缩图片的数量一致
}
@Override
public void run() {
synchronized (UploadService.class) {
try {
if (bean.isImagePost()) {
LogUtils.e(TAG, "开始任务处理图片压缩");
CompressTask(countDownLatch, newPath, bean.getImagePaths()); //处理压缩问题
countDownLatch.await();
bean.setImagePaths(newPath);
LogUtils.e(TAG, "压缩完成");
}
submitData(bean, faile); //提交
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
/**
* 创建处理压缩任务
*
* @param countDownLatch 线程同步辅助工具,用于记录图片压缩线程全部执行完毕后通知提交线程提交
* @param newPath 处理过后的图片的地址
* @param imagePath 原始图片地址
*/
private void CompressTask(CountDownLatch countDownLatch, List newPath, List imagePath) {
for (int i = 0; i < imagePath.size(); i++) {
String path = imagePath.get(i);
executorService.execute(new CompressRunnable(countDownLatch, path, newPath));
}
}
/**
* 压缩任务
*/
private class CompressRunnable implements Runnable {
private String filePath;
private List newPath;
private CountDownLatch countDownLatch;
/**
* 压缩图片处理
*
* @param countDownLatch 线程同步辅助类,用于基数当前线程是否完成,如果完成,线程数量减少1
* @param imagePath 要处理图片的路径
* @param newPath 处理后图片的新路径
*/
CompressRunnable(CountDownLatch countDownLatch, String imagePath, List newPath) {
this.newPath = newPath;
this.filePath = imagePath;
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
Bitmap smallBitmap = BitmapUtils.getSmallBitmap(filePath);
String tempPath = BitmapUtils.compressImage(smallBitmap);
if (!TextUtils.isEmpty(tempPath)) {
newPath.add(tempPath);
countDownLatch.countDown();
}
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
LogUtils.e(TAG, "UploadService is onDestroy");
executorService.shutdownNow();
singleTaskService.shutdownNow();
}
private void submitData(SubmitPostBena mBean, List faile) {
if (SystemUtils.checkNet(this)) {
//上传方法
}
}
}
图片压缩:
public class BitmapUtils {
/**
* 根据路径获得图片并压缩,返回bitmap用于显示
*
* @param filePath 图片路径
* @return
*/
public static Bitmap getSmallBitmap(String filePath) {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
options.inSampleSize = calculateInSampleSize(options, 480, 800);// Calculate inSampleSize
options.inJustDecodeBounds = false;// Decode bitmap with inSampleSize set
Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);
return bitmap;
}
/**
* 计算图片的缩放值
*
* @param options
* @param reqWidth
* @param reqHeight
* @return
*/
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
if (height > reqHeight || width > reqWidth) {
final int heightRatio = Math.round((float) height / (float) reqHeight);
final int widthRatio = Math.round((float) width / (float) reqWidth);
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return inSampleSize;
}
public static String compressImage(Bitmap image) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
int options = 90;
while (baos.toByteArray().length / 1024 > 100) {//循环判断如果压缩后图片是否大于100kb,大于继续压缩
baos.reset();//重置baos即清空baos
options -= 10;
image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中
if (options < 0) options = 0;
}
String tempName = UUID.randomUUID().toString().replace("-", "") + ".jpg";
File fileDir = new File(Commons.PHOTOCACHE_TEMP);
if (!fileDir.exists()) {
fileDir.mkdirs();
}
File file = new File(fileDir, tempName);
try {
if (file.exists())
file.delete();
FileOutputStream fos = new FileOutputStream(file);
image.compress(Bitmap.CompressFormat.JPEG, 50, fos);
fos.flush();
fos.close();
if (!image.isRecycled()) {
image.recycle();
}
} catch (IOException e) {
e.printStackTrace();
}
LogUtils.e("压缩后图片大小 >>>> ", file.length() + "");
return file.getPath();
}
}