細(xì)數(shù)圖片上傳功能用到的知識點(diǎn)(圖片壓縮篇)

先附上裁剪篇和選取篇的鏈接耙旦,結(jié)合本文食用風(fēng)味更佳~
選取篇域滥;裁剪篇

壓縮目標(biāo)

在講壓縮之前先要明確我們的目標(biāo)

  1. 對圖片進(jìn)行處理昆汹,使其滿足我們對圖片分辨率的要求坟奥;
  2. 盡可能減小圖片文件的大小树瞭,來節(jié)省上傳時間和用戶流量拇厢;
  3. 避免oom;

壓縮圖片相關(guān)函數(shù)

明確目標(biāo)之后首先我們來編寫我們可能需要用到的函數(shù)

讀取圖片

從file或者uri中讀取bitmap的這一步晒喷,我們要對圖片進(jìn)行第一次的處理孝偎。生成bitmap可以使用這個方法BitmapFactory.decodeStream(),由于目標(biāo)圖片可能分辨率很大,如果這里不進(jìn)行處理很容易造成oom厨埋。
這里我們可以利用兩個方式來降低bitmap所占用的內(nèi)存邪媳。

inSampleSize

利用BitmapFactory.Options中的inSampleSize屬性可以減小圖片的分辨率。若inSampleSize=x得到的bitmap屬性就是原始分辨率的1/x荡陷。inSampleSize值只能為2的倍數(shù)雨效。也就是說當(dāng)inSampleSize的值為2,4废赞,6徽龟,8的時候才有用。這種方式并不能精確的得到我們想要的分辨率唉地,但是作為初步的壓縮還是非常合適的据悔。
那么計(jì)算inSampleSize的值可以先獲取原始圖片的大小,再根據(jù)我們自己的目標(biāo)大小來進(jìn)行初步壓縮耘沼。要獲取原始圖片大小极颓,我們可以利用BitmapFactory.OptionsinJustDecodeBounds屬性。

inPreferredConfig

利用BitmapFactory.Options中的inPreferredConfig屬性可以改變圖片的默認(rèn)模式群嗤,bitmap有如下四種模式

模式 組成 占用內(nèi)存
ALPHA_8 Alpha由8位組成 一個像素占用1個字節(jié)
ARGB_4444 4個4位組成即16位 一個像素占用2個字節(jié)
ARGB_8888 4個8位組成即32位 一個像素占用4個字節(jié)
RGB_565 R為5位菠隆,G為6位,B為5位共16位 一個像素占用2個字節(jié)

Android默認(rèn)的圖片模式為ARGB_8888狂秘,但是如果我們的圖片不需要太高的質(zhì)量并且沒有透明通道骇径。我們完全可以使用RGB_565這種模式。

完整代碼
 /**
     * @param
     * @param uri
     * @param targetWidth  限制寬度
     * @param targetHeight 限制高度
     * @return
     * @throws Exception
     */
    public static Bitmap getBitmapFromUri(Context context, Uri uri, float targetWidth, float targetHeight) throws Exception {
        Bitmap bitmap = null;
        InputStream 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;
        //計(jì)算壓縮值
        float ratio = 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);
        //實(shí)際獲取圖片
        bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
        if (input != null) {
            input.close();
        }
        return bitmap;
    }

處理bitmap

bitmap的處理比較簡單者春,我們可以使用Android系統(tǒng)為我們提供的函數(shù)extractThumbnail(Bitmap source, int width, int height)破衔,這個函數(shù)的內(nèi)部實(shí)現(xiàn)很有意思,有空大家可以先看看钱烟。而如果我們的壓縮要保證圖片的等比例處理晰筛,需要合理的去計(jì)算新的width和height。計(jì)算方法如下

 float widthRadio = (float) bitmap.getWidth() /(float) maxWidth;
            float heightRadio = (float) bitmap.getHeight() / (float)maxHeight;
            float radio = widthRadio > heightRadio ? widthRadio : heightRadio;
            if (radio > 1) {
                bitmap = ThumbnailUtils.extractThumbnail(bitmap, (int) (bitmap.getWidth() / radio), (int) (bitmap.getHeight() / radio));
            }

保存bitmap并壓縮文件大小

得到了合適分辨率的bitmap拴袭,我們接下來就需要對圖片的大小進(jìn)行壓縮和保存了读第。接下來問題就來了,一張圖片應(yīng)該占用多大的空間呢稻扬?我的辦法就是引入一個參數(shù)表明1像素占用的大小來處理圖片。壓縮圖片大小我們可以使用bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos); 注意只有jpeg格式的圖片才能被這個函數(shù)壓縮羊瘩!第二個參數(shù)表明圖片的壓縮質(zhì)量泰佳,100表示不壓縮盼砍。我們無法估算這個參數(shù)對圖片最終大小的影響,所以我們只能采用循環(huán)的方式來處理我們的圖片逝她。


    /**
     * @param image
     * @param outputStream
     * @param limitSize    單位byte 由單位像素占用大小計(jì)算得出
     * @throws IOException
     */
    public static void compressImage(Bitmap image, OutputStream outputStream, float limitSize) throws Exception {

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        image.compress(Bitmap.CompressFormat.JPEG, 100, baos);
        int options = 100;
      //ignoreSize 以下的圖片不進(jìn)行壓縮浇坐,發(fā)現(xiàn)小圖的的壓縮效率不高而且質(zhì)量損毀的十分嚴(yán)重。
        while (baos.toByteArray().length > limitSize&&baos.toByteArray().length> ignoreSize) {
            baos.reset();
            image.compress(Bitmap.CompressFormat.JPEG, options, baos);
            //每次減少的量黔宛,可以進(jìn)行調(diào)整近刘。由于compress這個函數(shù)占用時間很長所以我們應(yīng)當(dāng)盡量減少循環(huán)次數(shù)
            options -= 15;
            Log.i("lzc","currentSize"+(baos.toByteArray().length/1024));
        }
        image.compress(Bitmap.CompressFormat.JPEG, options, outputStream);
        baos.close();
        outputStream.close();
    }

壓縮

編寫完成壓縮相關(guān)函數(shù),接下來我們就要考慮這些函數(shù)的調(diào)用方式了臀晃。很明顯這些操作都是耗時操作觉渴,不能放在主線程中執(zhí)行。而且我們有壓縮多張圖片的需求徽惋,考慮到內(nèi)存問題案淋,我們應(yīng)該使用service單獨(dú)開進(jìn)程來對圖片壓縮。
另外险绘,多張圖片的壓縮是順序踢京,還是并發(fā)執(zhí)行的問題值得我們考慮。順序執(zhí)行可以減少內(nèi)存占用而并發(fā)執(zhí)行可以減少壓縮時間宦棺。
我選擇了并發(fā)執(zhí)行瓣距,畢竟壓縮之后還要緊接上傳,不宜讓用戶等待過久代咸。我們來建立我們的service開啟線程池來處理壓縮圖片流程蹈丸。

創(chuàng)建service時建立線程池

  @Override
    public void onCreate() {
        super.onCreate();
        fileHashtable.clear();
        executorService = new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                9, TimeUnit.SECONDS,
                new SynchronousQueue<Runnable>());
    }

每次startServcie向線程池增加一個事件


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        executorService.execute(new PressRunnable(intent));
        return super.onStartCommand(intent, flags, startId);
    }

處理事件和壓縮

//壓縮圖片線程
private class PressRunnable implements Runnable {
        private Intent intent;

        public PressRunnable(Intent intent) {
            this.intent = intent;
        }

        @Override
        public void run() {
            onHandleIntent(intent);
        }
    }


//處理intent得到參數(shù)
    protected void onHandleIntent(Intent intent) {
        if (intent != null) {
            final String action = intent.getAction();
            if (ACTION_FOO.equals(action)) {
                final Uri uri = intent.getParcelableExtra(EXTRA_PARAM1);
                final ChoicePhotoManager.Option option = (ChoicePhotoManager.Option) intent.getSerializableExtra(EXTRA_PARAM2);
                realCount = intent.getIntExtra(EXTRA_PARAM3, 1);
                int position = intent.getIntExtra(EXTRA_PARAM4, 0);
                handleActionFoo(uri, option, position);
            }
        }
    }


//壓縮圖片
    private void handleActionFoo(Uri uri, ChoicePhotoManager.Option option, int position) {
        File file = new File(FileUntil.UriToFile(uri, this));
        if (!file.exists())
            return;
//創(chuàng)建新文件
        File newFile = FileUntil.createTempFile(FileName + UUID.randomUUID() + ".jpg");
  
//壓縮圖片并保存到新文件
        FileUntil.compressImg(this, file, newFile, option.pressRadio, option.maxWidth, option.maxHeight);

//讀取原始圖片的旋轉(zhuǎn)信息,并給以現(xiàn)有圖片
        FileUntil.setFilePictureDegree(newFile, FileUntil.readPictureDegree(file.getPath()));
        fileHashtable.put(position, newFile);
        synchronized (PressImgService.class) {
            count++;
            if (realCount == count) {
                callFinish();
            }
        }
    }

//壓縮完畢關(guān)閉servcie 回傳壓縮后圖片的uri
    private void callFinish() {
        Intent intent = new Intent();
        intent.setAction(callbackReceiver);
        Uri[] uris = new Uri[fileHashtable.keySet().size()];
        for (Map.Entry<Integer, File> integerFileEntry : fileHashtable.entrySet()) {
            uris[integerFileEntry.getKey()] = Uri.fromFile(integerFileEntry.getValue());
        }
        for (int i = 0; i < realCount; i++) {
            Log.i("lzc", "position---asd" + i);
        }
        intent.putExtra("data", uris);
        sendBroadcast(intent);
        fileHashtable.clear();
        count = 0;
        stopSelf();
    }

注意

上面的代碼很長侣背,但是要注意的只有兩點(diǎn)白华。

  1. 要注意讀取之前文件的旋轉(zhuǎn)信息,并賦值給新的文件贩耐。這樣弧腥,新的圖片才能得到正確的旋轉(zhuǎn)角度。
    用到的函數(shù)如下潮太。
//讀取文件的旋轉(zhuǎn)信息
 public static int readPictureDegree(String path) {
        int degree = 0;
        try {
            ExifInterface exifInterface = new ExifInterface(path);
            int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
            switch (orientation) {
                case ExifInterface.ORIENTATION_ROTATE_90:
                    degree = 90;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_180:
                    degree = 180;
                    break;
                case ExifInterface.ORIENTATION_ROTATE_270:
                    degree = 270;
                    break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return degree;
    }

  //為文件設(shè)置旋轉(zhuǎn)信息
    public static void setFilePictureDegree(File file, int degree) {
        try {
            ExifInterface exifInterface = new ExifInterface(file.getPath());
            int orientation = ExifInterface.ORIENTATION_NORMAL;
            switch (degree) {
                case 90:
                    orientation = ExifInterface.ORIENTATION_ROTATE_90;
                    break;
                case 180:
                    orientation = ExifInterface.ORIENTATION_ROTATE_180;
                    break;
                case 270:
                    orientation = ExifInterface.ORIENTATION_ROTATE_270;
                    break;
            }
            exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION, orientation + "");
            exifInterface.saveAttributes();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

2.由于是多線程并發(fā)管搪,所以我們需要對幾個關(guān)鍵模塊加上同步鎖。第一個是多張圖片完成壓縮記的計(jì)數(shù)

    synchronized (PressImgService.class) {
            count++;
            if (realCount == count) {
                callFinish();
            }

第二個地方是我們圖片讀取的地方铡买,否則會產(chǎn)生多張圖拼接到一起的問題更鲁。

   synchronized (PressImgService.class) {
                bitmap = getBitmapFromUri(context, Uri.fromFile(file), maxWidth, maxHeight);
            }

這樣我們細(xì)數(shù)圖片上傳功能用到的知識點(diǎn)的三篇文章就全部講完了。
撒花奇钞,完結(jié)澡为!

細(xì)數(shù)圖片上傳功能用到的知識點(diǎn)(圖片選取&拍照篇)
細(xì)數(shù)圖片上傳功能用到的知識點(diǎn)(裁剪篇)
細(xì)數(shù)圖片上傳功能用到的知識點(diǎn)(圖片壓縮篇)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市景埃,隨后出現(xiàn)的幾起案子媒至,更是在濱河造成了極大的恐慌顶别,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拒啰,死亡現(xiàn)場離奇詭異驯绎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)谋旦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門剩失,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人册着,你說我怎么就攤上這事拴孤。” “怎么了指蚜?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵乞巧,是天一觀的道長蒜绽。 經(jīng)常有香客問我诉字,道長,這世上最難降的妖魔是什么傲霸? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任免猾,我火速辦了婚禮是辕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘猎提。我一直安慰自己获三,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布锨苏。 她就那樣靜靜地躺著疙教,像睡著了一般。 火紅的嫁衣襯著肌膚如雪伞租。 梳的紋絲不亂的頭發(fā)上贞谓,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天,我揣著相機(jī)與錄音葵诈,去河邊找鬼裸弦。 笑死,一個胖子當(dāng)著我的面吹牛作喘,可吹牛的內(nèi)容都是我干的理疙。 我是一名探鬼主播,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼泞坦,長吁一口氣:“原來是場噩夢啊……” “哼窖贤!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤赃梧,失蹤者是張志新(化名)和其女友劉穎择吊,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體槽奕,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年房轿,在試婚紗的時候發(fā)現(xiàn)自己被綠了粤攒。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡囱持,死狀恐怖夯接,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情纷妆,我是刑警寧澤盔几,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站掩幢,受9級特大地震影響逊拍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜际邻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一芯丧、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧世曾,春花似錦缨恒、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至血巍,卻和暖如春萧锉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背藻茂。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工驹暑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人辨赐。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓优俘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親掀序。 傳聞我的和親對象是個殘疾皇子帆焕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評論 2 353

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