项目内res里面的图片可以放入https://tinypng.com/里面压一压,较大的项目随随便便小个10来M。
对于网络加载下来的图片我们可以使用Picasso、Glide、Fresco进行压缩,具体怎么使用百度一下就行了
如果是从本地通过相册或者拍照上传图片的话,则需要通过以下方式进行压缩
一、使用第三方框架
image.png使用步骤
1、导入依赖
implementation 'top.zibin:Luban:1.1.8'
2、调用压缩方法
异步调用:Luban内部采用IO线程进行图片压缩,外部调用只需设置好结果监听即可。
Luban.with(this)
.load(photos)
.ignoreBy(100)
.setTargetDir(getPath())
.filter(new CompressionPredicate() {
@Override
public boolean apply(String path) {
return !(TextUtils.isEmpty(path) || path.toLowerCase().endsWith(".gif"));
}
})
.setCompressListener(new OnCompressListener() {
@Override
public void onStart() {
// TODO 压缩开始前调用,可以在方法内启动 loading UI
}
@Override
public void onSuccess(File file) {
// TODO 压缩成功后调用,返回压缩后的图片文件
}
@Override
public void onError(Throwable e) {
// TODO 当压缩过程出现问题时调用
}
}).launch();
同步调用:同步方法请尽量避免在主线程调用以免阻塞主线程,下面以rxJava调用为例:
Flowable.just(photos)
.observeOn(Schedulers.io())
.map(new Function<List<String>, List<File>>() {
@Override public List<File> apply(@NonNull List<String> list) throws Exception {
// 同步方法直接返回压缩后的文件
return Luban.with(MainActivity.this).load(list).get();
}
})
.observeOn(AndroidSchedulers.mainThread())
.subscribe();
二、自定义压缩工具类
package com.contoso.facetutorial;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.sql.Date;
import java.text.SimpleDateFormat;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.os.Environment;
/**
* PictureCompressUtil
* 图像压缩工厂类
*
* compressAndGenImage() 将图片按质量压缩成指定大小,并将图像生成指定的路径
* compressBySampleSize() 将图片按采样率进行压缩,并将图像生成指定的路径
*
* @author hzy
*
*/
public class PictureCompressUtil {
/**
* 按质量压缩,并将图像生成指定的路径
*
* @param imgPath
* @param outPath
* @param maxSize 目标将被压缩到小于这个大小(KB)。
* @param needsDelete 是否压缩后删除原始文件
* @throws IOException
*/
public void compressByQuality(String imgPath, String outPath, int maxSize, boolean needsDelete)
throws IOException {
compressAndGenImage(getBitmap(imgPath), outPath, maxSize);
// Delete original file
if (needsDelete) {
File file = new File(imgPath);
if (file.exists()) {
file.delete();
}
}
}
/**
* 压缩图片(质量压缩)
*
* @param bm 图片格式 jpeg,png,webp
* @param quality 图片的质量,0-100,数值越小质量越差
* @param maxSize 压缩后图片的最大kb值
*/
public static File compressByQuality(Bitmap bm,int quality,int maxSize) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, quality, bos);// 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中
long length=bos.toByteArray().length;
while (length/ 1024 > maxSize) { // 循环判断如果压缩后图片是否大于minSize,大于继续压缩
bos.reset();// 重置bos即清空bos
quality -= 5;// 每次都减少5
bm.compress(Bitmap.CompressFormat.JPEG, quality, bos);// 这里压缩options%,把压缩后的数据存放到baos中
length = bos.toByteArray().length;
}
SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss");
Date date = new Date(System.currentTimeMillis());
String filename = format.format(date);
File file = new File(Environment.getExternalStorageDirectory(), filename + ".png");
try {
FileOutputStream fos = new FileOutputStream(file);
try {
fos.write(bos.toByteArray());
fos.flush();
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
recycleBitmap(bm);
return file;
}
public static void recycleBitmap(Bitmap... bitmaps) {
if (bitmaps == null) {
return;
}
for (Bitmap bm : bitmaps) {
if (null != bm && !bm.isRecycled()) {
bm.recycle();
}
}
}
/**
* 通过像素压缩图像,这将改变图像的宽度/高度。用于获取缩略图
*
* @param imgPath image path
* @param pixelW 目标宽度像素
* @param pixelH 高度目标像素
* @return
*/
public Bitmap ratio(String imgPath, float pixelW, float pixelH) {
BitmapFactory.Options newOpts = new BitmapFactory.Options();
// 开始读入图片,此时把options.inJustDecodeBounds 设回true,即只读边不读内容
newOpts.inJustDecodeBounds = true;
newOpts.inPreferredConfig = Config.RGB_565;
// 获取位图信息,但请注意位图现在为空
Bitmap bitmap = BitmapFactory.decodeFile(imgPath, newOpts);
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
// 想要缩放的目标尺寸,现在大部分手机都是1080*1920,参考值可以让宽高都缩小一倍
float hh = pixelH;// 设置高度为960f时,可以明显看到图片缩小了
float ww = pixelW;// 设置宽度为540f,可以明显看到图片缩小了
// 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
int be = 1;// be=1表示不缩放
if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
be = 1;
newOpts.inSampleSize = be;// 设置缩放比例
// 开始压缩图片,注意此时已经把options.inJustDecodeBounds 设回false了
bitmap = BitmapFactory.decodeFile(imgPath, newOpts);
// 压缩好比例大小后再进行质量压缩
// return compressByQuality(bitmap, 100,1000); // 这里再进行质量压缩的意义不大,反而耗资源,删除
return bitmap;
}
/**
* 压缩图像的大小,这将修改图像宽度/高度。用于获取缩略图
*
* @param bm
* @param pixelW target pixel of width
* @param pixelH target pixel of height
* @return
*/
public Bitmap ratio(Bitmap bm, float pixelW, float pixelH) {
ByteArrayOutputStream os = new ByteArrayOutputStream();
bm.compress(Bitmap.CompressFormat.JPEG, 100, os);
if (os.toByteArray().length / 1024 > 1024) {// 判断如果图片大于1M,进行压缩避免在生成图片(BitmapFactory.decodeStream)时溢出
os.reset();// 重置baos即清空baos
bm.compress(Bitmap.CompressFormat.JPEG, 50, os);// 这里压缩50%,把压缩后的数据存放到baos中
}
ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray());
BitmapFactory.Options newOpts = new BitmapFactory.Options();
// 开始读入图片,此时把options.inJustDecodeBounds 设回true了
newOpts.inJustDecodeBounds = true;
newOpts.inPreferredConfig = Config.RGB_565;
Bitmap bitmap = BitmapFactory.decodeStream(is, null, newOpts);
newOpts.inJustDecodeBounds = false;
int w = newOpts.outWidth;
int h = newOpts.outHeight;
// 想要缩放的目标尺寸,现在大部分手机都是1080*1920,参考值可以让宽高都缩小一倍
float hh = pixelH;// 设置高度为960f时,可以明显看到图片缩小了
float ww = pixelW;// 设置宽度为540f,可以明显看到图片缩小了
// 缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
int be = 1;// be=1表示不缩放
if (w > h && w > ww) {// 如果宽度大的话根据宽度固定大小缩放
be = (int) (newOpts.outWidth / ww);
} else if (w < h && h > hh) {// 如果高度高的话根据宽度固定大小缩放
be = (int) (newOpts.outHeight / hh);
}
if (be <= 0)
be = 1;
newOpts.inSampleSize = be;// 设置缩放比例
// 重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
is = new ByteArrayInputStream(os.toByteArray());
bitmap = BitmapFactory.decodeStream(is, null, newOpts);
// 压缩好比例大小后再进行质量压缩
// return compress(bitmap, maxSize); // 这里再进行质量压缩的意义不大,反而耗资源,删除
return bitmap;
}
/**
* 按质量压缩,并将图像生成指定的路径
*
* @param bm
* @param outPath
* @param maxSize 目标将被压缩到小于这个大小(KB)。
* @throws IOException
*/
public void compressAndGenImage(Bitmap bm, String outPath, int maxSize) throws IOException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
// scale
int options = 100;
// Store the bitmap into output stream(no compress)
bm.compress(Bitmap.CompressFormat.JPEG, options, os);
// Compress by loop
while (os.toByteArray().length / 1024 > maxSize) {
// Clean up os
os.reset();
// interval 10
options -= 10;
bm.compress(Bitmap.CompressFormat.JPEG, options, os);
}
// Generate compressed image file
FileOutputStream fos = new FileOutputStream(outPath);
fos.write(os.toByteArray());
fos.flush();
fos.close();
}
/**
* 比例和生成图片的路径指定
*
* @param bm
* @param outPath
* @param pixelW 目标宽度像素
* @param pixelH 高度目标像素
* @throws FileNotFoundException
*/
public void compressBySampleSize(Bitmap bm, String outPath, float pixelW, float pixelH)
throws FileNotFoundException {
Bitmap bitmap = ratio(bm, pixelW, pixelH);
storeImage(bitmap, outPath);
}
/**
* 比例生成图片的路径指定
*
* @param imgPath
* @param outPath
* @param pixelW 目标宽度像素
* @param pixelH 高度目标像素
* @param needsDelete 是否压缩后删除原始文件
* @throws FileNotFoundException
*/
public void compressBySampleSize(String imgPath, String outPath, float pixelW, float pixelH, boolean needsDelete)
throws FileNotFoundException {
Bitmap bitmap = ratio(imgPath, pixelW, pixelH);
storeImage(bitmap, outPath);
if (needsDelete) {
File file = new File(imgPath);
if (file.exists()) {
file.delete();
}
}
}
/**
* 将位图存储到指定的图像路径中
*
* @param bm
* @param outPath
* @throws FileNotFoundException
*/
public void storeImage(Bitmap bm, String outPath) throws FileNotFoundException {
FileOutputStream os = new FileOutputStream(outPath);
bm.compress(Bitmap.CompressFormat.JPEG, 100, os);
}
/**
* 从指定的图像路径获取位图
* @param imgPath
* @return
*/
public Bitmap getBitmap(String imgPath) {
BitmapFactory.Options newOpts = new BitmapFactory.Options();
newOpts.inJustDecodeBounds = false;
newOpts.inPurgeable = true;
newOpts.inInputShareable = true;
newOpts.inSampleSize = 1;
newOpts.inPreferredConfig = Config.RGB_565;//设置RGB
return BitmapFactory.decodeFile(imgPath, newOpts);
}
}
常用的压缩方法详解
压缩方法分为:
1、质量压缩
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bit.compress(CompressFormat.JPEG, quality, baos);
byte[] bytes = baos.toByteArray();
bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
Log.i("pic", "压缩后图片的大小" + (bm.getByteCount() / 1024 / 1024)
+ "M宽度为" + bm.getWidth() + "高度为" + bm.getHeight()
+ "bytes.length= " + (bytes.length / 1024) + "KB"
+ "quality=" + quality);
质量压缩不会减少图片的像素,它是在保持像素的前提下改变图片的位深及透明度,来达到压缩图片的目的,图片的长,宽,像素都不会改变,那么bitmap所占内存大小是不会变的。
我们可以看到有个参数:quality,可以调节你压缩的比例,但是还要注意一点就是,质量压缩对png格式这种图片没有作用,因为png是无损压缩。
2.采样率压缩
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 2;
bm = BitmapFactory.decodeFile(Environment
.getExternalStorageDirectory().getAbsolutePath()
+ "/Camera/test.jpg", options);
Log.i("pic", "压缩后图片的大小" + (bm.getByteCount() / 1024 / 1024)
+ "M宽度为" + bm.getWidth() + "高度为" + bm.getHeight());
采样率压缩其原理是缩放bitamp的尺寸,通过调节其inSampleSize参数,比如调节为2,宽高会为原来的1/2,内存变回原来的1/4.
3.缩放法压缩(martix)
Matrix matrix = new Matrix();
matrix.setScale(0.5f, 0.5f);
bm = Bitmap.createBitmap(bit, 0, 0, bit.getWidth(),
bit.getHeight(), matrix, true);
Log.i("pic", "压缩后图片的大小" + (bm.getByteCount() / 1024 / 1024)
+ "M宽度为" + bm.getWidth() + "高度为" + bm.getHeight());
放缩法压缩使用的是通过矩阵对图片进行裁剪,也是通过缩放图片尺寸,来达到压缩图片的效果,和采样率的原理一样。
4.RGB_565压缩
BitmapFactory.Options options2 = new BitmapFactory.Options();
options2.inPreferredConfig = Bitmap.Config.RGB_565;
bm = BitmapFactory.decodeFile(Environment
.getExternalStorageDirectory().getAbsolutePath()
+ "/Camera/test.jpg", options2);
Log.i("pic", "压缩后图片的大小" + (bm.getByteCount() / 1024 / 1024)
+ "M宽度为" + bm.getWidth() + "高度为" + bm.getHeight());
RGB_565压缩是通过改用内存占用更小的编码格式来达到压缩的效果。Android默认的颜色模式为ARGB_8888,这个颜色模式色彩最细腻,显示质量最高。一般不建议使用ARGB_4444,因为画质实在是辣鸡,如果对透明度没有要求,建议可以改成RGB_565,相比ARGB_8888将节省一半的内存开销。
5.createScaledBitmap
bm = Bitmap.createScaledBitmap(bit, 150, 150, true);
Log.i("pic", "压缩后图片的大小" + (bm.getByteCount() / 1024) + "KB宽度为"
+ bm.getWidth() + "高度为" + bm.getHeight());
这里是将图片压缩成用户所期望的长度和宽度,但是这里要说,如果用户期望的长度和宽度和原图长度宽度相差太多的话,图片会很不清晰。
此方法与第3种方法实现方式类似,其源码如下:
public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, int dstHeight, boolean filter) {
Matrix m;
synchronized (Bitmap.class) { // small pool of just 1 matrix
m = sScaleMatrix;
sScaleMatrix = null;
}
if (m == null) {
m = new Matrix();
}
final int width = src.getWidth();
final int height = src.getHeight();
final float sx = dstWidth / (float)width;
final float sy = dstHeight / (float)height; m.setScale(sx, sy);
Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter);
synchronized (Bitmap.class) { // do we need to check for null? why not just assign everytime?
if (sScaleMatrix == null) {
sScaleMatrix = m;
}
}
return b;
}
以上就是5种图片压缩的方法,这里需要强调,他们的压缩仅仅只是对Android中的Bitmap来说的。如果将这些压缩后的Bitmap另存为sd中,他们的内存大小并不一样。
Android手机中,图片的所占的内存大小和很多因素相关,计算起来也很麻烦。为了计算出一个图片的内存大小,可以将图片当做一个文件来间接计算,用如下的方法:
File file = new File(Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/Camera/test.jpg");
Log.i("pic", "file.length()=" + file.length() / 1024);
或者
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
Log.i("pic", "fis.available()=" + fis.available() / 1024);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
上面两个方法计算的结果是一样的。
五、总结
Bitmap所占用的内存 = 图片长度 x 图片宽度 x 一个像素点占用的字节数。3个参数,任意减少一个的值,都能达到了压缩的效果,只是效果不尽相同。
另外我们这里的介绍的压缩方法只是针对在运行加载的bitmap占用内存的大小。我们在做App内存优化的时候,一般可以从这两个方面入手,一个内存泄漏,另外一个是Bitmap压缩了,在要求像素不高的情况下,可以对Bitmap进行压缩,并且针对一些只使用一次的Bitmap,要做好recycle的处理。
参考链接:
https://www.jianshu.com/p/3ac26611bc0d
https://blog.csdn.net/HarryWeasley/article/details/51955467
https://www.jianshu.com/p/08ed0e3c4e71
网友评论