android圖片壓縮上傳系列-基礎篇

開發中遇到須要上傳圖片的場景仍是很常見的,這就涉及到圖片的壓縮處理。若是不進行壓縮,勢必形成消耗大量的流量,下載圖片的速度慢等。java

關於android如何壓縮,網上的資料也是不少,但大多數都是代碼片斷,講解壓縮步驟,而沒有一個實用的工具類庫。那麼如何將壓縮算法封裝成一個實用工具庫呢?其中會遇到些什麼問題,好比:android

  1. 須要壓縮的圖片有多少
  2. 壓縮後的圖片是覆蓋仍是保存到另外的目錄
  3. 若是是另存目錄須要將原始圖片刪除嗎
  4. 若是改變壓縮後的圖片的尺寸大小是按照原圖的比例縮小仍是直接指定大小
  5. 若是原圖有旋轉問題,需不須要進行修正
  6. 對於多圖壓縮是併發仍是線性的處理
  7. 能不能使用service來進行壓縮處理,是local(本地)仍是remote(遠程)的方式來啓動service
  8. 若是須要壓縮的圖片很是多,如何使用線程池來處理

基於以上幾點的思考,本人打算寫個系列文章來一步一步解決這些問題(忘你們持續關注),將Service,多線程的使用及壓縮算法集合到一個項目中。這樣不只在實際應用中仍是做爲學習資料來說都是比較好的。最終我會將這個系列中涉及的代碼及迭代的過程開源到github,歡迎你們star,歡迎遞交bug。git

固然有些朋友可能會說實際應用中一次上傳的圖片數量不會太多吧,考慮這些問題是否是有點多慮了,好吧,若是您真是這麼認爲的那麼能夠忽略本系列文章。github

實際需求中基本都會是按照原圖的寬高比進行壓縮,直接指定尺寸大小的比較少見,因此本系列文章也是針對這種等比率壓縮來進行的。算法

總之,對圖片進行壓縮,你們主要關注兩點:多線程

  1. 對圖片的尺寸大小進行縮放來達到壓縮的目的
  2. 對圖片進行質量壓縮

對圖片的尺寸大小進行縮放來達到壓縮的目的

針對這種狀況及圖片旋轉問題,你們能夠參考個人 android處理拍照旋轉問題及帶來的對內存佔用的思考 這篇文章。併發

只是你們須要注意的是,這裏須要按照原始圖片的寬高比(srcRatio)來計算最終輸出圖片的寬高(actualOutWidth,actualOutHeight),最後經過actualOutWidth,actualOutHeight來計算採樣值sampleSize。
核心代碼以下:
LGImageCompressor.java異步

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(srcImagePath, options);
//根據原始圖片的寬高比和指望的輸出圖片的寬高比計算最終輸出的圖片的寬和高
float srcWidth = options.outWidth;
float srcHeight = options.outHeight;
float maxWidth = outWidth;//指望輸出的圖片寬度
float maxHeight = outHeight;//指望輸出的圖片高度
float srcRatio = srcWidth / srcHeight;
float outRatio = maxWidth / maxHeight;
float actualOutWidth = srcWidth;//最終輸出的圖片寬度
float actualOutHeight = srcHeight;//最終輸出的圖片高度

if (srcWidth > maxWidth || srcHeight > maxHeight) {
    if (srcRatio < outRatio) {
        actualOutHeight = maxHeight;
        actualOutWidth = actualOutHeight * srcRatio;
    } else if (srcRatio > outRatio) {
        actualOutWidth = maxWidth;
        actualOutHeight = actualOutWidth / srcRatio;
    } else {
        actualOutWidth = maxWidth;
        actualOutHeight = maxHeight;
    }
}
//計算sampleSize
options.inSampleSize = computSampleSize(options, actualOutWidth, actualOutHeight);

爲了方便你們理解以上代碼,舉個極端例子:
假如原始圖片寬爲srcWidth=40,高爲srcHeight=20。指望輸出的寬爲maxWidth=300,高爲maxHeight=10。 那麼srcRatio=40:20=2,outRatio=300:10=30. 顯然srcRatio<outRatio,那麼咱們的實際最終輸出圖片的尺寸應該以maxHeight(10)爲準即actualOutHeight = maxHeight,最後根據原圖的比率來計算actualOutWidth=actualOutHeight*srcRatio = 10*40/20=20,最後獲得的actualOutWidth=20. 最終輸出圖片的寬高比爲20:10=2,和原始圖片寬高比相同。其它狀況相似,這裏不作詳解了。ide

對圖片進行質量壓縮

針對這種狀況,android的Bitmap類中API接口有compress方法
public boolean compress(CompressFormat format, int quality, OutputStream stream)
三個參數的理解應該不難,你們能夠查看官方doc文檔。compress方法主要經過quality來控制輸入到stream中的像素質量。
這針對但願輸出的圖片佔用的空間不大於必定的值這種場景會比較合適,由於咱們能夠經過循環判斷壓縮後的大小是否大於定值,若是知足則減小quality繼續執行compress操做。核心代碼以下:高併發

//進行有損壓縮
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int options_ = 100;
actualOutBitmap.compress(Bitmap.CompressFormat.JPEG, options_, baos);//質量壓縮方法,把壓縮後的數據存放到baos中 (100表示不壓縮,0表示壓縮到最小)

int baosLength = baos.toByteArray().length;
while (baosLength / 1024 > maxFileSize) {//循環判斷若是壓縮後圖片是否大於maxMemmorrySize,大於繼續壓縮
    baos.reset();//重置baos即讓下一次的寫入覆蓋以前的內容
    options_ = Math.max(0, options_ - 10);//圖片質量每次減小10
    actualOutBitmap.compress(Bitmap.CompressFormat.JPEG, options_, baos);//將壓縮後的圖片保存到baos中
    baosLength = baos.toByteArray().length;
    if (options_ == 0)//若是圖片的質量已降到最低則,再也不進行壓縮
        break;
}

壓縮一個超大圖是要費時間的,因此你們應該考慮將壓縮放到後臺線程中執行,若是沒有高併發的需求使用AsyncTask就能解決問題。
核心代碼:

private class CompressTask extends AsyncTask<String, Void, String> {

    @Override
    protected String doInBackground(String... params) {
        return compressImage();//執行壓縮操做
    }

    @Override
    protected void onPreExecute() {
        if (compressListener != null) {
            compressListener.onCompressStart();//監聽回調(開始壓縮)
        }
    }

    @Override
    protected void onPostExecute(String imageOutPath) {
        if (compressListener != null) {
            compressListener.onCompressEnd(imageOutPath);//監聽回調(壓縮結束)
        }
    }
}

通過適當的封裝代碼能夠經過在Activity中的執行
LGImgCompressor.getInstance(this).withListener(this).starCompress(Uri.fromFile(imageFile).toString(),outWidth,outHeight,maxFileSize);

來啓動壓縮任務

寫在最後

爲了達到最佳的壓縮結果,能夠將上面兩種方案同時進行。若是壓縮消耗的時間很長,須要將壓縮過程放入後臺線程中執行。本人寫了個簡單的demo程序,實現的功能有:

  1. 開啓攝像頭拍攝照片
  2. 指定照片的存儲位置
  3. 壓縮照片到指定目錄下
  4. 使用AsyncTask執行壓縮操做
  5. 顯示壓縮後的照片及其相關信息到前臺activity

因爲這個版本是使用AsyncTask異步任務來執行compress的,而AsyncTask因爲android版本分裂問題有些版本是多線程的,有些版本是單線程的,也是醉了,總之此版本適用於一次壓縮任務不是不少的狀況,若是須要處理數據很大的壓縮任務,須要考慮用線程池來處理。
另外,如何結合使用service和多線程會在下篇文章具體說明。

demo開源github地址以下:
LGImageCompressor 歡迎你們訪問並star,若是有任何問題能夠在評論中加以提問,謝謝~~

相關文章
相關標籤/搜索