圖片壓縮之優(yōu)化篇

之前曾經(jīng)對Android中圖片中的壓縮方式進(jìn)行分析和總結(jié)。詳見圖片壓縮篇》基本涵蓋了基礎(chǔ)的壓縮方法和思路。
但是在實(shí)際應(yīng)用運(yùn)用仍有許多地方需要優(yōu)化地方才能夠被應(yīng)用影锈。本文將就以下角度進(jìn)行思考和優(yōu)化:

  1. 一般性應(yīng)用于朋友圈之類的圖片依照怎樣的參數(shù)進(jìn)行哪些方面的優(yōu)化處理芹务?
  2. 如何有效的減少壓縮時間?
  3. 如何避免壓縮過程中的oom鸭廷?

那我們就開始吧枣抱!


合理的壓縮參數(shù)

首先我們要考慮我們應(yīng)該用哪些參數(shù)來控制我們的壓縮過程。下面是我的建議

最大寬度辆床,最大高度

用于控制圖片的最終分辨率佳晶。我們根據(jù)最寬高來進(jìn)行等比例壓縮。這個值我一般設(shè)置的為最大寬為1080
最大高為1920佛吓。這樣的設(shè)置能滿足一般照片之類的圖片的要求宵晚,但是對于超長圖和超寬圖就會出現(xiàn)垂攘,壓縮過度的問題,所以我們需要對超長圖和超寬圖進(jìn)行單獨(dú)的計算和處理淤刃。
首先要判斷是否為長圖晒他,我這里的判斷標(biāo)準(zhǔn)為寬/高高/寬 大于3。若判斷為長圖則修改最大寬高為極限寬高逸贾。10000這個數(shù)值也是新浪微博對于壓縮長圖的最大處理值陨仅。

    public static int imgMemoryMaxWidth = 10000;
    public static int imgMemoryMaxHeight = 10000;
    private static final float longImgRatio = 3f;

    public static void preDoOption(Context context, Uri uri, PressImgService.Option option) {
        if(!option.specialDealLongImg)
            return;
        try {
            FileUntil.ImgMsg imgMsg = FileUntil.getImgWidthAndHeight(context, uri);
            if (imgMsg == null) {
                return;
            }
            float ratio = (float) imgMsg.width / imgMsg.height;
            //超寬圖
            if (ratio > longImgRatio) {
                option.maxWidth = imgMemoryMaxWidth;
            }
            //超長圖
            else if (1 / ratio > longImgRatio) {
                option.maxHeight = imgMemoryMaxHeight;
            }


        } catch (IOException e) {
            e.printStackTrace();
        }
    }

壓縮率

即圖片 文件大小/(圖片寬*圖片高) 。這個比率其實(shí)不能夠最為一個絕對標(biāo)準(zhǔn)铝侵,尤其是在圖片特別小的情況下灼伤,所以我當(dāng)圖片小于50k的時候就不在壓縮了。

private final static float ignoreSize = 1024 * 50;

壓縮流程的優(yōu)化

之前曾說過圖片壓縮主要是分為兩個部分咪鲜,其一是壓縮圖片的分辨率 其二是壓縮圖片的質(zhì)量狐赡。這兩種方式結(jié)合才能讓我們得到體積小清晰度較高的圖片。一般分為以下幾個步驟

從Uri或者文件獲取圖片的bitmap

這里需要注意的是疟丙,我們要在這里進(jìn)行第一次關(guān)于圖片分辨率的壓縮颖侄。因?yàn)樵次募姆直媛适俏粗模蛔鋈魏蜗拗浦苯荧@取bitmap享郊,很可能直接oom览祖。處理方式為利用inJustDecodeBounds屬性只獲取圖片寬高,然后計算一個inSampleSize炊琉。注意展蒂,這個壓縮只能作為初步壓縮,因?yàn)?code>inSampleSize只能為2的倍數(shù)才有效苔咪,最終圖片很難得到精確的尺寸锰悼。
代碼如下

  //根據(jù)uri 獲取bitmap
    public static Bitmap getBitmapFromUri(Context context, Uri uri, float targetWidth, float targetHeight) throws Exception, OutOfMemoryError {
        Bitmap bitmap = null;
        int ratio = 1;
        InputStream input = null;
        if (targetWidth != -1 && targetHeight != -1) {
            input = context.getContentResolver().openInputStream(uri);
            BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
            onlyBoundsOptions.inJustDecodeBounds = true;
            onlyBoundsOptions.inDither = true;
            onlyBoundsOptions.inPreferredConfig = Bitmap.Config.RGB_565;
            BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
            if (input != null) {
                input.close();
            }
            int originalWidth = onlyBoundsOptions.outWidth;
            int originalHeight = onlyBoundsOptions.outHeight;
            if ((originalWidth == -1) || (originalHeight == -1))
                return null;
            float widthRatio = originalWidth / targetWidth;
            float heightRatio = originalHeight / targetHeight;
            ratio = (int) (widthRatio > heightRatio ? widthRatio : heightRatio);
            if (ratio < 1)
                ratio = 1;
        }

        BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
        bitmapOptions.inSampleSize = (int) ratio;
        bitmapOptions.inDither = true;
        bitmapOptions.inPreferredConfig = Bitmap.Config.RGB_565;
        input = context.getContentResolver().openInputStream(uri);
        bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
        if (input != null) {
            input.close();
        }
        return bitmap;
    }

處理bitmap至合適的分辨率

處理bitmap的分辨率我們可以利用Android自帶的ThumbnailUtils.extractThumbnail()方法來進(jìn)行處理。這里可以對bitmap的寬高進(jìn)行精確的壓縮悼泌。

 Bitmap originBitmap = FileUntil.getBitmapFromUri(context, Uri.fromFile(file), maxWidth, maxHeight);
            if (originBitmap == null)
                return false;
            float widthRadio = (float) originBitmap.getWidth() / (float) maxWidth;
            float heightRadio = (float) originBitmap.getHeight() / (float) maxHeight;
            float radio = widthRadio > heightRadio ? widthRadio : heightRadio;
            if (radio > 1) {
                bitmap = ThumbnailUtils.extractThumbnail(originBitmap, (int) (originBitmap.getWidth() / radio), (int) (originBitmap.getHeight() / radio));
                originBitmap.recycle();

            } else
                bitmap = originBitmap;

壓縮bitmap的生成流的大小松捉,并存儲為文件

之后我們需要對bitmap進(jìn)行存儲,并且壓縮圖片文件大小馆里“溃基于compress()函數(shù)的quality參數(shù)。這里對quality參數(shù)進(jìn)行了動態(tài)化處理鸠踪。

  //保存bitmap 為文件
    public static File saveImageAndGetFile(Bitmap bitmap, File file, float limitSize) {
        if (bitmap == null || file == null) {
            return null;
        }
        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);
            if (limitSize != -1) {
                PressImgUntil.compressImageFileSize(bitmap, fos, limitSize);
            } else {
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return null;

        } finally {
            try {
                if (fos != null)
                    fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return file;
    }
---------------------------------------------------------

 /**  壓縮文件大小
     * @param image
     * @param outputStream
     * @param limitSize    單位byte
     * @throws IOException
     */
    public static void compressImageFileSize(Bitmap image, OutputStream outputStream, float limitSize) throws Exception {
        ByteArrayOutputStream imgBytes = new ByteArrayOutputStream();
        int options = 100;
        image.compress(Bitmap.CompressFormat.JPEG, options, imgBytes);
        while (imgBytes.size() > limitSize && imgBytes.size() > ignoreSize && options > 20) {
            imgBytes.reset();
            int dx = 0;
            float dz = (float) imgBytes.size() / limitSize;
            if (dz > 2)
                dx = 30;
            else if (dz > 1)
                dx = 25;
            else
                dx = 20;
            options -= dx;
            image.compress(Bitmap.CompressFormat.JPEG, options, imgBytes);
//            Log.i("lzc", "compressImageFileSize   " + options + "---" + imgBytes.size() +"---"+image.getWidth()+"---"+image.getHeight());
        }

        outputStream.write(imgBytes.toByteArray());
        imgBytes.close();
        outputStream.close();
    }

基于線程池的動態(tài)任務(wù)分配

之前的文章曾講過壓縮圖片基于線程池的處理丙者。由于壓縮過程會消耗大量的內(nèi)存。所有中間提到一個矛盾:同時進(jìn)行的任務(wù)數(shù)越多营密,總體的壓縮速度越快械媒,但是oom的風(fēng)險也隨之增加。我通過兩種方式來嘗試解決這個問題:

單獨(dú)的壓縮進(jìn)程

將壓縮進(jìn)程單獨(dú)的放到某個進(jìn)程中,這樣就能夠獲取更多的可用內(nèi)存纷捞。但是這樣就需要我們進(jìn)行一些跨進(jìn)程的通信痢虹,來控制壓縮過程與接收壓縮回調(diào),這里可以基于Messenger機(jī)制來實(shí)現(xiàn)主儡。一個PressImgService類來負(fù)責(zé)壓縮業(yè)務(wù)奖唯。一個PressImgManager來控制壓縮和接收回調(diào)。

動態(tài)控制線程池中的任務(wù)

盡管我們新開了進(jìn)程糜值,能夠獲得較大的內(nèi)存丰捷,但是仍然有oom的風(fēng)險。所以我打算動態(tài)的計算每一個壓縮任務(wù)能占用的內(nèi)存寂汇,然后根據(jù)內(nèi)存剩余往線程池中添加線程病往。
獲取內(nèi)存信息

  public static MemoryMessage getMemoryMsg(Context context) {
        ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        int memClass = activityManager.getMemoryClass();//64,以m為單位
        int largeClass = activityManager.getLargeMemoryClass();//64骄瓣,以m為單位
        long freeMemory = Runtime.getRuntime().freeMemory();
        long totalMemory = Runtime.getRuntime().totalMemory();
        long maxMemory = Runtime.getRuntime().maxMemory();
        return new MemoryMessage(memClass, freeMemory, totalMemory, maxMemory,largeClass);
    }

首先我取總內(nèi)存的2/3為可用內(nèi)存

   FunUntil.MemoryMessage memoryMessage = FunUntil.getMemoryMsg(this);
        availableMemory = (memoryMessage.maxMemory - memoryMessage.totalMemory + memoryMessage.freeMemory) * 2 / 3;

計算每個任務(wù)的占用內(nèi)存,這也是個不精確的值停巷,但是已經(jīng)很接近了。


    //計算壓縮消耗內(nèi)存
    public static int calcPressTaskMemoryUse(Context context, PressImgService.PressMsg pressMsg) {
        int memory = 0;
        final int imgMemoryRatio = 2;
        int targetWidth = pressMsg.option.maxWidth;
        int targetHeight = pressMsg.option.maxHeight;
        Uri uri = Uri.fromFile(pressMsg.originFile);
        try {
            //獲取圖片寬高
            FileUntil.ImgMsg imgMsg = FileUntil.getImgWidthAndHeight(context, uri);
            if (imgMsg == null)
                return 0;
            //長寬比例
            float widthRatio = (float) imgMsg.width / targetWidth;
            float heightRatio = (float) imgMsg.height / targetHeight;
            int ratio = (int) (widthRatio > heightRatio ? widthRatio : heightRatio);
            if (ratio < 1)
                ratio = 1;
            //第一次處理后寬高
            int originWidth = 0;
            int originHeight = 0;
            ratio = Integer.highestOneBit(ratio);
            originWidth = imgMsg.width / ratio;
            originHeight = imgMsg.height / ratio;
            //計算內(nèi)存
            memory += originWidth * originHeight * imgMemoryRatio;

            //計算第二次處理
            float secondWidthRadio = (float) originWidth / (float) targetWidth;
            float secondHeightRadio = (float) originHeight / (float) targetHeight;
            float secondRadio = secondWidthRadio > secondHeightRadio ? secondWidthRadio : secondHeightRadio;
            if (secondRadio > 1) {
                memory += (originWidth / secondRadio) * (originHeight / ratio) * imgMemoryRatio;
            }
            memory += targetWidth * targetHeight * PressImgService.pressRadio;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
        return memory;
    }

將壓縮任務(wù)保存成隊列累贤,每當(dāng)有任務(wù)結(jié)束叠穆,按占用內(nèi)存大小排序少漆,重新分發(fā)等待中的任務(wù)臼膏。

  //分發(fā)任務(wù)
    private void dispatchTask() {
        if (pressMsgList.size() != 0) {
            Collections.sort(pressMsgList);
            if (pressMsgList.size() != 0)
                Log.i("lzc", "availableMemory  " + availableMemory);
            dispatchCore(pressMsgList);
        }

    }


    //核心分發(fā)
    public void dispatchCore(List<PressMsg> prepareMsgList) {
        int current = 0;
        while (current <= prepareMsgList.size() - 1) {
            if (prepareMsgList.get(current).userMemory > availableMemory)
                current++;
            else {
                PressMsg addTask = prepareMsgList.remove(current);
                startThread(addTask.uuid, addTask);
            }
        }
    }


    //開始執(zhí)行壓縮
    private void startThread(String uuid, PressMsg pressMsg) {
        if (shutDownSet.contains(uuid))
            return;
        try {
            pressMsg.currentStatus = 0;
            executorService.execute(new PressRunnable(pressMsg));
            availableMemory -= pressMsg.userMemory;
        } catch (Exception e) {
            e.printStackTrace();
            errorUUID(uuid, pressMsg, "線程啟動異常!");
        }
    }

通過這樣的方式基本可以完全避免oom的情況示损。

總結(jié)

利用上述的優(yōu)化方式渗磅,基本可以實(shí)現(xiàn)9張圖片在1-5秒的壓縮完成。一般9張相冊照片都在1秒左右即可壓縮完成检访。如30000多像素的超長圖始鱼,9張在5秒內(nèi)也可以壓縮完成。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末脆贵,一起剝皮案震驚了整個濱河市医清,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌卖氨,老刑警劉巖会烙,帶你破解...
    沈念sama閱讀 217,185評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異筒捺,居然都是意外死亡柏腻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評論 3 393
  • 文/潘曉璐 我一進(jìn)店門系吭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來五嫂,“玉大人,你說我怎么就攤上這事∥衷担” “怎么了躯枢?”我有些...
    開封第一講書人閱讀 163,524評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長槐臀。 經(jīng)常有香客問我闺金,道長,這世上最難降的妖魔是什么峰档? 我笑而不...
    開封第一講書人閱讀 58,339評論 1 293
  • 正文 為了忘掉前任败匹,我火速辦了婚禮,結(jié)果婚禮上讥巡,老公的妹妹穿的比我還像新娘掀亩。我一直安慰自己,他們只是感情好欢顷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評論 6 391
  • 文/花漫 我一把揭開白布槽棍。 她就那樣靜靜地躺著,像睡著了一般抬驴。 火紅的嫁衣襯著肌膚如雪炼七。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,287評論 1 301
  • 那天布持,我揣著相機(jī)與錄音豌拙,去河邊找鬼。 笑死题暖,一個胖子當(dāng)著我的面吹牛按傅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播胧卤,決...
    沈念sama閱讀 40,130評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼唯绍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了枝誊?” 一聲冷哼從身側(cè)響起况芒,我...
    開封第一講書人閱讀 38,985評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎叶撒,沒想到半個月后绝骚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡痊乾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評論 3 334
  • 正文 我和宋清朗相戀三年皮壁,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哪审。...
    茶點(diǎn)故事閱讀 39,779評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛾魄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情滴须,我是刑警寧澤舌狗,帶...
    沈念sama閱讀 35,477評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站扔水,受9級特大地震影響痛侍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜魔市,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評論 3 328
  • 文/蒙蒙 一主届、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧待德,春花似錦君丁、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至较坛,卻和暖如春印蔗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背丑勤。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評論 1 269
  • 我被黑心中介騙來泰國打工华嘹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人确封。 一個月前我還...
    沈念sama閱讀 47,876評論 2 370
  • 正文 我出身青樓除呵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親爪喘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評論 2 354

推薦閱讀更多精彩內(nèi)容