目录:
1.概述
2.压缩方法分类
3.压缩方法使用

1.概述:
在android的开发过程中我们经常会有压缩图片的需求,为啥要压缩图片呢?比如有的时候是需要用户从本地上传图片到服务器,而
这个时候如果不进行压缩,图片就会比较大,当然也就会耗更多流量,如果对图片进行压缩处理,也就为用户省了很多流量,同时
很多项目也会有浏览图片的需求,如果在其中加载过多图片很有可能会造成OOM(Out Of Memory),而防止程序OOM的解决方案就是对图片进行
压缩并利用缓存技术进行缓存与定时释放。

2.压缩方法分类
2.1 降低质量压缩图片
2.2 按比例缩放压缩图片

3.压缩方法使用
3.1从网络下载图片并按比例缩放图片

ps:图片按比例压缩的逻辑实现由ImageCacheManager类的compressImageByPixel(String urlSpec, int radio)方法实现,下面会贴出代码
同时在下面的案例中我并没有一开始就将下载的图片压缩而是在加载的过程中去压缩显示,当然这并没有达到压缩图片降低内存使用的目的,我
重在理解图片根据像素压缩的过程和实现,如果伙伴们想实现提前压缩缓存,可以试试将压缩操作提到下载缓存实现中去。

(1)布局文件activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.compressimage.MainActivity">
    <LinearLayout
        android:id="@+id/btn_layout"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/show_image"
            android:text="原图片加载"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <Button
            android:id="@+id/show_compress_image"
            android:text="压缩图片加载"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/image_layout"
        android:layout_below="@+id/btn_layout"
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
       <ImageView
           android:id="@+id/image"
           android:layout_marginRight="10dp"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content" />
        <ImageView
            android:id="@+id/compress_image"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            />
    </LinearLayout>
</RelativeLayout>



(2)主布局类MainActivity.java


package com.example.compressimage;


import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;


public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    private Button showImage,showCompressImage;
    private ImageView image,compressImage;
    private String imageUrl = "http://img2.imgtn.bdimg.com/it/u=223985654,3095755068&fm=21&gp=0.jpg";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //初始化控件
        initView();
        //设置绑定监听
        showImage.setOnClickListener(this);
        showCompressImage.setOnClickListener(this);
    }
    /*
    * 初始化控件
    * */
    private void initView() {
        showImage = (Button) findViewById(R.id.show_image);
        showCompressImage = (Button) findViewById(R.id.show_compress_image);
        image = (ImageView) findViewById(R.id.image);
        compressImage = (ImageView) findViewById(R.id.compress_image);
    }
    /*
    * 继承实现监听
    * */
    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.show_image:
                //开启线程下载显示未压缩的图片
                AsyncCompressImageTask asyncTask = new AsyncCompressImageTask(image);
                asyncTask.execute(imageUrl);
                break;
            case R.id.show_compress_image:
                //开启线程下载显示压缩的图片
                AsyncCompressImageTask asyncCompressTask = new AsyncCompressImageTask(compressImage,true);
                asyncCompressTask.execute(imageUrl);
                break;
            default:
                break;
        }
    }
}



(3)异步线程类AsyncCompressImageTask.java


package com.example.compressimage;


import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import android.widget.ImageView;


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;


/**
 * Created by elimy on 2016-09-25.
 */
public class AsyncCompressImageTask extends AsyncTask<String,Void,Bitmap> {
    //设置采样率,后面实现压缩图片宽高为原来的一半
    private final int radio = 2;
    private boolean compressFlag = false;
    private ImageView imageView = null;
    public  Bitmap bitmap;
    private final static ImageCacheManager imageCacheManager = ImageCacheManager.getInstance();


    /*
    * 无参构造方法
    * */
    public AsyncCompressImageTask() {
    }
    /*
    * 带一个参数构造方法,内部调用两个参数构造方法实现
    * */
    public AsyncCompressImageTask(ImageView imageView) {
        this.imageView = imageView;
    }
    /*
    * 带两个参构造方法实现传递参数的效果
    * */
    public AsyncCompressImageTask(ImageView imageView,boolean compressFlag) {
        this.imageView = imageView;
        this.compressFlag = compressFlag;
    }


    /*
    * 执行后台操作,获取图片bitmap
    * */
    @Override
    protected Bitmap doInBackground(String... params) {
        String urlSpec = params[0];
        //如果缓存没有则去本地文件缓存文件夹获取,如果还没有则去网络加载
        bitmap = imageCacheManager.getBitmapFromCache(urlSpec);
        if (bitmap == null) {
            File imageFile = new File(getImagePath(urlSpec));
            if (imageFile.exists()) {
                bitmap = BitmapFactory.decodeFile(imageFile.getPath());
                Log.d("Tag","从本地文件缓存中加载...");
            } else {
                //网络获取图片
                bitmap = downloadImage(urlSpec);
                Log.d("Tag","从网络中下载...");
            }
        }else {
            Log.d("Tag","从LruCache中加载...");
        }
        //根据compressFlag的值决定是否压缩图片
        if (compressFlag){
            Log.d("compressFlag","获取压缩图片");
            bitmap = imageCacheManager.compressImageByPixel(getImagePath(urlSpec),radio);
        }else {
            Log.d("compressFlag","获取源图片");
        }
        return bitmap;
    }


    /*
    * 后台线程执行操作后返回图片bitmap,该方法执行更新UI的操作
    * */
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
        Log.d("WidthAndHeight","Width="+bitmap.getWidth()+",Height="+bitmap.getHeight());
        imageView.setImageBitmap(bitmap);
    }


    /*
        * 从网络下载图片并缓存
        * */
    private Bitmap downloadImage(String urlSpec) {
        URL url = null;
        HttpURLConnection urlConn = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        File imageFile = null;
        FileOutputStream fos = null;
        try {
            url = new URL(urlSpec);
            urlConn = (HttpURLConnection) url.openConnection();
            urlConn.setRequestMethod("GET");
            urlConn.setDoInput(true);
            urlConn.setConnectTimeout(5000);
            urlConn.setReadTimeout(5000);
            if (urlConn.getResponseCode() ==HttpURLConnection.HTTP_OK){
                bis = new BufferedInputStream(urlConn.getInputStream());
                imageFile = new File(getImagePath(urlSpec));
                fos = new FileOutputStream(imageFile);
                bos = new BufferedOutputStream(fos);
                int len = 0;
                byte[] b =new byte[1024];
                while ((len =bis.read(b))!=-1){
                    bos.write(b,0,len);
                    bos.flush();
                }
            }else {
                Log.d("getResponseCode","下载失败!");
            }


        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                if (bis != null) {
                    bis.close();
                }
                if (bos != null) {
                    bos.close();
                }
                if (fos != null) {
                    fos.close();
                }
                if (urlConn != null) {
                    urlConn.disconnect();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (imageFile!=null){
            Bitmap bitmap = BitmapFactory.decodeFile(getImagePath(urlSpec));
            if (bitmap != null){
                imageCacheManager.addBitmapToCache(urlSpec,bitmap);
                return  bitmap;
            }
        }
        return null;
    }
    /*
* 该方法用来获取缓存图片的全地址也就是绝对地址+文件名
*
* */
    private String getImagePath(String urlSpec) {
        //截取urlSpec最后一个“/”后面的字符串作为文件名
        String imageName = urlSpec.substring(urlSpec.lastIndexOf("/") + 1);
        //定义图片缓存本地路径
        String imageDir = Environment.getExternalStorageDirectory() + "/CompressImage/";
        File file = new File(imageDir);
        if (!file.exists()) {
            file.mkdirs();
            Log.d("file", "file make success");
        }
        String path = imageDir + imageName;
        Log.d("path", path);
        return path;
    }
}



(4)LruCache缓存管理类ImageCacheManager.java


package com.example.compressimage;


import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.LruCache;


/**
 * Created by elimy on 2016-09-25.
 */
public class ImageCacheManager {
    //设置缓存大小为最大缓存的1/8
    private static final int mCacheSize = (int) (Runtime.getRuntime().maxMemory()/8);
    //定义缓存核心类
    static LruCache<String,Bitmap> mImageCache;
    //定义类本身实例
    private static ImageCacheManager imageCacheManager;


    private ImageCacheManager(){
        mImageCache = new LruCache<String,Bitmap>(mCacheSize){
            /*
            *返回图片的占用字节数,每次添加图片时会被调用
            * */
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount()/1024;
            }
        };
    }
    /*
    * 单例模式实现初始化ImageCacheManager类,好处是在整个操作过程中保持
    * 仅有一个实例,以此来节约系统资源
    * */
    public static ImageCacheManager getInstance(){
        if (imageCacheManager == null){
            imageCacheManager = new ImageCacheManager();
        }
        return imageCacheManager;
    }
    /*
    * 从缓存中获取bitmap
    * */
    public Bitmap getBitmapFromCache(String urlSpec){
        return mImageCache.get(urlSpec);
    }
    /*
    * 缓存图片
    * */
    public void addBitmapToCache(String url,Bitmap bitmap){
        if (mImageCache.get(url)==null){
            mImageCache.put(url,bitmap);
        }
    }
    /*
    * 根据传入的外存地址和采样率压缩图片
    * */
    public Bitmap compressImageByPixel(String urlSpec, int radio) {
        Bitmap bitmap=null;
        BitmapFactory.Options opts = new BitmapFactory.Options();
        //设置采样率为2,根据像素缩小图片
        opts.inSampleSize = radio;
        opts.inJustDecodeBounds=false;
        bitmap = BitmapFactory.decodeFile(urlSpec,opts);
        return bitmap;
    }
}



(5)manifest文件声明权限


<uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>



(6)效果截图

android 内存压缩技术_像素压缩


(7)操作Log输出(可以看见实际的执行流程和最后图片展示的尺寸)


09-25 10:00:56.572 9758-9916/com.example.compressimage D/path: /storage/emulated/0/CompressImage/u=223985654,3095755068&fm=21&gp=0.jpg
                                                               
                                                               --------- beginning of /dev/log/system
09-25 10:00:56.736 9758-9916/com.example.compressimage D/path: /storage/emulated/0/CompressImage/u=223985654,3095755068&fm=21&gp=0.jpg
09-25 10:00:56.740 9758-9916/com.example.compressimage D/dalvikvm: GC_FOR_ALLOC freed 280K, 15% free 2692K/3140K, paused 2ms, total 2ms
09-25 10:00:56.740 9758-9916/com.example.compressimage D/path: /storage/emulated/0/CompressImage/u=223985654,3095755068&fm=21&gp=0.jpg
09-25 10:00:56.744 9758-9916/com.example.compressimage D/Tag: 从网络中下载...
09-25 10:00:56.744 9758-9916/com.example.compressimage D/compressFlag: 获取源图片
09-25 10:00:56.744 9758-9758/com.example.compressimage D/WidthAndHeight: Width=147,Height=220
09-25 10:01:00.076 9758-9966/com.example.compressimage D/Tag: 从LruCache中加载...
09-25 10:01:00.076 9758-9966/com.example.compressimage D/compressFlag: 获取压缩图片
09-25 10:01:00.076 9758-9966/com.example.compressimage D/path: /storage/emulated/0/CompressImage/u=223985654,3095755068&fm=21&gp=0.jpg
09-25 10:01:00.092 9758-9758/com.example.compressimage D/WidthAndHeight: Width=74,Height=110



3.2从网络下载图片并进行降低质量压缩




ps:这个案例我就只贴出异步线程下载缓存类的代码了,质量压缩的代码在compressImageByQuality(InputStream is, String imagePath)方法中,其他代码基本没有变化




(1)异步线程类AsyncCompressImageTask.java


package com.example.compressimage;


import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log;
import android.widget.ImageView;


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;


/**
 * Created by elimy on 2016-09-25.
 */
public class AsyncCompressImageTask extends AsyncTask<String, Void, Bitmap> {
    //设置采样率,后面实现压缩图片宽高为原来的一半
    private final int radio = 2;
    private boolean compressFlag = false;
    private boolean qualityCompressFlag = false;
    private ImageView imageView = null;
    public Bitmap bitmap;
    private final static ImageCacheManager imageCacheManager = ImageCacheManager.getInstance();


    /*
    * 无参构造方法
    * */
    public AsyncCompressImageTask() {
    }


    /*
    * 带一个参数构造方法,内部调用两个参数构造方法实现
    * */
    public AsyncCompressImageTask(ImageView imageView) {
        this.imageView = imageView;
    }


    /*
    * 带两个参构造方法实现传递参数的效果
    * */
/*    public AsyncCompressImageTask(ImageView imageView, boolean compressFlag) {
        this.imageView = imageView;
        this.compressFlag = compressFlag;
    }*/
   public AsyncCompressImageTask(ImageView imageView, boolean qualityCompressFlag) {
       this.imageView = imageView;
       this.qualityCompressFlag = qualityCompressFlag;
   }
    /*
    * 执行后台操作,获取图片bitmap
    * */
    @Override
    protected Bitmap doInBackground(String... params) {
        String urlSpec = params[0];
        //如果缓存没有则去本地文件缓存文件夹获取,如果还没有则去网络加载
        bitmap = imageCacheManager.getBitmapFromCache(urlSpec);
        if (bitmap == null) {
            File imageFile = new File(getImagePath(urlSpec));
            if (imageFile.exists()) {
                bitmap = BitmapFactory.decodeFile(imageFile.getPath());
                Log.d("Tag", "从本地文件缓存中加载...");
            } else {
                //网络获取图片
                bitmap = downloadImage(urlSpec);
                Log.d("Tag", "从网络中下载...");
            }
        } else {
            Log.d("Tag", "从LruCache中加载...");
        }
        //根据compressFlag的值决定是否压缩图片
        if (compressFlag) {
            Log.d("compressFlag", "获取压缩图片");
            bitmap = imageCacheManager.compressImageByPixel(getImagePath(urlSpec), radio);
        } else {
            Log.d("compressFlag", "获取源图片");
        }
        return bitmap;
    }


    /*
    * 后台线程执行操作后返回图片bitmap,该方法执行更新UI的操作
    * */
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
      //  Log.d("WidthAndHeight", "Width=" + bitmap.getWidth() + ",Height=" + bitmap.getHeight());
        imageView.setImageBitmap(bitmap);
    }


    /*
        * 从网络下载图片并缓存
        * */
    private Bitmap downloadImage(String urlSpec) {
        URL url = null;
        InputStream is = null;
        HttpURLConnection urlConn = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        File imageFile = null;
        FileOutputStream fos = null;
        try {
            url = new URL(urlSpec);
            urlConn = (HttpURLConnection) url.openConnection();
            urlConn.setRequestMethod("GET");
            urlConn.setDoInput(true);
            urlConn.setConnectTimeout(5000);
            urlConn.setReadTimeout(5000);
            if (urlConn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                is = urlConn.getInputStream();
                imageFile = new File(getImagePath(urlSpec));
                if (qualityCompressFlag){
                    Log.d("qualityCompress","质量压缩图片");
                    bitmap = compressImageByQuality(is,imageFile.getPath());
                }else {
                    Log.d("qualityCompress","不压缩图片");
                    bitmap = SaveAndCacheImage(is,imageFile);
                }




            } else {
                Log.d("getResponseCode", "下载失败!");
            }


        } catch (IOException e) {
            e.printStackTrace();
        }
        if (bitmap != null) {
            imageCacheManager.addBitmapToCache(urlSpec, bitmap);
            return bitmap;
        }
        return null;
    }


    /*
    * 不压缩直接保存图片到本地外存自定位置并返回bitmap
    * */
    private Bitmap SaveAndCacheImage(InputStream is, File imageFile) {
        Bitmap bitmap = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;
        FileOutputStream fos = null;
        try {
            bis = new BufferedInputStream(is);
            fos = new FileOutputStream(imageFile);
            bos = new BufferedOutputStream(fos);
            int len = 0;
            byte[] b = new byte[1024];
            while ((len = bis.read(b)) != -1) {
                bos.write(b, 0, len);
                bos.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bis != null) {
                    bis.close();
                }
                if (bos != null) {
                    bos.close();
                }
                if (fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        bitmap = BitmapFactory.decodeFile(imageFile.getPath());
        return bitmap;
    }


    /*
    * 根据质量压缩返回图片
    * */
    private Bitmap compressImageByQuality(InputStream is, String imagePath) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        Bitmap bitmap = BitmapFactory.decodeStream(is);
        //压缩质量
        int quality = 100;
        //不压缩
        bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
        //得到原图片质量大小,单位为kb
        int size = baos.toByteArray().length / 1024;
        //压缩的目标质量大小,我们取原图质量的一半
        int targetSize = size / 2;
        Log.d("size+targetSize","size="+size+",targetSize="+targetSize);
        while (size > targetSize) {
            //因为需要循环重复压缩,所以需要重置baos
            baos.reset();
            //质量每次减少20,直到压缩到最小
            quality -= 20;
            if (quality < 0) {
                //0是压缩最小值
                quality = 0;
            }
            bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);
            //如果质量已经是0则退出循环
            if (quality == 0) {
                break;
            }
        }
        try {
            FileOutputStream fos = new FileOutputStream(new File(imagePath));
            fos.write(baos.toByteArray());
            fos.flush();
            fos.close();


        } catch (IOException e) {
            e.printStackTrace();
        }
        //保存后取出
        bitmap = BitmapFactory.decodeFile(imagePath);
        Log.d("targetByteCount","targetByteCount="+bitmap.getByteCount()/1024);


        return bitmap;
    }


    /*
* 该方法用来获取缓存图片的全地址也就是绝对地址+文件名
*
* */
    private String getImagePath(String urlSpec) {
        //截取urlSpec最后一个“/”后面的字符串作为文件名
        String imageName = urlSpec.substring(urlSpec.lastIndexOf("/") + 1);
        //定义图片缓存本地路径
        String imageDir = Environment.getExternalStorageDirectory() + "/CompressImage/";
        File file = new File(imageDir);
        if (!file.exists()) {
            file.mkdirs();
            Log.d("file", "file make success");
        }
        String path = imageDir + imageName;
        Log.d("path", path);
        return path;
    }
}



(2)显示效果截图


android 内存压缩技术_像素压缩_02



(3)Log执行步骤输出



09-26 02:54:36.652 5107-5166/com.example.compressimage D/qualityCompress: 质量压缩图片
09-26 02:54:36.676 5107-5166/com.example.compressimage D/dalvikvm: GC_FOR_ALLOC freed 278K, 13% free 2684K/3056K, paused 23ms, total 23ms
09-26 02:54:36.684 5107-5166/com.example.compressimage D/size+targetSize: size=18,targetSize=9
09-26 02:54:36.696 5107-5166/com.example.compressimage D/dalvikvm: GC_FOR_ALLOC freed 102K, 13% free 2858K/3272K, paused 2ms, total 2ms
09-26 02:54:36.696 5107-5166/com.example.compressimage D/targetByteCount: targetByteCount=126
09-26 02:54:36.696 5107-5166/com.example.compressimage D/Tag: 从网络中下载...
09-26 02:54:36.696 5107-5166/com.example.compressimage D/compressFlag: 获取源图片
09-26 02:54:38.572 5107-5197/com.example.compressimage D/Tag: 从LruCache中加载...
09-26 02:54:38.572 5107-5197/com.example.compressimage D/compressFlag: 获取源图片