在 Android10.0 以上通过绝对路径加载非沙盒目录下的本地图片不显示,需要通过文件 Uri 来访问。App 沙盒目录有两个位置 /sdcard/Android/data/包名/ 和 /data/data/包名/,这两个目录 App 可以直接通过绝对路径来访问,后面 App 卸载,这两个目录也会一起被删除。
//通过图片地址查询Media Uri
public static Uri getMediaUriFromPath(Context context, String path) {
Uri mediaUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
Cursor cursor = context.getContentResolver().query(
mediaUri,
null,
MediaStore.Images.Media.DISPLAY_NAME + "= ?",
new String[] {path.substring(path.lastIndexOf("/") + 1)},
null);
Uri uri = null;
if(cursor.moveToFirst()) {
// 通过Media Id组装Uri,也可以通过Uri.withAppendedPath(mediaUri, id)这种方式组装
uri = ContentUris.withAppendedId(mediaUri,
cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID)));
}
cursor.close();
return uri;
}
通过 Uri 来获取 Bitmap,通过 Bitmap 显示在 ImageView 上
@RequiresApi(api = Build.VERSION_CODES.N)
public static Bitmap getBitmapFromUri(Context context, Uri fileUri){
Bitmap bitmap = null;
ParcelFileDescriptor pfd = null;
try {
// 先获取ParcelFileDescriptor,第二个参数有 r: 读, w: 写,rw:读写
pfd = context.getContentResolver().openFileDescriptor(fileUri, "r");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
try {
if(pfd != null){
// 然后通过BitmapFactory把ParcelFileDescriptor转换成Bitmap
bitmap = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor(),null, null);
//旋转图片
int photoDegree = readPictureDegree(pfd.getFileDescriptor());
if(photoDegree != 0){
Matrix matrix = new Matrix();
matrix.postRotate(photoDegree);
// 创建新的图片
bitmap = Bitmap.createBitmap(bitmap, 0, 0,
bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}
}
} finally{
if(pfd != null) {
try {
pfd.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return bitmap;
}
// 通过FileDescriptor获取图片旋转角度
@RequiresApi(api = Build.VERSION_CODES.N)
public static int readPictureDegree(FileDescriptor fileDescriptor) {
int degree = 0;
try {
ExifInterface exifInterface = new ExifInterface(fileDescriptor);
int orientation = exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_90:
degree = 90;
break;
case ExifInterface.ORIENTATION_ROTATE_180:
degree = 180;
break;
case ExifInterface.ORIENTATION_ROTATE_270:
degree = 270;
break;
}
} catch (IOException e) {
e.printStackTrace();
}
return degree;
}
压缩图片, 通过上面 getBitmapFromUri 方法获取 Bitmap, 再通过 FileInputStream 获取文件大小,看看是否需要压缩,然后进行尺寸压缩和质量压缩,最后把 bitmap 保存到 App 沙盒私有目录下,后面就可以直接通过地址获取文件。
private static int defaultImageMaxHeight = 1920; // 图片最大尺寸 1920*1080
private static int defaultImageMaxWidth = 1080;
private static int defaultImageMaxSize = 1024 * 1024; // 图片最大1M 单位Byte
// 10.0以上调用, 返回null 不压缩或压缩失败
@RequiresApi(api = Build.VERSION_CODES.Q)
private static String compress(Uri fileUri) {
Bitmap image = BitmapUtils.getBitmapFromUri(SSTApplication.getContext(), fileUri);
if (image == null)
return null;
long imageFileSize = 0L;
try {
ParcelFileDescriptor pfd = SSTApplication.getContext().getContentResolver().openFileDescriptor(fileUri, "r");
if (pfd != null) {
// 通过 FileInputStream.available()获取文件大小
FileInputStream fileInputStream = new FileInputStream(pfd.getFileDescriptor());
imageFileSize = fileInputStream.available();
fileInputStream.close();
}
// 1M 以下不压缩
if (imageFileSize <= defaultImageMaxSize) {
return null;
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
// 尺寸压缩
int ratio = getRatioSize(image.getWidth(),image.getHeight(), defaultImageMaxWidth, defaultImageMaxHeight);
// 压缩Bitmap到对应尺寸
Bitmap result = Bitmap.createBitmap(image.getWidth() / ratio,image.getHeight() / ratio, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
Rect rect = new Rect(0, 0, image.getWidth() / ratio, image.getHeight() / ratio);
canvas.drawBitmap(image,null,rect,null);
// 创建沙盒私有目录下压缩要保存的文件
File tempFile = new File(SSTApplication.getContext().getExternalCacheDir(), UUID.randomUUID().toString()+".jpg");
try {
//质量压缩
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//这里100表示不压缩,把压缩后的数据存放到baos中
int quality = 100;
result.compress(Bitmap.CompressFormat.JPEG, quality, baos);
// 循环判断如果压缩后图片是否大于100kb, 大于继续压缩
while (baos.toByteArray().length > defaultImageMaxSize) {
// 重置baos即清空baos
baos.reset();
// 每次都减少10
quality -= 10;
// 这里压缩quality,把压缩后的数据存放到baos中
result.compress(Bitmap.CompressFormat.JPEG, quality, baos);
if (quality <= 10) {
break;
}
}
// 保存本地
FileOutputStream fos = new FileOutputStream(tempFile);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
// 释放Bitmap
if (!result.isRecycled()) {
result.recycle();
}
return tempFile.getAbsolutePath();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// 10.0 以下调用
private static String compress(String imagePath) {
Bitmap image = BitmapUtils.getBitmapFromFile(imagePath);
if (image == null)
return imagePath;
long imageFileSize = new File(imagePath).length();
if (imageFileSize <= defaultImageMaxSize) {
return imagePath;
}
// 尺寸压缩
int ratio = getRatioSize(image.getWidth(),image.getHeight(), defaultImageMaxWidth, defaultImageMaxHeight);
// 压缩Bitmap到对应尺寸
Bitmap result = Bitmap.createBitmap(image.getWidth() / ratio,image.getHeight() / ratio, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(result);
Rect rect = new Rect(0, 0, image.getWidth() / ratio, image.getHeight() / ratio);
canvas.drawBitmap(image,null,rect,null);
String tempFile = SSTApplication.getContext().getFilesDir() + "/" + System.currentTimeMillis() + ".jpg";
try {
//质量压缩
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//这里100表示不压缩,把压缩后的数据存放到baos中
int quality = 100;
result.compress(Bitmap.CompressFormat.JPEG, quality, baos);
// 循环判断如果压缩后图片是否大于100kb, 大于继续压缩
while (baos.toByteArray().length > defaultImageMaxSize) {
// 重置baos即清空baos
baos.reset();
// 每次都减少10
quality -= 10;
// 这里压缩quality,把压缩后的数据存放到baos中
result.compress(Bitmap.CompressFormat.JPEG, quality, baos);
if (quality <= 10) {
break;
}
}
// 保存本地
FileOutputStream fos = new FileOutputStream(tempFile);
fos.write(baos.toByteArray());
fos.flush();
fos.close();
// 释放Bitmap
if (!result.isRecycled()) {
result.recycle();
}
} catch (Exception e) {
e.printStackTrace();
return imagePath;
}
return tempFile;
}
// 获取尺寸压缩比
private static int getRatioSize(int bitWidth, int bitHeight, int maxWidth, int maxHeight) {
// 缩放比
int ratio = 1;
// 缩放比,由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
if (bitWidth > bitHeight && bitWidth > maxWidth) {
// 如果图片宽度比高度大,以宽度为基准
ratio = bitWidth / maxWidth;
} else if (bitWidth < bitHeight && bitHeight > maxHeight) {
// 如果图片高度比宽度大,以高度为基准
ratio = bitHeight / maxHeight;
}
return ratio;
}
拍照保存图片, 先判断系统是否有相机功能,如果不判断缺少相机会直接报错,然后判断是否是 Android10以上,如果是则创建 Uri, 否就创建 File , 然后保存 全局变量 mCameraFile 和 mCameraUri,最后在 onActivityForResult 回调函数中拿这两个全局变量处理相应逻辑即可。
public void takePicture(Activity activity, int requestCode) {
boolean isAndroidQ = Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q;
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 检查是否有相机
if (takePictureIntent.resolveActivity(activity.getPackageManager()) != null) {
Uri photoUri = null;
File photoFile = null;
if (isAndroidQ) {
// 适配android 10
photoUri = createImageUri(activity.getApplicationContext());
} else {
photoFile = createFile(activity.getApplicationContext());
if (photoFile != null) {
mCameraFile = photoFile;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
takePictureIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
photoUri = FileProvider.getUriForFile(activity, activity.getPackageName() + ".fileProvider", photoFile);
} else {
photoUri = Uri.fromFile(photoFile);
}
}
}
if (photoUri != null) {
mCameraUri = photoUri;
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
takePictureIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
activity.startActivityForResult(takePictureIntent, requestCode);
}
}
}
/**
* 创建图片地址uri,用于保存拍照后的照片 Android 10以后使用这种方法
*/
private Uri createImageUri(Context context) {
String status = Environment.getExternalStorageState();
// 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储
if (status.equals(Environment.MEDIA_MOUNTED)) {
return context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new ContentValues());
} else {
return context.getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, new ContentValues());
}
}
/** 创建临时文件 */
public static File createFile(Context context) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.CHINA);
String filename = "IMG_" + dateFormat.format(new Date(System.currentTimeMillis())) + ".jpg";
File storageDir;
// 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储
if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
storageDir = new File(Environment.getExternalStorageDirectory(), "/DCIM/camera/");
} else {
storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
}
if (storageDir!= null && !storageDir.exists()) {
storageDir.mkdir();
}
File tempFile = new File(storageDir, filename);
if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))) {
return null;
}
return tempFile;
}
Android10.0 上传图片,同样通过 Uri 将图片复制到 App 的沙盒目录下再上传.
public static File writeExternalCacheFile(Context context, Uri fileUri) throws IOException{
if (context == null) {
return null;
}
FileOutputStream outs = null;
FileInputStream ins = null;
File externalCacheFile = null;
try {
// 先获取ParcelFileDescriptor
ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(fileUri, "r");
if (pfd == null) {
return null;
}
// 然后通过这种方式创建文件
externalCacheFile = new File(context.getExternalCacheDir(), UUID.randomUUID().toString()+".jpg");
// 创建文件输出流,用于写入文件
outs = new FileOutputStream(externalCacheFile);
// 把ParcelFileDescriptor转成输入流,用于文件读取
ins = new FileInputStream(pfd.getFileDescriptor());
byte[] bytes = new byte[1024];
int index;
while ((index = ins.read(bytes)) != -1) {
// 循环获取文件流
outs.write(bytes,0 , index);
}
} finally {
if (outs != null) {
outs.close();
}
if (ins != null) {
ins.close();
}
}
return externalCacheFile;
}