開發中遇到須要上傳圖片的場景仍是很常見的,這就涉及到圖片的壓縮處理。若是不進行壓縮,勢必形成消耗大量的流量,下載圖片的速度慢等。java
關於android如何壓縮,網上的資料也是不少,但大多數都是代碼片斷,講解壓縮步驟,而沒有一個實用的工具類庫。那麼如何將壓縮算法封裝成一個實用工具庫呢?其中會遇到些什麼問題,好比:android
基於以上幾點的思考,本人打算寫個系列文章來一步一步解決這些問題(忘你們持續關注),將Service,多線程的使用及壓縮算法集合到一個項目中。這樣不只在實際應用中仍是做爲學習資料來說都是比較好的。最終我會將這個系列中涉及的代碼及迭代的過程開源到github,歡迎你們star,歡迎遞交bug。git
固然有些朋友可能會說實際應用中一次上傳的圖片數量不會太多吧,考慮這些問題是否是有點多慮了,好吧,若是您真是這麼認爲的那麼能夠忽略本系列文章。github
實際需求中基本都會是按照原圖的寬高比進行壓縮,直接指定尺寸大小的比較少見,因此本系列文章也是針對這種等比率壓縮來進行的。算法
總之,對圖片進行壓縮,你們主要關注兩點:多線程
針對這種狀況及圖片旋轉問題,你們能夠參考個人 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程序,實現的功能有:
因爲這個版本是使用AsyncTask異步任務來執行compress的,而AsyncTask因爲android版本分裂問題有些版本是多線程的,有些版本是單線程的,也是醉了,總之此版本適用於一次壓縮任務不是不少的狀況,若是須要處理數據很大的壓縮任務,須要考慮用線程池來處理。
另外,如何結合使用service和多線程會在下篇文章具體說明。
demo開源github地址以下:
LGImageCompressor 歡迎你們訪問並star,若是有任何問題能夠在評論中加以提問,謝謝~~