Android Service後臺多執行緒壓縮並提交圖片及資料
阿新 • • 發佈:2019-02-19
手機端發帖,多張圖片上傳是個問題.最近重構專案程式碼,正好碰到這個,這裡把解決的方案整理,以備後用.
方案原理:
- 建立上傳任務表, 帖子內容釋出的時候將資料存放到任務表中,並傳遞資料到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<SubmitPostBena> 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<String> newPath = Collections.synchronizedList(new ArrayList<String>());//返回值
private List<String> faile = Collections.synchronizedList(new ArrayList<String>());//提交失敗返回值
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<String> newPath, List<String> 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<String> newPath;
private CountDownLatch countDownLatch;
/**
* 壓縮圖片處理
*
* @param countDownLatch 執行緒同步輔助類,用於基數當前執行緒是否完成,如果完成,執行緒數量減少1
* @param imagePath 要處理圖片的路徑
* @param newPath 處理後圖片的新路徑
*/
CompressRunnable(CountDownLatch countDownLatch, String imagePath, List<String> 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<String> 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();
}
}