├── .gitattributes ├── .gitignore ├── .idea ├── checkstyle-idea.xml ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── liujc │ │ └── androidpiccompress │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── liujc │ │ │ └── androidpiccompress │ │ │ ├── MainActivity.java │ │ │ └── PermissionsChecker.java │ └── res │ │ ├── drawable │ │ └── addphoto_button.png │ │ ├── layout │ │ ├── activity_main.xml │ │ └── content_main.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── liujc │ └── androidpiccompress │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lib_jpeg_compress ├── .gitignore ├── build.gradle ├── libs │ ├── armeabi-v7a │ │ ├── libbitherjni.so │ │ └── libjpegbither.so │ └── armeabi │ │ ├── libbitherjni.so │ │ └── libjpegbither.so ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── net │ │ └── bitch │ │ └── util │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ ├── jpegcompress │ │ │ └── FileUtils.java │ │ └── net │ │ │ └── bither │ │ │ └── util │ │ │ └── NativeUtil.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── net │ └── bitch │ └── util │ └── ExampleUnitTest.java ├── screenshot └── 1.png └── settings.gradle /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/checkstyle-idea.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 26 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 53 | 54 | 55 | 56 | 57 | 1.8 58 | 59 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AndroidPicCompress 2 | 3 | 具体使用方法和分析可参考博文[【Android中图片压缩方案详解】](https://www.jianshu.com/p/0b4854aae105) 4 | 5 | 图片的展示可以说在我们任何一个应用中都避免不了,可是大量的图片就会出现很多的问题,比如加载大图片或者多图时的OOM问题,可以移步到[Android高效加载大图、多图避免程序OOM](http://www.jianshu.com/p/da754f9fad51).还有一个问题就是图片的上传下载问题,往往我们都喜欢图片既清楚又占的内存小,也就是尽可能少的耗费我们的流量,这就是我今天所要讲述的问题:图片的压缩方案的详解。 6 | 7 | ## 1、质量压缩法 8 | 9 | 设置bitmap options属性,降低图片的质量,像素不会减少 10 | 第一个参数为需要压缩的bitmap图片对象,第二个参数为压缩后图片保存的位置 11 | 设置options 属性0-100,来实现压缩。 12 | 13 | ``` 14 | private Bitmap compressImage(Bitmap image) { 15 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 16 | image.compress(Bitmap.CompressFormat.JPEG, 100, baos);//质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 17 | int options = 100; 18 | while ( baos.toByteArray().length / 1024>100) { //循环判断如果压缩后图片是否大于100kb,大于继续压缩 19 | baos.reset();//重置baos即清空baos 20 | image.compress(Bitmap.CompressFormat.JPEG, options, baos);//这里压缩options%,把压缩后的数据存放到baos中 21 | options -= 10;//每次都减少10 22 | } 23 | ByteArrayInputStream isBm = new ByteArrayInputStream(baos.toByteArray());//把压缩后的数据baos存放到ByteArrayInputStream中 24 | Bitmap bitmap = BitmapFactory.decodeStream(isBm, null, null);//把ByteArrayInputStream数据生成图片 25 | return bitmap; 26 | } 27 | ``` 28 | 质量压缩不会减少图片的像素。它是在保持像素不变的前提下改变图片的位深及透明度等,来达到压缩图片的目的。进过它压缩的图片文件大小会有改变,但是导入成bitmap后占得内存是不变的。因为要保持像素不变,所以它就无法无限压缩,到达一个值之后就不会继续变小了。显然这个方法并不适用于缩略图,其实也不适用于想通过压缩图片减少内存的适用,仅仅适用于想在保证图片质量的同时减少文件大小的情况而已。 29 | 30 | ## 2、采样率压缩法 31 | 32 | ``` 33 | private Bitmap getimage(String srcPath) { 34 | BitmapFactory.Options newOpts = new BitmapFactory.Options(); 35 | //开始读入图片,此时把options.inJustDecodeBounds 设回true了 36 | newOpts.inJustDecodeBounds = true; 37 | Bitmap bitmap = BitmapFactory.decodeFile(srcPath,newOpts);//此时返回bm为空 38 | newOpts.inJustDecodeBounds = false; 39 | int w = newOpts.outWidth; 40 | int h = newOpts.outHeight; 41 | //现在主流手机比较多是1280*720分辨率,所以高和宽我们设置为 42 | float hh = 1280f;//这里设置高度为1280f 43 | float ww = 720f;//这里设置宽度为720f 44 | //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 45 | int be = 1;//be=1表示不缩放 46 | if (w > h && w > ww) {//如果宽度大的话根据宽度固定大小缩放 47 | be = (int) (newOpts.outWidth / ww); 48 | } else if (w < h && h > hh) {//如果高度高的话根据宽度固定大小缩放 49 | be = (int) (newOpts.outHeight / hh); 50 | } 51 | if (be <= 0) 52 | be = 1; 53 | newOpts.inSampleSize = be;//设置缩放比例 54 | //重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了 55 | bitmap = BitmapFactory.decodeFile(srcPath, newOpts); 56 | return compressImage(bitmap);//压缩好比例大小后再进行质量压缩 57 | } 58 | ``` 59 | 60 | 这个方法的好处是大大的缩小了内存的使用,在读存储器上的图片时,如果不需要高清的效果,可以先只读取图片的边,通过宽和高设定好取样率后再加载图片,这样就不会过多的占用内存。 61 | 62 | ## 3、缩放法 63 | 64 | 通过缩放图片像素来减少图片占用内存大小。 65 | 66 | + **方式一** 67 | ``` 68 | public static void compressBitmapToFile(Bitmap bmp, File file){ 69 | // 尺寸压缩倍数,值越大,图片尺寸越小 70 | int ratio = 2; 71 | // 压缩Bitmap到对应尺寸 72 | Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Config.ARGB_8888); 73 | Canvas canvas = new Canvas(result); 74 | Rect rect = new Rect(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio); 75 | canvas.drawBitmap(bmp, null, rect, null); 76 | 77 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 78 | // 把压缩后的数据存放到baos中 79 | result.compress(Bitmap.CompressFormat.JPEG, 100 ,baos); 80 | try { 81 | FileOutputStream fos = new FileOutputStream(file); 82 | fos.write(baos.toByteArray()); 83 | fos.flush(); 84 | fos.close(); 85 | } catch (Exception e) { 86 | e.printStackTrace(); 87 | } 88 | } 89 | ``` 90 | 91 | + **方式二** 92 | 93 | ``` 94 | ByteArrayOutputStream out = new ByteArrayOutputStream(); 95 | image.compress(Bitmap.CompressFormat.JPEG, 85, out); 96 | float zoom = (float)Math.sqrt(size * 1024 / (float)out.toByteArray().length); 97 | 98 | Matrix matrix = new Matrix(); 99 | matrix.setScale(zoom, zoom); 100 | 101 | Bitmap result = Bitmap.createBitmap(image, 0, 0, image.getWidth(), image.getHeight(), matrix, true); 102 | 103 | out.reset(); 104 | result.compress(Bitmap.CompressFormat.JPEG, 85, out); 105 | while(out.toByteArray().length > size * 1024){ 106 | System.out.println(out.toByteArray().length); 107 | matrix.setScale(0.9f, 0.9f); 108 | result = Bitmap.createBitmap(result, 0, 0, result.getWidth(), result.getHeight(), matrix, true); 109 | out.reset(); 110 | result.compress(Bitmap.CompressFormat.JPEG, 85, out); 111 | } 112 | ``` 113 | 114 | 缩放法其实很简单,设定好matrix,在createBitmap就可以了。但是我们并不知道缩放比例,而是要求了图片的最终大小。直接用大小的比例来做的话肯定是有问题的,用大小比例的开方来做会比较接近,但是还是有差距。但是只要再做一下微调应该就可以了,微调的话就是修改过的图片大小比最终大小还大的话,就进行0.8的压缩再比较,循环直到大小合适。这样就能得到合适大小的图片,而且也能比较保证质量。 115 | 116 | ## 4、JNI调用libjpeg库压缩 117 | 118 | JNI静态调用 `bitherlibjni.c` 中的方法来实现压缩`Java_net_bither_util_NativeUtil_compressBitmap` 119 | net_bither_util为包名,NativeUtil为类名,compressBitmap为native方法名,我们只需要调用saveBitmap()方法就可以,bmp 需要压缩的Bitmap对象, quality压缩质量0-100, fileName 压缩后要保存的文件地址, optimize 是否采用哈弗曼表数据计算 品质相差5-10倍。 120 | 121 | ``` 122 | jstring Java_net_bither_util_NativeUtil_compressBitmap(JNIEnv* env, 123 | jobject thiz, jobject bitmapcolor, int w, int h, int quality, 124 | jbyteArray fileNameStr, jboolean optimize) { 125 | 126 | AndroidBitmapInfo infocolor; 127 | BYTE* pixelscolor; 128 | int ret; 129 | BYTE * data; 130 | BYTE *tmpdata; 131 | char * fileName = jstrinTostring(env, fileNameStr); 132 | if ((ret = AndroidBitmap_getInfo(env, bitmapcolor, &infocolor)) < 0) { 133 | LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); 134 | return (*env)->NewStringUTF(env, "0");; 135 | } 136 | if ((ret = AndroidBitmap_lockPixels(env, bitmapcolor, &pixelscolor)) < 0) { 137 | LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); 138 | } 139 | 140 | BYTE r, g, b; 141 | data = NULL; 142 | data = malloc(w * h * 3); 143 | tmpdata = data; 144 | int j = 0, i = 0; 145 | int color; 146 | for (i = 0; i < h; i++) { 147 | for (j = 0; j < w; j++) { 148 | color = *((int *) pixelscolor); 149 | r = ((color & 0x00FF0000) >> 16); 150 | g = ((color & 0x0000FF00) >> 8); 151 | b = color & 0x000000FF; 152 | *data = b; 153 | *(data + 1) = g; 154 | *(data + 2) = r; 155 | data = data + 3; 156 | pixelscolor += 4; 157 | 158 | } 159 | 160 | } 161 | AndroidBitmap_unlockPixels(env, bitmapcolor); 162 | int resultCode= generateJPEG(tmpdata, w, h, quality, fileName, optimize); 163 | free(tmpdata); 164 | if(resultCode==0){ 165 | jstring result=(*env)->NewStringUTF(env, error); 166 | error=NULL; 167 | return result; 168 | } 169 | return (*env)->NewStringUTF(env, "1"); //success 170 | } 171 | ``` 172 | 173 | ## 5、质量压缩+采样率压缩+JNI调用libjpeg库压缩结合使用 174 | 175 | 首先通过尺寸压缩,压缩到手机常用的一个分辨率(1280*960 微信好像是压缩到这个分辨率),然后我们要把图片压缩到一定大小以内(比如说200k),然后通过循环进行质量压缩来计算options需要设置为多少,最后调用JNI压缩。效果如下图: 176 | ![1.png](https://upload-images.jianshu.io/upload_images/3678546-597fdb06c17dba8b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 177 | 178 | + 计算缩放比 179 | 180 | ``` 181 | /** 182 | * 计算缩放比 183 | * @param bitWidth 当前图片宽度 184 | * @param bitHeight 当前图片高度 185 | * @return int 缩放比 186 | */ 187 | public static int getRatioSize(int bitWidth, int bitHeight) { 188 | // 图片最大分辨率 189 | int imageHeight = 1280; 190 | int imageWidth = 960; 191 | // 缩放比 192 | int ratio = 1; 193 | // 缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 194 | if (bitWidth > bitHeight && bitWidth > imageWidth) { 195 | // 如果图片宽度比高度大,以宽度为基准 196 | ratio = bitWidth / imageWidth; 197 | } else if (bitWidth < bitHeight && bitHeight > imageHeight) { 198 | // 如果图片高度比宽度大,以高度为基准 199 | ratio = bitHeight / imageHeight; 200 | } 201 | // 最小比率为1 202 | if (ratio <= 0) 203 | ratio = 1; 204 | return ratio; 205 | } 206 | ``` 207 | 208 | + 质量压缩+JNI压缩 209 | 210 | ``` 211 | /** 212 | * @Description: 通过JNI图片压缩把Bitmap保存到指定目录 213 | * @param curFilePath 214 | * 当前图片文件地址 215 | * @param targetFilePath 216 | * 要保存的图片文件地址 217 | */ 218 | public static void compressBitmap(String curFilePath, String targetFilePath) { 219 | // 最大图片大小 500KB 220 | int maxSize = 500; 221 | //根据地址获取bitmap 222 | Bitmap result = getBitmapFromFile(curFilePath); 223 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 224 | // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 225 | int quality = 100; 226 | result.compress(Bitmap.CompressFormat.JPEG, quality, baos); 227 | // 循环判断如果压缩后图片是否大于500kb,大于继续压缩 228 | while (baos.toByteArray().length / 1024 > maxSize) { 229 | // 重置baos即清空baos 230 | baos.reset(); 231 | // 每次都减少10 232 | quality -= 10; 233 | // 这里压缩quality,把压缩后的数据存放到baos中 234 | result.compress(Bitmap.CompressFormat.JPEG, quality, baos); 235 | } 236 | // JNI保存图片到SD卡 这个关键 237 | NativeUtil.saveBitmap(result, quality, targetFilePath, true); 238 | // 释放Bitmap 239 | if (!result.isRecycled()) { 240 | result.recycle(); 241 | } 242 | } 243 | ``` 244 | 245 | + JNI图片压缩工具类 246 | 247 | ``` 248 | package net.bither.util; 249 | import android.graphics.Bitmap; 250 | import android.graphics.Bitmap.Config; 251 | import android.graphics.BitmapFactory; 252 | import android.graphics.Canvas; 253 | import android.graphics.Matrix; 254 | import android.graphics.Rect; 255 | import android.media.ExifInterface; 256 | import java.io.ByteArrayOutputStream; 257 | import java.io.File; 258 | import java.io.FileInputStream; 259 | import java.io.FileNotFoundException; 260 | import java.io.IOException; 261 | /** 262 | * JNI图片压缩工具类 263 | * 264 | * @Description TODO 265 | * @Package net.bither.util 266 | * @Class NativeUtil 267 | */ 268 | public class NativeUtil { 269 | 270 | private static int DEFAULT_QUALITY = 95; 271 | 272 | /** 273 | * @Description: JNI基本压缩 274 | * @param bit 275 | * bitmap对象 276 | * @param fileName 277 | * 指定保存目录名 278 | * @param optimize 279 | * 是否采用哈弗曼表数据计算 品质相差5-10倍 280 | */ 281 | public static void compressBitmap(Bitmap bit, String fileName, boolean optimize) { 282 | saveBitmap(bit, DEFAULT_QUALITY, fileName, optimize); 283 | } 284 | 285 | /** 286 | * @Description: 通过JNI图片压缩把Bitmap保存到指定目录 287 | * @param image 288 | * bitmap对象 289 | * @param filePath 290 | * 要保存的指定目录 291 | */ 292 | public static void compressBitmap(Bitmap image, String filePath) { 293 | // 最大图片大小 150KB 294 | int maxSize = 150; 295 | // 获取尺寸压缩倍数 296 | int ratio = NativeUtil.getRatioSize(image.getWidth(),image.getHeight()); 297 | // 压缩Bitmap到对应尺寸 298 | Bitmap result = Bitmap.createBitmap(image.getWidth() / ratio,image.getHeight() / ratio, Config.ARGB_8888); 299 | Canvas canvas = new Canvas(result); 300 | Rect rect = new Rect(0, 0, image.getWidth() / ratio, image.getHeight() / ratio); 301 | canvas.drawBitmap(image,null,rect,null); 302 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 303 | // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 304 | int options = 100; 305 | result.compress(Bitmap.CompressFormat.JPEG, options, baos); 306 | // 循环判断如果压缩后图片是否大于100kb,大于继续压缩 307 | while (baos.toByteArray().length / 1024 > maxSize) { 308 | // 重置baos即清空baos 309 | baos.reset(); 310 | // 每次都减少10 311 | options -= 10; 312 | // 这里压缩options%,把压缩后的数据存放到baos中 313 | result.compress(Bitmap.CompressFormat.JPEG, options, baos); 314 | } 315 | // JNI保存图片到SD卡 这个关键 316 | NativeUtil.saveBitmap(result, options, filePath, true); 317 | // 释放Bitmap 318 | if (!result.isRecycled()) { 319 | result.recycle(); 320 | } 321 | } 322 | 323 | /** 324 | * @Description: 通过JNI图片压缩把Bitmap保存到指定目录 325 | * @param curFilePath 326 | * 当前图片文件地址 327 | * @param targetFilePath 328 | * 要保存的图片文件地址 329 | */ 330 | public static void compressBitmap(String curFilePath, String targetFilePath) { 331 | // 最大图片大小 500KB 332 | int maxSize = 500; 333 | //根据地址获取bitmap 334 | Bitmap result = getBitmapFromFile(curFilePath); 335 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 336 | // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 337 | int quality = 100; 338 | result.compress(Bitmap.CompressFormat.JPEG, quality, baos); 339 | // 循环判断如果压缩后图片是否大于500kb,大于继续压缩 340 | while (baos.toByteArray().length / 1024 > maxSize) { 341 | // 重置baos即清空baos 342 | baos.reset(); 343 | // 每次都减少10 344 | quality -= 10; 345 | // 这里压缩quality,把压缩后的数据存放到baos中 346 | result.compress(Bitmap.CompressFormat.JPEG, quality, baos); 347 | } 348 | // JNI保存图片到SD卡 这个关键 349 | NativeUtil.saveBitmap(result, quality, targetFilePath, true); 350 | // 释放Bitmap 351 | if (!result.isRecycled()) { 352 | result.recycle(); 353 | } 354 | 355 | } 356 | 357 | /** 358 | * 计算缩放比 359 | * @param bitWidth 当前图片宽度 360 | * @param bitHeight 当前图片高度 361 | * @return int 缩放比 362 | */ 363 | public static int getRatioSize(int bitWidth, int bitHeight) { 364 | // 图片最大分辨率 365 | int imageHeight = 1280; 366 | int imageWidth = 960; 367 | // 缩放比 368 | int ratio = 1; 369 | // 缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 370 | if (bitWidth > bitHeight && bitWidth > imageWidth) { 371 | // 如果图片宽度比高度大,以宽度为基准 372 | ratio = bitWidth / imageWidth; 373 | } else if (bitWidth < bitHeight && bitHeight > imageHeight) { 374 | // 如果图片高度比宽度大,以高度为基准 375 | ratio = bitHeight / imageHeight; 376 | } 377 | // 最小比率为1 378 | if (ratio <= 0) 379 | ratio = 1; 380 | return ratio; 381 | } 382 | 383 | /** 384 | * 通过文件路径读获取Bitmap防止OOM以及解决图片旋转问题 385 | * @param filePath 386 | * @return 387 | */ 388 | public static Bitmap getBitmapFromFile(String filePath){ 389 | BitmapFactory.Options newOpts = new BitmapFactory.Options(); 390 | newOpts.inJustDecodeBounds = true;//只读边,不读内容 391 | BitmapFactory.decodeFile(filePath, newOpts); 392 | int w = newOpts.outWidth; 393 | int h = newOpts.outHeight; 394 | // 获取尺寸压缩倍数 395 | newOpts.inSampleSize = NativeUtil.getRatioSize(w,h); 396 | newOpts.inJustDecodeBounds = false;//读取所有内容 397 | newOpts.inDither = false; 398 | newOpts.inPurgeable=true; 399 | newOpts.inInputShareable=true; 400 | newOpts.inTempStorage = new byte[32 * 1024]; 401 | Bitmap bitmap = null; 402 | File file = new File(filePath); 403 | FileInputStream fs = null; 404 | try { 405 | fs = new FileInputStream(file); 406 | } catch (FileNotFoundException e) { 407 | e.printStackTrace(); 408 | } 409 | try { 410 | if(fs!=null){ 411 | bitmap = BitmapFactory.decodeFileDescriptor(fs.getFD(),null,newOpts); 412 | //旋转图片 413 | int photoDegree = readPictureDegree(filePath); 414 | if(photoDegree != 0){ 415 | Matrix matrix = new Matrix(); 416 | matrix.postRotate(photoDegree); 417 | // 创建新的图片 418 | bitmap = Bitmap.createBitmap(bitmap, 0, 0, 419 | bitmap.getWidth(), bitmap.getHeight(), matrix, true); 420 | } 421 | } 422 | } catch (IOException e) { 423 | e.printStackTrace(); 424 | } finally{ 425 | if(fs!=null) { 426 | try { 427 | fs.close(); 428 | } catch (IOException e) { 429 | e.printStackTrace(); 430 | } 431 | } 432 | } 433 | return bitmap; 434 | } 435 | 436 | /** 437 | * 438 | * 读取图片属性:旋转的角度 439 | * @param path 图片绝对路径 440 | * @return degree旋转的角度 441 | */ 442 | 443 | public static int readPictureDegree(String path) { 444 | int degree = 0; 445 | try { 446 | ExifInterface exifInterface = new ExifInterface(path); 447 | int orientation = exifInterface.getAttributeInt( 448 | ExifInterface.TAG_ORIENTATION, 449 | ExifInterface.ORIENTATION_NORMAL); 450 | switch (orientation) { 451 | case ExifInterface.ORIENTATION_ROTATE_90: 452 | degree = 90; 453 | break; 454 | case ExifInterface.ORIENTATION_ROTATE_180: 455 | degree = 180; 456 | break; 457 | case ExifInterface.ORIENTATION_ROTATE_270: 458 | degree = 270; 459 | break; 460 | } 461 | } catch (IOException e) { 462 | e.printStackTrace(); 463 | } 464 | return degree; 465 | } 466 | 467 | /** 468 | * 调用native方法 469 | * @Description:函数描述 470 | * @param bit 471 | * @param quality 472 | * @param fileName 473 | * @param optimize 474 | */ 475 | private static void saveBitmap(Bitmap bit, int quality, String fileName, boolean optimize) { 476 | compressBitmap(bit, bit.getWidth(), bit.getHeight(), quality, fileName.getBytes(), optimize); 477 | } 478 | 479 | /** 480 | * 调用底层 bitherlibjni.c中的方法 481 | * @Description:函数描述 482 | * @param bit 483 | * @param w 484 | * @param h 485 | * @param quality 486 | * @param fileNameBytes 487 | * @param optimize 488 | * @return 489 | */ 490 | private static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes, 491 | boolean optimize); 492 | /** 493 | * 加载lib下两个so文件 494 | */ 495 | static { 496 | System.loadLibrary("jpegbither"); 497 | System.loadLibrary("bitherjni"); 498 | } 499 | } 500 | ``` 501 | 502 | ## 图片压缩处理中可能遇到的问题: 503 | 504 | + 请求系统相册有三个Action 505 | 506 | 注意:图库(缩略图) 和 图片(原图) 507 | 508 | + `ACTION_OPEN_DOCUMENT ` 仅限4.4或以上使用 默认打开原图 509 | 510 | 从图片获取到的uri 格式为:`content://com.android.providers.media.documents/document/image%666>>>` 511 | 512 | + `ACTION_GET_CONTENT ` 4.4以下默认打开缩略图 。 以上打开文件管理器 供选择,选择图库打开为缩略图页面,选择图片打开为原图浏览。 513 | 514 | 从图库获取到的uri格式为:`content://media/external/images/media/666666` 515 | 516 | + `ACTION_PICK ` 都可用,打开默认是缩略图界面,还需要进一步点开查看。 517 | 518 | 参考代码: 519 | ``` 520 | public void pickFromGallery() { 521 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 522 | startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("image/*"), 523 | REQUEST_PICK_IMAGE); 524 | } else { 525 | Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); 526 | intent.addCategory(Intent.CATEGORY_OPENABLE); 527 | intent.setType("image/*"); 528 | startActivityForResult(intent, REQUEST_KITKAT_PICK_IMAGE); 529 | } 530 | } 531 | ``` 532 | 533 | + 根据URI获取对应的文件路径 534 | 535 | 在我们从图库中选择图片后回调给我们的`data.getData()`可能是URI,我们平时对文件的操作基本上都是基于路径然后进行各种操作与转换,如今我们需要将URI对应的文件路径找出来,具体参考代码如下: 536 | 537 | ``` 538 | public static String getPathByUri(Context context, Uri data){ 539 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 540 | return getPathByUri4BeforeKitkat(context, data); 541 | }else { 542 | return getPathByUri4AfterKitkat(context, data); 543 | } 544 | } 545 | //4.4以前通过Uri获取路径:data是Uri,filename是一个String的字符串,用来保存路径 546 | public static String getPathByUri4BeforeKitkat(Context context, Uri data) { 547 | String filename=null; 548 | if (data.getScheme().toString().compareTo("content") == 0) { 549 | Cursor cursor = context.getContentResolver().query(data, new String[] { "_data" }, null, null, null); 550 | if (cursor.moveToFirst()) { 551 | filename = cursor.getString(0); 552 | } 553 | } else if (data.getScheme().toString().compareTo("file") == 0) {// file:///开头的uri 554 | filename = data.toString().replace("file://", "");// 替换file:// 555 | if (!filename.startsWith("/mnt")) {// 加上"/mnt"头 556 | filename += "/mnt"; 557 | } 558 | } 559 | return filename; 560 | } 561 | //4.4以后根据Uri获取路径: 562 | @SuppressLint("NewApi") 563 | public static String getPathByUri4AfterKitkat(final Context context, final Uri uri) { 564 | final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 565 | // DocumentProvider 566 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { 567 | if (isExternalStorageDocument(uri)) {// ExternalStorageProvider 568 | final String docId = DocumentsContract.getDocumentId(uri); 569 | final String[] split = docId.split(":"); 570 | final String type = split[0]; 571 | if ("primary".equalsIgnoreCase(type)) { 572 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 573 | } 574 | } else if (isDownloadsDocument(uri)) {// DownloadsProvider 575 | final String id = DocumentsContract.getDocumentId(uri); 576 | final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), 577 | Long.valueOf(id)); 578 | return getDataColumn(context, contentUri, null, null); 579 | } else if (isMediaDocument(uri)) {// MediaProvider 580 | final String docId = DocumentsContract.getDocumentId(uri); 581 | final String[] split = docId.split(":"); 582 | final String type = split[0]; 583 | Uri contentUri = null; 584 | if ("image".equals(type)) { 585 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 586 | } else if ("video".equals(type)) { 587 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 588 | } else if ("audio".equals(type)) { 589 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 590 | } 591 | final String selection = "_id=?"; 592 | final String[] selectionArgs = new String[] { split[1] }; 593 | return getDataColumn(context, contentUri, selection, selectionArgs); 594 | } 595 | } else if ("content".equalsIgnoreCase(uri.getScheme())) {// MediaStore 596 | // (and 597 | // general) 598 | return getDataColumn(context, uri, null, null); 599 | } else if ("file".equalsIgnoreCase(uri.getScheme())) {// File 600 | return uri.getPath(); 601 | } 602 | return null; 603 | } 604 | 605 | /** 606 | * Get the value of the data column for this Uri. This is useful for 607 | * MediaStore Uris, and other file-based ContentProviders. 608 | * 609 | * @param context 610 | * The context. 611 | * @param uri 612 | * The Uri to query. 613 | * @param selection 614 | * (Optional) Filter used in the query. 615 | * @param selectionArgs 616 | * (Optional) Selection arguments used in the query. 617 | * @return The value of the _data column, which is typically a file path. 618 | */ 619 | public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { 620 | Cursor cursor = null; 621 | final String column = "_data"; 622 | final String[] projection = { column }; 623 | try { 624 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); 625 | if (cursor != null && cursor.moveToFirst()) { 626 | final int column_index = cursor.getColumnIndexOrThrow(column); 627 | return cursor.getString(column_index); 628 | } 629 | } finally { 630 | if (cursor != null) 631 | cursor.close(); 632 | } 633 | return null; 634 | } 635 | 636 | /** 637 | * @param uri 638 | * The Uri to check. 639 | * @return Whether the Uri authority is ExternalStorageProvider. 640 | */ 641 | public static boolean isExternalStorageDocument(Uri uri) { 642 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 643 | } 644 | 645 | /** 646 | * @param uri 647 | * The Uri to check. 648 | * @return Whether the Uri authority is DownloadsProvider. 649 | */ 650 | public static boolean isDownloadsDocument(Uri uri) { 651 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 652 | } 653 | 654 | /** 655 | * @param uri 656 | * The Uri to check. 657 | * @return Whether the Uri authority is MediaProvider. 658 | */ 659 | public static boolean isMediaDocument(Uri uri) { 660 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 661 | } 662 | ``` 663 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.1" 6 | defaultConfig { 7 | applicationId "com.liujc.androidpiccompress" 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(include: ['*.jar'], dir: 'libs') 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | compile 'com.android.support:appcompat-v7:25.0.1' 28 | compile 'com.android.support:design:25.0.1' 29 | testCompile 'junit:junit:4.12' 30 | compile project(':lib_jpeg_compress') 31 | compile 'com.github.bumptech.glide:glide:3.7.0' 32 | } 33 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\Android-sdk\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/liujc/androidpiccompress/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.liujc.androidpiccompress; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.liujc.androidpiccompress", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/liujc/androidpiccompress/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.liujc.androidpiccompress; 2 | 3 | import android.Manifest; 4 | import android.annotation.TargetApi; 5 | import android.app.Activity; 6 | import android.content.Context; 7 | import android.content.Intent; 8 | import android.net.Uri; 9 | import android.os.Build; 10 | import android.os.Environment; 11 | import android.support.design.widget.FloatingActionButton; 12 | import android.support.v4.app.ActivityCompat; 13 | import android.support.v7.app.AppCompatActivity; 14 | import android.os.Bundle; 15 | import android.text.TextUtils; 16 | import android.util.Log; 17 | import android.view.View; 18 | import android.widget.ImageView; 19 | import android.widget.TextView; 20 | 21 | import com.bumptech.glide.Glide; 22 | 23 | import net.bither.util.NativeUtil; 24 | 25 | import java.io.File; 26 | 27 | import jpegcompress.FileUtils; 28 | 29 | public class MainActivity extends AppCompatActivity { 30 | 31 | private TextView fileSize; 32 | private TextView imageSize; 33 | private TextView thumbFileSize; 34 | private TextView thumbImageSize; 35 | private ImageView image; 36 | public static final int REQUEST_PICK_IMAGE = 10011; 37 | public static final int REQUEST_KITKAT_PICK_IMAGE = 10012; 38 | @Override 39 | protected void onCreate(Bundle savedInstanceState) { 40 | super.onCreate(savedInstanceState); 41 | setContentView(R.layout.activity_main); 42 | doCheckPermission(); 43 | initView(); 44 | } 45 | 46 | final String[] PERMISSIONS = new String[]{ 47 | Manifest.permission.WRITE_EXTERNAL_STORAGE, 48 | Manifest.permission.READ_EXTERNAL_STORAGE, 49 | Manifest.permission.CAMERA, 50 | }; 51 | //检查所需的全部权限 52 | public boolean doCheckPermission() { 53 | PermissionsChecker mPermissionsChecker = new PermissionsChecker(MainActivity.this); 54 | if (mPermissionsChecker.lacksPermissions(PERMISSIONS)) { 55 | ActivityCompat.requestPermissions(MainActivity.this, PERMISSIONS, 0x12); 56 | return false; 57 | } 58 | return true; 59 | } 60 | 61 | private void initView() { 62 | fileSize = (TextView) findViewById(R.id.file_size); 63 | imageSize = (TextView) findViewById(R.id.image_size); 64 | thumbFileSize = (TextView) findViewById(R.id.thumb_file_size); 65 | thumbImageSize = (TextView) findViewById(R.id.thumb_image_size); 66 | image = (ImageView) findViewById(R.id.image); 67 | FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab); 68 | fab.setOnClickListener(new View.OnClickListener() { 69 | @Override 70 | public void onClick(View view) { 71 | pickFromGallery(); 72 | } 73 | }); 74 | } 75 | public void pickFromGallery() { 76 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 77 | startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT).setType("image/*"), 78 | REQUEST_PICK_IMAGE); 79 | } else { 80 | Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); 81 | intent.addCategory(Intent.CATEGORY_OPENABLE); 82 | intent.setType("image/*"); 83 | startActivityForResult(intent, REQUEST_KITKAT_PICK_IMAGE); 84 | } 85 | } 86 | @Override 87 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 88 | super.onActivityResult(requestCode, resultCode, data); 89 | if (resultCode == Activity.RESULT_OK) { 90 | switch (requestCode) { 91 | 92 | case REQUEST_PICK_IMAGE: 93 | if (data != null) { 94 | Uri uri = data.getData(); 95 | String imgUrl = FileUtils.getPathByUri(MainActivity.this,uri); 96 | File imgFile = new File(imgUrl); 97 | fileSize.setText(imgFile.length() / 1024 + "k"); 98 | imageSize.setText(FileUtils.getImageSize(imgUrl)[0] + " * " + FileUtils.getImageSize(imgUrl)[1]); 99 | compressImage(imgUrl); 100 | } else { 101 | Log.e("androidpiccompress", "=====图片为空====="); 102 | } 103 | break; 104 | case REQUEST_KITKAT_PICK_IMAGE: 105 | if (data != null) { 106 | Uri uri = ensureUriPermission(this, data); 107 | String imgUrl = FileUtils.getPathByUri(MainActivity.this,uri); 108 | File imgFile = new File(imgUrl); 109 | fileSize.setText(imgFile.length() / 1024 + "k"); 110 | imageSize.setText(FileUtils.getImageSize(imgUrl)[0] + " * " + FileUtils.getImageSize(imgUrl)[1]); 111 | compressImage(imgUrl); 112 | } else { 113 | Log.e("androidpiccompress", "=====图片为空====="); 114 | } 115 | break; 116 | } 117 | } 118 | } 119 | @SuppressWarnings("ResourceType") 120 | @TargetApi(Build.VERSION_CODES.KITKAT) 121 | public static Uri ensureUriPermission(Context context, Intent intent) { 122 | Uri uri = intent.getData(); 123 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 124 | final int takeFlags = intent.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION; 125 | context.getContentResolver().takePersistableUriPermission(uri, takeFlags); 126 | } 127 | return uri; 128 | } 129 | 130 | public String getSDPath() { 131 | boolean sdCardExist = Environment.getExternalStorageState().equals( 132 | Environment.MEDIA_MOUNTED); 133 | if (sdCardExist) { 134 | return Environment.getExternalStorageDirectory().toString(); 135 | } else { 136 | return ""; 137 | } 138 | } 139 | 140 | public String getPicPath() { 141 | String sdCardPath = getSDPath(); 142 | String picUrl = ""; 143 | if (TextUtils.isEmpty(sdCardPath)) { 144 | // return ""; 145 | } else { 146 | picUrl = sdCardPath + File.separator + "PicCompress" 147 | + File.separator + "pic"; 148 | } 149 | File file = new File(picUrl); 150 | if (!file.exists()){ 151 | file.mkdirs(); 152 | } 153 | return picUrl; 154 | } 155 | 156 | // public void compressImage(Uri uri) { 157 | public void compressImage(String imageUrl) { 158 | Log.e("androidpiccompress", "====开始====imageUrl==" + imageUrl); 159 | File saveFile = new File(getPicPath(), "compress_" + System.currentTimeMillis() + ".jpg"); 160 | 161 | Log.e("androidpiccompress", "====开始==压缩==saveFile==" + saveFile.getAbsolutePath()); 162 | NativeUtil.compressBitmap(imageUrl, saveFile.getAbsolutePath()); 163 | Log.e("androidpiccompress", "====完成==压缩==saveFile==" + saveFile.getAbsolutePath()); 164 | File imgFile = new File(saveFile.getAbsolutePath()); 165 | thumbFileSize.setText(imgFile.length() / 1024 + "k"); 166 | thumbImageSize.setText(FileUtils.getImageSize(saveFile.getAbsolutePath())[0] + " * " 167 | + FileUtils.getImageSize(saveFile.getAbsolutePath())[1]); 168 | Glide.with(MainActivity.this).load(imgFile).into(image); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /app/src/main/java/com/liujc/androidpiccompress/PermissionsChecker.java: -------------------------------------------------------------------------------- 1 | package com.liujc.androidpiccompress; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageManager; 5 | import android.os.Build; 6 | import android.support.v4.content.ContextCompat; 7 | 8 | /** 9 | * 类名称:PermissionsChecker 10 | * 创建者:Create by liujc 11 | * 创建时间:Create on 2016/12/2 12:44 12 | * 描述:针对android 6.0 动态获取权限,检查权限的工具类 13 | */ 14 | public class PermissionsChecker { 15 | private final Context mContext; 16 | 17 | public PermissionsChecker(Context context) { 18 | mContext = context.getApplicationContext(); 19 | } 20 | 21 | // 判断权限集合 22 | public boolean lacksPermissions(String... permissions) { 23 | for (String permission : permissions) { 24 | if (lacksPermission(permission)) { 25 | return true; 26 | } 27 | } 28 | return false; 29 | } 30 | 31 | // 判断是否缺少权限 32 | private boolean lacksPermission(String permission) { 33 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 34 | return ContextCompat.checkSelfPermission(mContext, permission) == 35 | PackageManager.PERMISSION_DENIED; 36 | } 37 | return false; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/addphoto_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujinchao/AndroidPicCompress/28f26ac89f1692eb157aa52bbb98d8ca6da1bc14/app/src/main/res/drawable/addphoto_button.png -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 16 | 17 | 18 | 19 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/layout/content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 17 | 18 | 22 | 23 | 28 | 29 | 34 | 35 | 36 | 37 | 41 | 42 | 46 | 47 | 52 | 53 | 58 | 59 | 60 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujinchao/AndroidPicCompress/28f26ac89f1692eb157aa52bbb98d8ca6da1bc14/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujinchao/AndroidPicCompress/28f26ac89f1692eb157aa52bbb98d8ca6da1bc14/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujinchao/AndroidPicCompress/28f26ac89f1692eb157aa52bbb98d8ca6da1bc14/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujinchao/AndroidPicCompress/28f26ac89f1692eb157aa52bbb98d8ca6da1bc14/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujinchao/AndroidPicCompress/28f26ac89f1692eb157aa52bbb98d8ca6da1bc14/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidPicCompress 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/liujc/androidpiccompress/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.liujc.androidpiccompress; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.2.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujinchao/AndroidPicCompress/28f26ac89f1692eb157aa52bbb98d8ca6da1bc14/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /lib_jpeg_compress/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /lib_jpeg_compress/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 15 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | 15 | } 16 | buildTypes { 17 | release { 18 | minifyEnabled false 19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 20 | } 21 | } 22 | //so文件加载,需要添加下面代码 23 | sourceSets { 24 | main { 25 | jniLibs.srcDirs = ['libs'] 26 | } 27 | } 28 | } 29 | 30 | dependencies { 31 | compile fileTree(dir: 'libs', include: ['*.jar']) 32 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 33 | exclude group: 'com.android.support', module: 'support-annotations' 34 | }) 35 | compile 'com.android.support:appcompat-v7:25.0.1' 36 | testCompile 'junit:junit:4.12' 37 | } 38 | -------------------------------------------------------------------------------- /lib_jpeg_compress/libs/armeabi-v7a/libbitherjni.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujinchao/AndroidPicCompress/28f26ac89f1692eb157aa52bbb98d8ca6da1bc14/lib_jpeg_compress/libs/armeabi-v7a/libbitherjni.so -------------------------------------------------------------------------------- /lib_jpeg_compress/libs/armeabi-v7a/libjpegbither.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujinchao/AndroidPicCompress/28f26ac89f1692eb157aa52bbb98d8ca6da1bc14/lib_jpeg_compress/libs/armeabi-v7a/libjpegbither.so -------------------------------------------------------------------------------- /lib_jpeg_compress/libs/armeabi/libbitherjni.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujinchao/AndroidPicCompress/28f26ac89f1692eb157aa52bbb98d8ca6da1bc14/lib_jpeg_compress/libs/armeabi/libbitherjni.so -------------------------------------------------------------------------------- /lib_jpeg_compress/libs/armeabi/libjpegbither.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujinchao/AndroidPicCompress/28f26ac89f1692eb157aa52bbb98d8ca6da1bc14/lib_jpeg_compress/libs/armeabi/libjpegbither.so -------------------------------------------------------------------------------- /lib_jpeg_compress/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in E:\Android-sdk\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /lib_jpeg_compress/src/androidTest/java/net/bitch/util/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package net.bitch.util; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("net.bitch.util.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib_jpeg_compress/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib_jpeg_compress/src/main/java/jpegcompress/FileUtils.java: -------------------------------------------------------------------------------- 1 | package jpegcompress; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.ContentUris; 5 | import android.content.Context; 6 | import android.database.Cursor; 7 | import android.graphics.BitmapFactory; 8 | import android.net.Uri; 9 | import android.os.Build; 10 | import android.os.Environment; 11 | import android.provider.DocumentsContract; 12 | import android.provider.MediaStore; 13 | 14 | /** 15 | * 类名称:FileUtils 16 | * 创建者:Create by liujc 17 | * 创建时间:Create on 2017/3/6 16:32 18 | * 描述:文件获取相关工具类 19 | */ 20 | public class FileUtils { 21 | public static int[] getImageSize(String imagePath) { 22 | int[] res = new int[2]; 23 | 24 | BitmapFactory.Options options = new BitmapFactory.Options(); 25 | options.inJustDecodeBounds = true; 26 | options.inSampleSize = 1; 27 | BitmapFactory.decodeFile(imagePath, options); 28 | 29 | res[0] = options.outWidth; 30 | res[1] = options.outHeight; 31 | return res; 32 | } 33 | public static String getPathByUri(Context context, Uri data){ 34 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { 35 | return getPathByUri4BeforeKitkat(context, data); 36 | }else { 37 | return getPathByUri4AfterKitkat(context, data); 38 | } 39 | } 40 | //4.4以前通过Uri获取路径:data是Uri,filename是一个String的字符串,用来保存路径 41 | public static String getPathByUri4BeforeKitkat(Context context, Uri data) { 42 | String filename=null; 43 | if (data.getScheme().toString().compareTo("content") == 0) { 44 | Cursor cursor = context.getContentResolver().query(data, new String[] { "_data" }, null, null, null); 45 | if (cursor.moveToFirst()) { 46 | filename = cursor.getString(0); 47 | } 48 | } else if (data.getScheme().toString().compareTo("file") == 0) {// file:///开头的uri 49 | filename = data.toString().replace("file://", "");// 替换file:// 50 | if (!filename.startsWith("/mnt")) {// 加上"/mnt"头 51 | filename += "/mnt"; 52 | } 53 | } 54 | return filename; 55 | } 56 | //4.4以后根据Uri获取路径: 57 | @SuppressLint("NewApi") 58 | public static String getPathByUri4AfterKitkat(final Context context, final Uri uri) { 59 | final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 60 | // DocumentProvider 61 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { 62 | if (isExternalStorageDocument(uri)) {// ExternalStorageProvider 63 | final String docId = DocumentsContract.getDocumentId(uri); 64 | final String[] split = docId.split(":"); 65 | final String type = split[0]; 66 | if ("primary".equalsIgnoreCase(type)) { 67 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 68 | } 69 | } else if (isDownloadsDocument(uri)) {// DownloadsProvider 70 | final String id = DocumentsContract.getDocumentId(uri); 71 | final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), 72 | Long.valueOf(id)); 73 | return getDataColumn(context, contentUri, null, null); 74 | } else if (isMediaDocument(uri)) {// MediaProvider 75 | final String docId = DocumentsContract.getDocumentId(uri); 76 | final String[] split = docId.split(":"); 77 | final String type = split[0]; 78 | Uri contentUri = null; 79 | if ("image".equals(type)) { 80 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 81 | } else if ("video".equals(type)) { 82 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 83 | } else if ("audio".equals(type)) { 84 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 85 | } 86 | final String selection = "_id=?"; 87 | final String[] selectionArgs = new String[] { split[1] }; 88 | return getDataColumn(context, contentUri, selection, selectionArgs); 89 | } 90 | } else if ("content".equalsIgnoreCase(uri.getScheme())) {// MediaStore 91 | // (and 92 | // general) 93 | return getDataColumn(context, uri, null, null); 94 | } else if ("file".equalsIgnoreCase(uri.getScheme())) {// File 95 | return uri.getPath(); 96 | } 97 | return null; 98 | } 99 | 100 | /** 101 | * Get the value of the data column for this Uri. This is useful for 102 | * MediaStore Uris, and other file-based ContentProviders. 103 | * 104 | * @param context 105 | * The context. 106 | * @param uri 107 | * The Uri to query. 108 | * @param selection 109 | * (Optional) Filter used in the query. 110 | * @param selectionArgs 111 | * (Optional) Selection arguments used in the query. 112 | * @return The value of the _data column, which is typically a file path. 113 | */ 114 | public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { 115 | Cursor cursor = null; 116 | final String column = "_data"; 117 | final String[] projection = { column }; 118 | try { 119 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); 120 | if (cursor != null && cursor.moveToFirst()) { 121 | final int column_index = cursor.getColumnIndexOrThrow(column); 122 | return cursor.getString(column_index); 123 | } 124 | } finally { 125 | if (cursor != null) 126 | cursor.close(); 127 | } 128 | return null; 129 | } 130 | 131 | /** 132 | * @param uri 133 | * The Uri to check. 134 | * @return Whether the Uri authority is ExternalStorageProvider. 135 | */ 136 | public static boolean isExternalStorageDocument(Uri uri) { 137 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 138 | } 139 | 140 | /** 141 | * @param uri 142 | * The Uri to check. 143 | * @return Whether the Uri authority is DownloadsProvider. 144 | */ 145 | public static boolean isDownloadsDocument(Uri uri) { 146 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 147 | } 148 | 149 | /** 150 | * @param uri 151 | * The Uri to check. 152 | * @return Whether the Uri authority is MediaProvider. 153 | */ 154 | public static boolean isMediaDocument(Uri uri) { 155 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /lib_jpeg_compress/src/main/java/net/bither/util/NativeUtil.java: -------------------------------------------------------------------------------- 1 | package net.bither.util; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Bitmap.Config; 5 | import android.graphics.BitmapFactory; 6 | import android.graphics.Canvas; 7 | import android.graphics.Matrix; 8 | import android.graphics.Rect; 9 | import android.media.ExifInterface; 10 | 11 | import java.io.ByteArrayOutputStream; 12 | import java.io.File; 13 | import java.io.FileInputStream; 14 | import java.io.FileNotFoundException; 15 | import java.io.IOException; 16 | 17 | /** 18 | * JNI图片压缩工具类 19 | * 20 | * @Description TODO 21 | * @Package net.bither.util 22 | * @Class NativeUtil 23 | */ 24 | public class NativeUtil { 25 | 26 | private static int DEFAULT_QUALITY = 95; 27 | 28 | /** 29 | * @Description: JNI基本压缩 30 | * @param bit 31 | * bitmap对象 32 | * @param fileName 33 | * 指定保存目录名 34 | * @param optimize 35 | * 是否采用哈弗曼表数据计算 品质相差5-10倍 36 | */ 37 | public static void compressBitmap(Bitmap bit, String fileName, boolean optimize) { 38 | saveBitmap(bit, DEFAULT_QUALITY, fileName, optimize); 39 | } 40 | 41 | /** 42 | * @Description: 通过JNI图片压缩把Bitmap保存到指定目录 43 | * @param image 44 | * bitmap对象 45 | * @param filePath 46 | * 要保存的指定目录 47 | */ 48 | public static void compressBitmap(Bitmap image, String filePath) { 49 | // 最大图片大小 150KB 50 | int maxSize = 150; 51 | // 获取尺寸压缩倍数 52 | int ratio = NativeUtil.getRatioSize(image.getWidth(),image.getHeight()); 53 | // 压缩Bitmap到对应尺寸 54 | Bitmap result = Bitmap.createBitmap(image.getWidth() / ratio,image.getHeight() / ratio, Config.ARGB_8888); 55 | Canvas canvas = new Canvas(result); 56 | Rect rect = new Rect(0, 0, image.getWidth() / ratio, image.getHeight() / ratio); 57 | canvas.drawBitmap(image,null,rect,null); 58 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 59 | // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 60 | int options = 100; 61 | result.compress(Bitmap.CompressFormat.JPEG, options, baos); 62 | // 循环判断如果压缩后图片是否大于100kb,大于继续压缩 63 | while (baos.toByteArray().length / 1024 > maxSize) { 64 | // 重置baos即清空baos 65 | baos.reset(); 66 | // 每次都减少10 67 | options -= 10; 68 | // 这里压缩options%,把压缩后的数据存放到baos中 69 | result.compress(Bitmap.CompressFormat.JPEG, options, baos); 70 | } 71 | // JNI保存图片到SD卡 这个关键 72 | NativeUtil.saveBitmap(result, options, filePath, true); 73 | // 释放Bitmap 74 | if (!result.isRecycled()) { 75 | result.recycle(); 76 | } 77 | } 78 | 79 | /** 80 | * @Description: 通过JNI图片压缩把Bitmap保存到指定目录 81 | * @param curFilePath 82 | * 当前图片文件地址 83 | * @param targetFilePath 84 | * 要保存的图片文件地址 85 | */ 86 | public static void compressBitmap(String curFilePath, String targetFilePath) { 87 | // 最大图片大小 500KB 88 | int maxSize = 500; 89 | //根据地址获取bitmap 90 | Bitmap result = getBitmapFromFile(curFilePath); 91 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 92 | // 质量压缩方法,这里100表示不压缩,把压缩后的数据存放到baos中 93 | int quality = 100; 94 | result.compress(Bitmap.CompressFormat.JPEG, quality, baos); 95 | // 循环判断如果压缩后图片是否大于500kb,大于继续压缩 96 | while (baos.toByteArray().length / 1024 > maxSize) { 97 | // 重置baos即清空baos 98 | baos.reset(); 99 | // 每次都减少10 100 | quality -= 10; 101 | // 这里压缩quality,把压缩后的数据存放到baos中 102 | result.compress(Bitmap.CompressFormat.JPEG, quality, baos); 103 | } 104 | // JNI保存图片到SD卡 这个关键 105 | NativeUtil.saveBitmap(result, quality, targetFilePath, true); 106 | // 释放Bitmap 107 | if (!result.isRecycled()) { 108 | result.recycle(); 109 | } 110 | 111 | } 112 | 113 | /** 114 | * 计算缩放比 115 | * @param bitWidth 当前图片宽度 116 | * @param bitHeight 当前图片高度 117 | * @return int 缩放比 118 | */ 119 | public static int getRatioSize(int bitWidth, int bitHeight) { 120 | // 图片最大分辨率 121 | int imageHeight = 1280; 122 | int imageWidth = 960; 123 | // 缩放比 124 | int ratio = 1; 125 | // 缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可 126 | if (bitWidth > bitHeight && bitWidth > imageWidth) { 127 | // 如果图片宽度比高度大,以宽度为基准 128 | ratio = bitWidth / imageWidth; 129 | } else if (bitWidth < bitHeight && bitHeight > imageHeight) { 130 | // 如果图片高度比宽度大,以高度为基准 131 | ratio = bitHeight / imageHeight; 132 | } 133 | // 最小比率为1 134 | if (ratio <= 0) 135 | ratio = 1; 136 | return ratio; 137 | } 138 | 139 | /** 140 | * 通过文件路径读获取Bitmap防止OOM以及解决图片旋转问题 141 | * @param filePath 142 | * @return 143 | */ 144 | public static Bitmap getBitmapFromFile(String filePath){ 145 | BitmapFactory.Options newOpts = new BitmapFactory.Options(); 146 | newOpts.inJustDecodeBounds = true;//只读边,不读内容 147 | BitmapFactory.decodeFile(filePath, newOpts); 148 | int w = newOpts.outWidth; 149 | int h = newOpts.outHeight; 150 | // 获取尺寸压缩倍数 151 | newOpts.inSampleSize = NativeUtil.getRatioSize(w,h); 152 | newOpts.inJustDecodeBounds = false;//读取所有内容 153 | newOpts.inDither = false; 154 | newOpts.inPurgeable=true; 155 | newOpts.inInputShareable=true; 156 | newOpts.inTempStorage = new byte[32 * 1024]; 157 | Bitmap bitmap = null; 158 | File file = new File(filePath); 159 | FileInputStream fs = null; 160 | try { 161 | fs = new FileInputStream(file); 162 | } catch (FileNotFoundException e) { 163 | e.printStackTrace(); 164 | } 165 | try { 166 | if(fs!=null){ 167 | bitmap = BitmapFactory.decodeFileDescriptor(fs.getFD(),null,newOpts); 168 | //旋转图片 169 | int photoDegree = readPictureDegree(filePath); 170 | if(photoDegree != 0){ 171 | Matrix matrix = new Matrix(); 172 | matrix.postRotate(photoDegree); 173 | // 创建新的图片 174 | bitmap = Bitmap.createBitmap(bitmap, 0, 0, 175 | bitmap.getWidth(), bitmap.getHeight(), matrix, true); 176 | } 177 | } 178 | } catch (IOException e) { 179 | e.printStackTrace(); 180 | } finally{ 181 | if(fs!=null) { 182 | try { 183 | fs.close(); 184 | } catch (IOException e) { 185 | e.printStackTrace(); 186 | } 187 | } 188 | } 189 | return bitmap; 190 | } 191 | 192 | /** 193 | * 194 | * 读取图片属性:旋转的角度 195 | * @param path 图片绝对路径 196 | * @return degree旋转的角度 197 | */ 198 | 199 | public static int readPictureDegree(String path) { 200 | int degree = 0; 201 | try { 202 | ExifInterface exifInterface = new ExifInterface(path); 203 | int orientation = exifInterface.getAttributeInt( 204 | ExifInterface.TAG_ORIENTATION, 205 | ExifInterface.ORIENTATION_NORMAL); 206 | switch (orientation) { 207 | case ExifInterface.ORIENTATION_ROTATE_90: 208 | degree = 90; 209 | break; 210 | case ExifInterface.ORIENTATION_ROTATE_180: 211 | degree = 180; 212 | break; 213 | case ExifInterface.ORIENTATION_ROTATE_270: 214 | degree = 270; 215 | break; 216 | } 217 | } catch (IOException e) { 218 | e.printStackTrace(); 219 | } 220 | return degree; 221 | } 222 | 223 | /** 224 | * 调用native方法 225 | * @Description:函数描述 226 | * @param bit 227 | * @param quality 228 | * @param fileName 229 | * @param optimize 230 | */ 231 | private static void saveBitmap(Bitmap bit, int quality, String fileName, boolean optimize) { 232 | compressBitmap(bit, bit.getWidth(), bit.getHeight(), quality, fileName.getBytes(), optimize); 233 | } 234 | 235 | /** 236 | * 调用底层 bitherlibjni.c中的方法 237 | * @Description:函数描述 238 | * @param bit 239 | * @param w 240 | * @param h 241 | * @param quality 242 | * @param fileNameBytes 243 | * @param optimize 244 | * @return 245 | */ 246 | private static native String compressBitmap(Bitmap bit, int w, int h, int quality, byte[] fileNameBytes, 247 | boolean optimize); 248 | /** 249 | * 加载lib下两个so文件 250 | */ 251 | static { 252 | System.loadLibrary("jpegbither"); 253 | System.loadLibrary("bitherjni"); 254 | } 255 | 256 | } -------------------------------------------------------------------------------- /lib_jpeg_compress/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | LibJpegCompress 3 | 4 | -------------------------------------------------------------------------------- /lib_jpeg_compress/src/test/java/net/bitch/util/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package net.bitch.util; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() throws Exception { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /screenshot/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liujinchao/AndroidPicCompress/28f26ac89f1692eb157aa52bbb98d8ca6da1bc14/screenshot/1.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':lib_jpeg_compress' 2 | --------------------------------------------------------------------------------