一、Android Q 分区存储
首先明确一个概念,外部储存和内部储存。上几张图:
1、通过MediaStore和共享目录下的媒体文件Uri来访问文件
Android10版本对于存储做了严格的管理,虽然现在才写晚了点,不过好过没有吧,先写个草稿,慢慢整理
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* 该工具类主要用于APP对 公共存储目录下 照片/视频/音频 这类媒体文件的操作(保存和取出,通过 MediaStore API 进行操作)
* 即允许用户看到
* 适配 Android Q 版本,重点:AndroidQ中共享目录不支持文件路径访问,只能通过uri方式访问
* <p>
* 外部存储目录,创建和访问应用自己的文件夹不需要存储权限,创建和访问其他应用的 media 文件需要存储权限
* 公有目录:Download、Documents、Pictures 、DCIM、Movies、Music、Ringtones等
* 地址:/storage/emulated/0/Downloads(Pictures),无法直接使用路径访问公共目录文件
* 公有目录下的文件不会跟随APP卸载而删除。
* <p>
* APP私有目录,读写均不需要存储权限
* 地址:/storage/emulated/0/Android/data/包名/files
* 私有目录存放app的私有文件,会随着App的卸载而删除。
* <p>
* 核心思路:通过将最终获得的数据流inputsteam或者FileDescriptor等方式,写入到ContentValues和MediaStore选好保存类型和形式中去,以实现保存
*
* @author xuetaotao
* 2020.12.01
*/
public class MediaStoreUtils {
public static final String IMG_PUBLIC_DCIM_DIR = Environment.DIRECTORY_DCIM + File.separator + "com.jlpay.partner";///DCIM/com.jlpay.partner
public static final String IMG_PUBLIC_PIC_DIR = Environment.DIRECTORY_PICTURES + File.separator + "com.jlpay.partner";///Pictures/com.jlpay.partner
public static final String DOWNLOADS_PUBLIC_DIR = Environment.DIRECTORY_DOWNLOADS + File.separator + "com.jlpay.partner";//Download/com.jlpay.partner
/**
* 保存图片到相册,即允许用户看到的图片
* 存储在 DCIM/ 或 Pictures/ 目录中,系统将这些文件添加到 MediaStore.Images 表格中
* <p>
* 注:存储在外部APP私有目录下的图片能否用context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + dir + "/" + filename)))
* 的方式显示在相册中? 答:不可以,Android Q版本使用,将图片存放到沙盒文件内,图库无法刷新,无法显示
* 可以通过下面的方法以复制的形式copy到外部公共目录下,进而实现相册中的显示,但是不太好
* <p>
* 注:APP自己保存在外部共享目录下的图片,自己具有删除权限,但是不建议删除,因为部分手机(如华为)会弹出删除通知
*
* @param baseActivity 上下文环境
* @param inputStream 要保存到本地外部公共存储目录的输入流
*/
public static void imgInsert(BaseActivity baseActivity, InputStream inputStream) {
//照片、视频、音频这类媒体文件。使用 MediaStore 访问,访问其他应用的媒体文件时需要 READ_EXTERNAL_STORAGE 权限。
String finalFileName = "IMG" + System.currentTimeMillis() + ".jpg";
//在共享目录下创建和访问应用自己的文件夹,可以不需要存储权限;创建和访问其他应用的 media 文件需要存储权限
//这里为避免意外情况,采取保守策略
PermissionUtils.getStoragePermission(baseActivity, () -> {
ContentResolver contentResolver = baseActivity.getApplicationContext().getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, finalFileName);//设置带扩展名的文件名,如:IMG1024.JPG,必须
contentValues.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis());//设置文件第一次被添加的时间,非必须
contentValues.put(MediaStore.Images.ImageColumns.MIME_TYPE, getImgMimeType(finalFileName));//设置文件类型,如:IMG1024,必须
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//AndroidQ 中不再使用DATA字段,而用RELATIVE_PATH代替
//RELATIVE_PATH是相对路径不是绝对路径,DCIM是系统文件夹
contentValues.put(MediaStore.Images.ImageColumns.RELATIVE_PATH, IMG_PUBLIC_DCIM_DIR);//文件存储的相对路径,必须
} else {
//AndroidQ以下版本,直接使用外部公共存储目录下的图片绝对路径,不能去掉,因为上面的要求API>=29,在10.0以下设备中下一步insert产生的Uri会为空
String fileDir = Constants.external_storage_dir + File.separator + IMG_PUBLIC_DCIM_DIR;
if (createDirs(fileDir)) {
String filepath = fileDir + File.separator + finalFileName;
contentValues.put(MediaStore.Images.ImageColumns.DATA, filepath);//文件存储的绝对路径,必须
}
}
//执行insert操作,向系统文件夹中添加文件,若生成了uri,则表示该文件添加成功,使用流将内容写入该uri中即可
//这一步会在DCIM目录下创建partner文件夹(没有该文件夹时)
Uri uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
if (uri != null) {
try {
OutputStream outputStream = contentResolver.openOutputStream(uri);
if (outputStream != null) {
streamOperator(inputStream, outputStream);
ToastUtils.showToastApplication(baseActivity, "图片已成功保存至手机相册");
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
});
}
/**
* 將APP私有目录下的图片插入到共享目录,进而显示到相册(插入到共享目录后相册会自动刷新,不需要通知)
*
* @param baseActivity 上下文环境
* @param filePath 私有目录下的图片路径
*/
public static void imgInsert(BaseActivity baseActivity, @NonNull String filePath) {
PermissionUtils.getStoragePermission(baseActivity, () -> {
File file = new File(filePath);
try {
FileInputStream fileInputStream = new FileInputStream(file);
imgInsert(baseActivity, fileInputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
});
}
/**
* 将公共共享目录下的图片转为输入流
*
* @param uri 将公共共享目录下的图片Uri
* @return 输入流
*/
public static InputStream getImgStream(@NonNull Uri uri) {
return getImgStream(AppUtils.getContext(), uri);
}
public static InputStream getImgStream(Context context, @NonNull Uri uri) {
ContentResolver contentResolver = context.getContentResolver();
try {
return contentResolver.openInputStream(uri);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
/**
* 将公共共享目录下的图片转为输入流
* 通过文件描述符方式
*
* @param uri 将公共共享目录下的图片Uri
* @return 输入流
*/
public static InputStream getImgStream2(Uri uri) {
if (uri == null) {
return null;
}
ContentResolver contentResolver = AppUtils.getContext().getContentResolver();
try {
ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r");
if (parcelFileDescriptor != null) {
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
return new FileInputStream(fileDescriptor);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* 将公共共享目录下的图片复制到 APP外部私有目录下
* 注:这样复制后,在压缩上传完后,建议把复制后的原图删除,但是切勿删除真正的原图!!
* <p>
* PS:不建议使用,因为 Android 11 又恢复了使用直接文件路径访问访问媒体文件,不再需要这一步,仅是AndroidQ的妥协
*
* @return APP私有目录下的图片存储路径(Android Q后只有APP私有目录的文件路径可以访问到文件, 共享目录只能通过Uri访问)
*/
@Deprecated
public static String copyImgUriToExternalFilesPath(Uri uri) {
if (uri == null) {
return null;
}
createDirs(Constants.IMAGE_SAVE_DIR);
String fileName = "IMG" + System.currentTimeMillis() + ".jpg";
File file = new File(Constants.IMAGE_SAVE_DIR, fileName);
InputStream inputStream = getImgStream(uri);
try {
FileOutputStream fileOutputStream = new FileOutputStream(file);
streamOperator(inputStream, fileOutputStream);
return file.getAbsolutePath();
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
/**
* 将共享目录下的图片/音频/视频 复制到APP外部私有目录下
*
* @param uri 共享目录下的文件Uri
* @param fileName 带文件扩展名的文件名
* @return true:成功
*/
public static boolean copyUriToExternalFilesPath(@NonNull Context context, @NonNull Uri uri, @NonNull String fileName) {
if (TextUtils.isEmpty(fileName)) {
return false;
}
createDirs(Constants.FILE_SAVE_DIR);
File file = new File(Constants.FILE_SAVE_DIR, fileName);
ContentResolver contentResolver = context.getContentResolver();
try {
InputStream inputStream = contentResolver.openInputStream(uri);
FileOutputStream fileOutputStream = new FileOutputStream(file);
streamOperator(inputStream, fileOutputStream);
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
/**
* 通过Uri获取Bitmap
*
* @param context 上下文环境
* @param uri 图片uri
* @return Bitmap
*/
public static Bitmap getBitmapFromUri(Context context, Uri uri) {
//方式一
// ContentResolver contentResolver = context.getContentResolver();
// try {
// ParcelFileDescriptor parcelFileDescriptor = contentResolver.openFileDescriptor(uri, "r");
// if (parcelFileDescriptor != null) {
// FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
// Bitmap bitmap = BitmapFactory.decodeFileDescriptor(fileDescriptor);
// parcelFileDescriptor.close();
// return bitmap;
// }
// } catch (IOException e) {
// e.printStackTrace();
// }
// return null;
//方式二
InputStream inputStream = getImgStream(context, uri);
return BitmapFactory.decodeStream(inputStream);
}
/**
* 流操作
*
* @param inputStream 输入流
* @param outputStream 输出流
*/
private static void streamOperator(InputStream inputStream, OutputStream outputStream) {
if (inputStream == null || outputStream == null) {
return;
}
byte[] buffer = new byte[1024];
int readLength;
try {
while ((readLength = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, readLength);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
outputStream.flush();
outputStream.close();
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 创建文件目录
*
* @param dir 目录名
* @return 创建结果
*/
private static boolean createDirs(String dir) {
if (TextUtils.isEmpty(dir)) {
return false;
}
File dirFile = new File(dir);
if (!dirFile.exists()) {
return dirFile.mkdirs();
}
return true;
}
/**
* 获取图片的 mime_type
*
* @param filePath 图片的文件路径
* @return 图片的 mime_type
*/
private static String getImgMimeType(String filePath) {
String lowerPath = filePath.toLowerCase();
if (lowerPath.endsWith("jpg") || lowerPath.endsWith("jpeg")) {
return "image/jpeg";
} else if (lowerPath.endsWith("png")) {
return "image/png";
} else if (lowerPath.endsWith("gif")) {
return "image/gif";
}
return "image/jpeg";
}
/**
* 媒体类资源
* 插入时初始化公共字段
*
* @param fileName 文件名(带文件名后缀,如:.jpg),非文件路径地址
*/
private static ContentValues commonMediaColumn(String fileName) {
ContentValues contentValues = new ContentValues();
// contentValues.put(MediaStore.MediaColumns.TITLE, file.getName());//不带扩展名的文件名,如:IMG1024
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);//带扩展名的文件名,如:IMG1024.JPG
// contentValues.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath());//文件绝对路径,已过期
// contentValues.put(MediaStore.MediaColumns.SIZE, file.length());//文件大小,单位为 byte
contentValues.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis());//文件第一次被添加的时间
return contentValues;
}
/**
* 视频:
* 存储在 DCIM/、Movies/ 和 Pictures/ 目录中
* 系统将这些文件添加到 MediaStore.Video 表格中
*/
public static void videoInsert(BaseActivity baseActivity) {
// MediaStore.Video
}
/**
* 音频:
* 存储在 Alarms/、Audiobooks/、Music/、Notifications/、Podcasts/、Ringtones 目录中,以及位于 Music/或 Movies/ 目录中的音频播放列表中
* 系统将这些文件添加到 MediaStore.Audio 表格中
*/
public static void audioInsert() {
// MediaStore.Audio
}
/**
* 下载的文件:
* 存储在 storage/emulated/0/Download/com.jlpay.partner 目录中
* 在搭载 Android 10 (API级别29)及更高的设备上,这些文件存储在 MediaStore.Downloads 表格中,此表格在 Android 9 (API级别28)及更低版本中不可用
*
* @param baseActivity 上下文环境
* @param inputStream 下载文件的输入流
* @param fileName 带扩展名的文件名
*/
public static void downloadInsert(BaseActivity baseActivity, InputStream inputStream, String fileName) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentResolver contentResolver = baseActivity.getContentResolver();
//Android 10在MediaStore中新增了一种Downloads集合,专门用于执行文件下载操作
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);//带扩展名的文件名,如:IMG1024.JPG,必须
contentValues.put(MediaStore.MediaColumns.DATE_ADDED, System.currentTimeMillis());//文件第一次被添加的时间,非必须
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, DOWNLOADS_PUBLIC_DIR);//文件存储的相对路径,必须
//执行insert操作,向系统文件夹中添加文件,若生成了uri,则表示该文件添加成功,使用流将内容写入该uri中即可
//这一步会在Download目录下创建partner文件夹(没有该文件夹时)
//MediaStore.Downloads是Android 10中新增的API,至于Android 9及以下的系统版本,仍然使用之前的代码来进行文件下载
Uri uri = contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues);
if (uri != null) {
try {
OutputStream outputStream = contentResolver.openOutputStream(uri);
if (outputStream != null) {
streamOperator(inputStream, outputStream);
ToastUtils.showToastApplication(baseActivity, "文件下载完成");
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
} else {
//AndroidQ以下版本,直接使用外部公共存储目录下的文件绝对路径
String fileDir = Constants.external_storage_dir + File.separator + DOWNLOADS_PUBLIC_DIR;
if (createDirs(fileDir)) {
String filePath = fileDir + File.separator + fileName;
try {
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
streamOperator(inputStream, fileOutputStream);
ToastUtils.showToastApplication(baseActivity, "文件下载完成");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
}
/**
* 文件:
* 内容取决于应用是否使用分区存储(适用于以 Android 10 或更高版本为目标平台的应用)
* 如果启用了分区存储,MediaStore.Files 集合只会显示应用创建的照片、视频和音频文件
* 如果分区存储不可用或未使用,集合将显示所有类型的媒体文件
*/
public static void filesInsert() {
// MediaStore.Files
}
/**
* 删除文件,慎用(Android10以上部分设备删除如相册图片会弹通知告知用户)
*
* @param context 上下文环境
* @param uri 文件Uri
*/
public static void deleteFile(Context context, Uri uri) {
context.getContentResolver().delete(uri, null, null);
}
}
2、图片压缩操作处理适配Android 10
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.BitmapFactory.Options;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.exifinterface.media.ExifInterface;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import top.zibin.luban.Luban;
import top.zibin.luban.OnCompressListener;
public class BitmapUtil {
private static final int ww = 480;
private static final int hh = 800;
private static final String TAG = BitmapUtil.class.getSimpleName();
/**
* 图片压缩监听接口
*/
public interface ImageCompressListener {
void onSuccess(File file);
void onError(String msg);
}
/**
* 图片压缩
*
* @param context
* @param imgPath 图片本地路径
* @param listener
*/
public static void imageCompress(Context context, String imgPath, ImageCompressListener listener) {
// sizeCompress(imgPath, System.currentTimeMillis() + "_sizeCompress", 1500, 1500, listener);//压缩尺寸标准是后台老王给的
// qualityCompress(imgPath, System.currentTimeMillis() + "_qualityCompress", 300, listener);//质量压缩
// commonCompress(imgPath, System.currentTimeMillis() + "_commonCompress", 1500, 1500, 300, listener);//通用压缩
lubanCompress(context, imgPath, listener);
}
/**
* 图片压缩,适配 Android Q
*
* @param context
* @param uri 图片uri
* @param listener
*/
public static void imageCompress(Context context, Uri uri, ImageCompressListener listener) {
// sizeCompress(uri, System.currentTimeMillis() + "_sizeCompress", 1500, 1500, listener);//压缩尺寸标准是后台老王给的
// qualityCompress(uri, System.currentTimeMillis() + "_qualityCompress", 300, listener);//质量压缩
commonCompress(uri, System.currentTimeMillis() + "_commonCompress", 1500, 1500, 300, listener);//通用压缩
// lubanCompress(context, uri, listener);//LuBan.load(Uri uri)方法有问题,只能通过图片文件路径来load,
}
/**
* 图片压缩-Luban压缩
* 注:LuBan.load(Uri uri)方法有问题,只能通过图片文件路径来load,
* 因此适配 Android Q时,对于共享目录下的图片文件无法使用Uri进行压缩,暂时只能通过复制到APP私有目录下再进行压缩(APP外部私有目录可以通过路径访问),然后压缩完后删除原图
* 自己写的基于Android原生方法压缩的不需要复制这一步
*
* @param context
* @param imgPath 图片本地路径
* @param listener
*/
public static void lubanCompress(Context context, @NonNull String imgPath, ImageCompressListener listener) {
FileUtils.makeDirs(IMAGE_SAVE_DIR);
File originFile = new File(imgPath);
if (!originFile.exists()) {
listener.onError(context.getResources().getString(R.string.file_not_exist));
return;
}
Luban.with(context)
.load(originFile)
.ignoreBy(1024)
.setTargetDir(IMAGE_SAVE_DIR)
.setCompressListener(new OnCompressListener() {
@Override
public void onStart() {
}
@Override
public void onSuccess(File file) {
//小于1024KB的文件不会压缩,若BasePicturesActivity里考虑使用复制到APP外部私有目录下再压缩的策略,可以放开这里
if (!file.getAbsolutePath().equals(originFile.getAbsolutePath())) {
boolean delete = originFile.delete();
}
listener.onSuccess(file);
}
@Override
public void onError(Throwable e) {
listener.onError(e.getMessage());
}
})
.launch();
}
/**
* 图片压缩-Android原生,Android Q 后仅限APP私有目录下文件使用
* 先进行尺寸压缩,加载到内存中后看照片是否有旋转角度,进行旋转,再进行质量压缩,最后保存本地
*
* @param imgPath 图片路径
* @param compressFileName 压缩后的文件名,不用带目录和后缀(如.png或者.jpg)
* @param reqWidth 要求压缩后的图片宽度
* @param reqHeight 要求压缩后的图片高度
* @param targetSize 要求压缩后的图片大小,单位:KB
* @param listener 压缩结果监听
*/
public static void commonCompress(@NonNull String imgPath, @NonNull String compressFileName, @NonNull int reqWidth, @NonNull int reqHeight,
@NonNull int targetSize, @NonNull ImageCompressListener listener) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
Bitmap bitmap = BitmapFactory.decodeFile(imgPath);
int width = bitmap.getWidth();
int height = bitmap.getHeight();
options.inJustDecodeBounds = false;
int sampleSize = 1;
while ((width / sampleSize > reqWidth) || (height / sampleSize > reqHeight)) {
sampleSize *= 2;
}
options.inSampleSize = sampleSize;
try {
bitmap = BitmapFactory.decodeFile(imgPath, options);
} catch (OutOfMemoryError e) {
listener.onError("The bitmap has out of memory:" + e.getMessage());
return;
}
//角度旋转
bitmap = rotatePicByDegree(bitmap, getPictureDegree(imgPath));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
int quality = 90;
while (byteArrayOutputStream.toByteArray().length / 1024 > targetSize && quality >= 0) {
byteArrayOutputStream.reset();//重置
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
quality -= 10;
}
String child = compressFileName + ".jpg";
File file = new File(IMAGE_SAVE_DIR, child);
try {
FileOutputStream outputStream = new FileOutputStream(file);
byteArrayOutputStream.writeTo(outputStream);
// outputStream.write(byteArrayOutputStream.toByteArray());//或者这样
outputStream.flush();
outputStream.close();
byteArrayOutputStream.close();
Log.e("Bitmap.Size.Compressed:", file.length() / 1024 + "KB");
listener.onSuccess(file);
} catch (IOException e) {
e.printStackTrace();
listener.onError(e.getMessage());
}
bitmapRecycle(bitmap);
}
/**
* 图片压缩-Android原生,适配 Android Q,且不需要复制到APP私有目录这一步
* 先进行尺寸压缩,加载到内存中后看照片是否有旋转角度,进行旋转,再进行质量压缩,最后保存本地
*
* @param uri 图片uri
* @param compressFileName 压缩后的文件名,不用带目录和后缀(如.png或者.jpg)
* @param reqWidth 要求压缩后的图片宽度
* @param reqHeight 要求压缩后的图片高度
* @param targetSize 要求压缩后的图片大小,单位:KB
* @param listener 压缩结果监听
*/
public static void commonCompress(@NonNull Uri uri, @NonNull String compressFileName, @NonNull int reqWidth, @NonNull int reqHeight,
@NonNull int targetSize, @NonNull ImageCompressListener listener) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
InputStream imgStream = MediaStoreUtils.getImgStream(uri);
Bitmap bitmap = BitmapFactory.decodeStream(imgStream);
int width = bitmap.getWidth();
int height = bitmap.getHeight();
options.inJustDecodeBounds = false;
int sampleSize = 1;
while ((width / sampleSize > reqWidth) || (height / sampleSize > reqHeight)) {
sampleSize *= 2;
}
options.inSampleSize = sampleSize;
try {
bitmap = BitmapFactory.decodeStream(imgStream, null, options);
} catch (OutOfMemoryError e) {
listener.onError("The bitmap has out of memory:" + e.getMessage());
return;
}
//角度旋转
bitmap = rotatePicByDegree(bitmap, getPictureDegree(imgStream));
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
int quality = 90;
while (byteArrayOutputStream.toByteArray().length / 1024 > targetSize && quality >= 0) {
byteArrayOutputStream.reset();//重置
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
quality -= 10;
}
String child = compressFileName + ".jpg";
File file = new File(IMAGE_SAVE_DIR, child);
try {
FileOutputStream outputStream = new FileOutputStream(file);
byteArrayOutputStream.writeTo(outputStream);
// outputStream.write(byteArrayOutputStream.toByteArray());//或者这样
outputStream.flush();
outputStream.close();
byteArrayOutputStream.close();
Log.e("Bitmap.Size.Compressed:", file.length() / 1024 + "KB");
listener.onSuccess(file);
} catch (IOException e) {
e.printStackTrace();
listener.onError(e.getMessage());
}
bitmapRecycle(bitmap);
}
/**
* 图片压缩-Android原生只尺寸压缩
* 但是实际上{@link #bitmapToFile 方法中还是有调用compress的质量压缩,只是取了最高quality:100}
* 一般通用完整流程:先进行尺寸压缩,然后在进行质量压缩,然后看照片是否有旋转角度,如果有,rotate一下,最后返回处理后的照片路径
*
* @param imgPath 图片路径
* @param compressFileName 压缩后的文件名,不用带目录和后缀(如.png或者.jpg)
* @param reqWidth 要求压缩后的图片宽度
* @param reqHeight 要求压缩后的图片高度
* @param listener 压缩结果监听
*/
public static void sizeCompress(String imgPath, String compressFileName, int reqWidth, int reqHeight, ImageCompressListener listener) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;//开始读入图片
Bitmap bitmap = BitmapFactory.decodeFile(imgPath, options);//此时返回bitmap为空
int width = options.outWidth;
int height = options.outHeight;
options.inJustDecodeBounds = false;
//计算采样率
int sampleSize = 1;//压缩比例
while ((width / sampleSize > reqWidth) || (height / sampleSize > reqHeight)) {
sampleSize *= 2;
}
options.inSampleSize = sampleSize;
try {
bitmap = BitmapFactory.decodeFile(imgPath, options);//加载采样率压缩后的图片到内存
} catch (OutOfMemoryError e) {
listener.onError("The bitmap has out of memory:" + e.getMessage());
return;
}
bitmap = rotatePicByDegree(bitmap, getPictureDegree(imgPath));//将图片旋转为正常角度
//保存压缩后的图片到本地
FileUtils.makeDirs(IMAGE_SAVE_DIR);
String child = compressFileName + ".jpg";
if (bitmapToFile(bitmap, child)) {
listener.onSuccess(new File(IMAGE_SAVE_DIR, child));
} else {
listener.onError("image_compress_failed");
}
bitmapRecycle(bitmap);
}
/**
* 图片压缩-Android原生只尺寸压缩,适配 Android Q
* 但是实际上{@link #bitmapToFile 方法中还是有调用compress的质量压缩,只是取了最高quality:100}
* 一般通用完整流程:先进行尺寸压缩,然后在进行质量压缩,然后看照片是否有旋转角度,如果有,rotate一下,最后返回处理后的照片路径
*
* @param uri 图片uri
* @param compressFileName 压缩后的文件名,不用带目录和后缀(如.png或者.jpg)
* @param reqWidth 要求压缩后的图片宽度
* @param reqHeight 要求压缩后的图片高度
* @param listener 压缩结果监听
*/
public static void sizeCompress(@NonNull Uri uri, String compressFileName, int reqWidth, int reqHeight, ImageCompressListener listener) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;//开始读入图片
InputStream imgStream = MediaStoreUtils.getImgStream(uri);
Bitmap bitmap = BitmapFactory.decodeStream(imgStream, null, options);//此时返回bitmap为空
int width = options.outWidth;
int height = options.outHeight;
options.inJustDecodeBounds = false;
//计算采样率
int sampleSize = 1;//压缩比例
while ((width / sampleSize > reqWidth) || (height / sampleSize > reqHeight)) {
sampleSize *= 2;
}
options.inSampleSize = sampleSize;
try {
bitmap = BitmapFactory.decodeStream(imgStream, null, options);//加载采样率压缩后的图片到内存
} catch (OutOfMemoryError e) {
listener.onError("The bitmap has out of memory:" + e.getMessage());
return;
}
bitmap = rotatePicByDegree(bitmap, getPictureDegree(imgStream));//将图片旋转为正常角度
//保存压缩后的图片到本地
FileUtils.makeDirs(IMAGE_SAVE_DIR);
String child = compressFileName + ".jpg";
if (bitmapToFile(bitmap, child)) {
listener.onSuccess(new File(IMAGE_SAVE_DIR, child));
} else {
listener.onError("image_compress_failed");
}
bitmapRecycle(bitmap);
}
/**
* 图片压缩-Android原生只质量压缩
*
* @param imgPath 图片路径
* @param compressFileName 压缩后的文件名,不用带目录和后缀(如.png或者.jpg)
* @param targetSize 要求压缩后的图片大小,单位:KB
* @param listener 压缩结果监听
*/
public static void qualityCompress(String imgPath, String compressFileName, int targetSize, ImageCompressListener listener) {
Bitmap bitmap = null;
try {
//避免图片加载占用过大内存导致崩溃
bitmap = BitmapFactory.decodeFile(imgPath);
bitmap = rotatePicByDegree(bitmap, getPictureDegree(imgPath));
} catch (OutOfMemoryError e) {
listener.onError("The bitmap has out of memory:" + e.getMessage());
return;
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
int quality = 90;
//这里也可以设置为quality >= 0,这样可以压到最小,但是也不一定就能满足targetSize
while (byteArrayOutputStream.toByteArray().length / 1024 > targetSize && quality >= 0) {
byteArrayOutputStream.reset();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
Log.d("Bitmap.Size.Comp.Str:", byteArrayOutputStream.toByteArray().length / 1024 + "KB" + "\t" + "quality = " + quality);
quality -= 10;
}
FileUtils.makeDirs(IMAGE_SAVE_DIR);
String child = compressFileName + ".jpg";
File file = new File(IMAGE_SAVE_DIR, child);
try {
FileOutputStream fileOutputStream = new FileOutputStream(file);
byteArrayOutputStream.writeTo(fileOutputStream);
// fileOutputStream.write(byteArrayOutputStream.toByteArray());//或者這樣
fileOutputStream.flush();
fileOutputStream.close();
byteArrayOutputStream.close();
Log.e("Bitmap.Size.Compressed:", file.length() / 1024 + "KB");
listener.onSuccess(file);
} catch (IOException e) {
e.printStackTrace();
listener.onError(e.getMessage());
}
bitmapRecycle(bitmap);
}
/**
* 图片压缩-Android原生只质量压缩, 适配 Android Q
*
* @param uri 图片uri
* @param compressFileName 压缩后的文件名,不用带目录和后缀(如.png或者.jpg)
* @param targetSize 要求压缩后的图片大小,单位:KB
* @param listener 压缩结果监听
*/
public static void qualityCompress(@NonNull Uri uri, String compressFileName, int targetSize, ImageCompressListener listener) {
Bitmap bitmap = null;
InputStream imgStream = MediaStoreUtils.getImgStream(uri);
try {
//避免图片加载占用过大内存导致崩溃
bitmap = BitmapFactory.decodeStream(imgStream);
bitmap = rotatePicByDegree(bitmap, getPictureDegree(imgStream));
} catch (OutOfMemoryError e) {
listener.onError("The bitmap has out of memory:" + e.getMessage());
return;
}
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
int quality = 90;
//这里也可以设置为quality >= 0,这样可以压到最小,但是也不一定就能满足targetSize
while (byteArrayOutputStream.toByteArray().length / 1024 > targetSize && quality >= 0) {
byteArrayOutputStream.reset();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
Log.d("Bitmap.Size.Comp.Str:", byteArrayOutputStream.toByteArray().length / 1024 + "KB" + "\t" + "quality = " + quality);
quality -= 10;
}
FileUtils.makeDirs(IMAGE_SAVE_DIR);
String child = compressFileName + ".jpg";
File file = new File(IMAGE_SAVE_DIR, child);
try {
FileOutputStream fileOutputStream = new FileOutputStream(file);
byteArrayOutputStream.writeTo(fileOutputStream);
// fileOutputStream.write(byteArrayOutputStream.toByteArray());//或者這樣
fileOutputStream.flush();
fileOutputStream.close();
byteArrayOutputStream.close();
Log.e("Bitmap.Size.Compressed:", file.length() / 1024 + "KB");
listener.onSuccess(file);
} catch (IOException e) {
e.printStackTrace();
listener.onError(e.getMessage());
}
bitmapRecycle(bitmap);
}
public static byte[] qualityCompress(Bitmap bitmap, int targetSize) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
byte[] result;
int quality = 100;
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
while (byteArrayOutputStream.toByteArray().length / 1024 > targetSize && quality >= 0) {
byteArrayOutputStream.reset();
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, byteArrayOutputStream);
Log.d("Bitmap.Size.Comp.Str:", byteArrayOutputStream.toByteArray().length / 1024 + "KB" + "\t" + "quality = " + quality);
quality -= 10;
}
result = byteArrayOutputStream.toByteArray();
return result;
}
/**
* 将图片按照某个角度进行旋转
*
* @param bitmap 待旋转图片
* @param degree 旋转角度
* @return 旋转后的图片
*/
public static Bitmap rotatePicByDegree(Bitmap bitmap, int degree) {
Bitmap rotateBitmap = null;
//根据旋转角度,生成旋转矩阵
Matrix matrix = new Matrix();
matrix.postRotate(degree);
try {
// 将原始图片按照旋转矩阵进行旋转,并得到新的图片
rotateBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
} catch (OutOfMemoryError e) {
e.printStackTrace();
}
if (rotateBitmap == null) {
rotateBitmap = bitmap;
}
if (bitmap != rotateBitmap) {
bitmap.recycle();
bitmap = null;
}
return rotateBitmap;
}
/**
* 获取图片的旋转角度
* 手机拍照的图片,本地查看正常的照片,传到服务器发现照片旋转了90°或者270°,这是因为有些手机摄像头的参数原因,拍出来的照片是自带旋转角度的
*
* @param imgPath 本地图片路径
* @return 图片旋转角度
*/
public static int getPictureDegree(String imgPath) {
int degree = 0;
try {
//获取图片的exif信息,exif是照片的一些头部信息,如拍照时间,相机品牌,型号,色彩编码,旋转角度等
ExifInterface exifInterface = new ExifInterface(imgPath);
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;
}
/**
* 获取图片的旋转角度,适配 Android Q
* 手机拍照的图片,本地查看正常的照片,传到服务器发现照片旋转了90°或者270°,这是因为有些手机摄像头的参数原因,拍出来的照片是自带旋转角度的
*
* @param inputStream 输入流
* @return 图片旋转角度
*/
public static int getPictureDegree(@NonNull InputStream inputStream) {
int degree = 0;
try {
//获取图片的exif信息,exif是照片的一些头部信息,如拍照时间,相机品牌,型号,色彩编码,旋转角度等
ExifInterface exifInterface = new ExifInterface(inputStream);
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;
}
/**
* bitmap格式转文件
* 存在质量压缩
*
* @param bitmap 待转换bitmap
* @param fileName 本地文件名,默认不带后缀,需要调用者自己加
* @return true 转换成功
*/
public static boolean bitmapToFile(Bitmap bitmap, String fileName) {
if (bitmap == null || bitmap.isRecycled()) {
return false;
}
if (TextUtils.isEmpty(fileName)) {
return false;
}
boolean convertResult = false;
FileUtils.makeDirs(IMAGE_SAVE_DIR);
File file = new File(IMAGE_SAVE_DIR, fileName);
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(file);
convertResult = bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
Log.e("Bitmap.Size.Compressed:", file.length() / 1024 + "KB");
} catch (FileNotFoundException e) {
e.printStackTrace();
} finally {
try {
if (outputStream != null) {
outputStream.flush();
outputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return convertResult;
}
/**
* 文件转Bitmap
*
* @param filePath 文件路径
* @return Bitmap
*/
public static Bitmap fileToBitmap(@NonNull String filePath) {
if (TextUtils.isEmpty(filePath)) {
return null;
}
Bitmap bitmap = null;
try {
bitmap = BitmapFactory.decodeFile(filePath);
} catch (OutOfMemoryError e) {
e.printStackTrace();
return null;
}
return bitmap;
}
/**
* Bitmap内存回收
*
* @param bitmaps 准备回收内存的Bitmaps
*/
public static void bitmapRecycle(Bitmap... bitmaps) {
if (bitmaps == null) {
return;
}
for (Bitmap bitmap : bitmaps) {
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
}
}
/**
* 根据图片资源的Uri获取输入流
*
* @param context
* @param uri Android图片资源的Uri
* @return 图片的输入流
*/
// private InputStream getImageStream(Context context, Uri uri) {
// InputStream inputStream = null;
// try {
// inputStream = context.getApplicationContext().getContentResolver().openInputStream(uri);
// } catch (FileNotFoundException e) {
// e.printStackTrace();
// }
// return inputStream;
// }
/**
* 图片变圆角
*
* @param bitmap
* @param pixels
* @return
*/
public static Bitmap toRoundCorner(Bitmap bitmap, int pixels) {
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(output);
final int color = 0xff424242;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(rect);
final float roundPx = pixels;
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawRoundRect(rectF, roundPx, roundPx, paint);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
return output;
}
/**
* 通过 filePath 转换成 Bitmap
*
* @param filePath
* @return
*/
public static Bitmap getdecodeBitmap(String filePath) {
if (filePath == null) {
return null;
}
File file = new File(filePath);
if (!file.exists()) {
return null;
}
Options options = new Options();
options.inJustDecodeBounds = true;
int width = options.outWidth;
int height = options.outHeight;
float scale = 1f;
if (width > ww && width > height) {
scale = width / ww;
} else if (height > hh && height > width) {
scale = height / hh;
} else {
scale = 1;
}
options.inSampleSize = (int) scale;
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);
return bitmap;
}
/**
* 保存到本地
*
* @param mBitmap
*/
public static boolean savePhotoToLocal(Bitmap mBitmap, String dir, String filename) {
if (mBitmap == null || TextUtils.isEmpty(dir) || TextUtils.isEmpty(filename)) {
return false;
}
boolean compress;
String sdStatus = Environment.getExternalStorageState();
if (!sdStatus.equals(Environment.MEDIA_MOUNTED)) { // 检测sd是否可用
return false;
}
FileOutputStream b = null;
File file = new File(dir);
if (!file.exists()) {
file.mkdirs();// 创建文件夹
}
File filePic = new File(dir + File.separator + filename);
if (filePic.exists()) {
filePic.delete();
}
try {
b = new FileOutputStream(file + File.separator + filename);
compress = mBitmap.compress(Bitmap.CompressFormat.PNG, 100, b);// 把数据写入文件
} catch (FileNotFoundException e) {
e.printStackTrace();
return false;
} finally {
try {
//关闭流
if (b != null) {
b.flush();
b.close();
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
return compress;
}
/**
* 保存到本地
* 并通知图库更新
*
* @param mBitmap
*/
public static boolean savePhotoToLocalAndBroadcast(Context context, Bitmap mBitmap, String dir, String filename) {
if (savePhotoToLocal(mBitmap, dir, filename)) {
if (context != null) {
// 最后通知图库更新
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + dir + "/" + filename)));
}
return true;
}
return false;
}
@Deprecated
public static void insertImageToMediaStore(BaseActivity context, String filePath) {
insertImageToMediaStore(context, filePath, 0, 0);
}
/**
* 保存到照片到本地,并插入MediaStore以保证相册可以查看到,这是更优化的方法,防止读取的照片获取不到宽高
*
* @param context 上下文
* @param filePath 文件路径
* @param width 宽度
* @param height 高度
*/
@Deprecated
public static void insertImageToMediaStore(BaseActivity context, String filePath, int width, int height) {
if (!checkFile(filePath))
return;
PermissionUtils.getStoragePermission(context, () -> {
long createTime = System.currentTimeMillis();
ContentValues values = initCommonContentValues(filePath, createTime);
values.put(MediaStore.Images.ImageColumns.DATE_TAKEN, createTime);
values.put(MediaStore.Images.ImageColumns.ORIENTATION, 0);
values.put(MediaStore.Images.ImageColumns.ORIENTATION, 0);
if (width > 0) values.put(MediaStore.Images.ImageColumns.WIDTH, 0);
if (height > 0) values.put(MediaStore.Images.ImageColumns.HEIGHT, 0);
values.put(MediaStore.MediaColumns.MIME_TYPE, getPhotoMimeType(filePath));
Uri uri = context.getApplicationContext().getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
ToastUtils.showToastApplication(context, context.getResources().getString(R.string.qr_code_has_saved));
});
}
///
// 非系统相册像MediaContent中插入数据,核心方法
///
/**
* 针对非系统文件夹下的文件,使用该方法
* 插入时初始化公共字段
*
* @param filePath 文件
* @param time ms
* @return ContentValues
*/
private static ContentValues initCommonContentValues(String filePath, long time) {
ContentValues values = new ContentValues();
File saveFile = new File(filePath);
long timeMillis = getTimeWrap(time);
values.put(MediaStore.MediaColumns.TITLE, saveFile.getName());
values.put(MediaStore.MediaColumns.DISPLAY_NAME, saveFile.getName());
values.put(MediaStore.MediaColumns.DATE_MODIFIED, timeMillis);
values.put(MediaStore.MediaColumns.DATE_ADDED, timeMillis);
values.put(MediaStore.MediaColumns.DATA, saveFile.getAbsolutePath());//TODO
values.put(MediaStore.MediaColumns.SIZE, saveFile.length());
return values;
}
/**
* 获取照片的mine_type
*
* @param path 文件路径
* @return mine_type
*/
private static String getPhotoMimeType(String path) {
String lowerPath = path.toLowerCase();
if (lowerPath.endsWith("jpg") || lowerPath.endsWith("jpeg")) {
return "image/jpeg";
} else if (lowerPath.endsWith("png")) {
return "image/png";
} else if (lowerPath.endsWith("gif")) {
return "image/gif";
}
return "image/jpeg";
}
// 获得转化后的时间
private static long getTimeWrap(long time) {
if (time <= 0) {
return System.currentTimeMillis();
}
return time;
}
// 检测文件存在
private static boolean checkFile(String filePath) {
boolean result = false;
File mFile = new File(filePath);
if (mFile.exists()) {
result = true;
}
Log.e(TAG, "文件不存在 path = " + filePath);
return result;
}
/**
* 通知图库有更新
*
* @param context
* @param filePath
* @return
*/
public static boolean notiGallery(Context context, String filePath) {
if (context != null) {
// 最后通知图库更新
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + filePath)));
}
return false;
}
public static boolean isCanUseSD() {
try {
return Environment.getExternalStorageState().equals("mounted");
} catch (Exception var1) {
var1.printStackTrace();
return false;
}
}
public static Uri addImageGallery(File file) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
Uri insertUri = PartnerApp.getContext().getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
return insertUri;
}
/**
* 将文件转成base64 字符串
*
* @param path 文件路径
* @return *
* @throws Exception
*/
public static String encodeBase64File(String path) throws Exception {
File file = new File(path);
FileInputStream inputFile = new FileInputStream(file);
byte[] buffer = new byte[(int) file.length()];
inputFile.read(buffer);
inputFile.close();
return Base64.encodeToString(buffer, Base64.DEFAULT);
}
/**
* bitmap转为base64
*
* @param bitmap
* @return
*/
public static String bitmapToBase64(Bitmap bitmap) {
String result = null;
ByteArrayOutputStream baos = null;
try {
if (bitmap != null) {
baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
baos.flush();
baos.close();
byte[] bitmapBytes = baos.toByteArray();
result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (baos != null) {
baos.flush();
baos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
/**
* H5界面的图片的base64编码转文件
*
* @param context
* @param base64Str
* @return
*/
public static File h5Base64ToFile(Context context, String base64Str, String fileName) {
if (context == null || TextUtils.isEmpty(base64Str)) {
return null;
}
int indexOf = base64Str.lastIndexOf(",");
String base64 = base64Str.substring(indexOf + 1);
File file = null;
FileOutputStream out = null;
try {
String dir = IMAGE_SAVE_DIR;
File dirFile = new File(dir);
if (!dirFile.exists()) {
dirFile.mkdirs();
}
file = new File(dir, fileName);
if (file.exists()) {
file.delete();
}
byte[] bytes = Base64.decode(base64, Base64.DEFAULT);// 将字符串转换为byte数组
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
byte[] buffer = new byte[1024];
out = new FileOutputStream(file);
int bytesum = 0;
int byteread = 0;
while ((byteread = in.read(buffer)) != -1) {
bytesum += byteread;
out.write(buffer, 0, byteread);
}
} catch (IllegalArgumentException illegalArguExce) {
CrashReport.postCatchedException(new Exception("base64异常:" + base64Str));
} catch (IOException io) {
io.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return file;
}
/**
* 图片的base64编码转文件
*
* @param base64 图片的base64编码
* @return 转换后的本地文件
*/
public static File base64ToFile(Context context, String base64, String fileName) {
if (context == null || TextUtils.isEmpty(base64)) {
return null;
}
File file = null;
FileOutputStream out = null;
try {
String dir = context.getExternalFilesDir(null) + "/";
File dirFile = new File(dir);
if (!dirFile.exists()) {
dirFile.mkdirs();
}
file = new File(dir, fileName);
if (file.exists()) {
file.delete();
}
byte[] bytes = Base64.decode(base64, Base64.DEFAULT);// 将字符串转换为byte数组
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
byte[] buffer = new byte[1024];
out = new FileOutputStream(file);
int bytesum = 0;
int byteread = 0;
while ((byteread = in.read(buffer)) != -1) {
bytesum += byteread;
out.write(buffer, 0, byteread);
}
} catch (IOException ioe) {
ioe.printStackTrace();
} finally {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return file;
}
/**
* 文件转base64字符串
*
* @param file
* @return
*/
public static String fileToBase64(File file) {
if (file == null || !file.exists()) {
return null;
}
String base64 = null;
InputStream in = null;
try {
in = new FileInputStream(file);
byte[] bytes = new byte[in.available()];
int length = in.read(bytes);
base64 = Base64.encodeToString(bytes, 0, length, Base64.DEFAULT);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return base64;
}
/**
* @param filePath
* @return
*/
public static Bitmap getShareIcon(String filePath) {
File file = new File(filePath);
if (!file.exists()) {
return null;
}
int ww = 100;
int hh = 100;
if (filePath == null) {
return null;
}
Options options = new Options();
options.inJustDecodeBounds = true;
int width = options.outWidth;
int height = options.outHeight;
float scale = 1f;
if (width > ww && width > height) {
scale = width / ww;
} else if (height > hh && height > width) {
scale = height / hh;
} else {
scale = 1;
}
options.inSampleSize = (int) scale;
options.inJustDecodeBounds = false;
Bitmap bitmap = BitmapFactory.decodeFile(filePath, options);
return bitmap;
}
/**
* 获取图片宽高比 ratio
* 不能为0;
*
* @param resId
*/
public static double getPicHeightWidthRatio(Context context, int resId) {
double ratio = 1;
Options opts = new Options();
opts.inJustDecodeBounds = true;
BitmapFactory.decodeResource(context.getResources(), resId, opts);
if (opts.outHeight * 1.0 / opts.outWidth > 0) {
ratio = opts.outHeight * 1.0 / opts.outWidth;
}
return ratio;
}
/**
* 拷贝文件
*
* @param oldPath
* @param newPath
*/
public static void copyFile(String oldPath, String newPath) {
try {
int bytesum = 0;
int byteread = 0;
File oldfile = new File(oldPath);
if (oldfile.exists()) { //文件存在时
File newFile = new File(newPath);
if (newFile.exists()) {
newFile.delete();
}
InputStream inStream = new FileInputStream(oldPath); //读入原文件
FileOutputStream fs = new FileOutputStream(newPath);
byte[] buffer = new byte[1444];
int length;
while ((byteread = inStream.read(buffer)) != -1) {
bytesum += byteread; //字节数 文件大小
System.out.println(bytesum);
fs.write(buffer, 0, byteread);
}
inStream.close();
}
} catch (Exception e) {
System.out.println("复制单个文件操作出错");
e.printStackTrace();
}
}
/**
* 从网络路径中获取文件名
* <p>
* http://112.95.224.228:49154/group1/M00/0A/DF/rBEDm1akgiWAdCd1AAFIsYTwxFE442.jpg?ts=1456395458&token=933bcbefe9ac41e57d3e457ef641f875
* ---> rBEDm1akgiWAdCd1AAFIsYTwxFE442.jpg
*
* @param url
* @return
*/
public static String getImageNameFrom(String url) {
if (url == null) {
return null;
}
int startIndex = url.lastIndexOf("/");
int lastIndex = url.indexOf("?");
if (startIndex < 0 || lastIndex < 0) {
return null;
}
if (url.length() < lastIndex) {
return null;
}
String filename = url.substring(startIndex + 1, lastIndex);
return filename;
}
/**
* 从本地获取网络图片的缓存的路径
*
* @param url
* @return
*/
public static String getCacheImagePathFromUrl(String url) {
String path = null;
String cacheDir = AccountManager.get().getCacheDir();
String sdStatus = Environment.getExternalStorageState();
if (!sdStatus.equals(Environment.MEDIA_MOUNTED)) { // 检测sd是否可用
return path;
}
FileOutputStream b = null;
File file = new File(cacheDir);
if (!file.exists()) {
file.mkdirs();
}
final String imagename = getImageNameFrom(url);
String[] list = file.list(new FilenameFilter() {
@Override
public boolean accept(File dir, String filename) {
if (filename.equals(imagename)) {
return true;
}
return false;
}
});
if (list.length > 0) {
path = list[0];
path = AccountManager.get().getCacheDir() + "/" + path;
}
return path;
}
/**
* 把batmap 转file
*
* @param bitmap
* @param filePath
* @param quality
* @return
*/
public static File saveBitmapFile(Bitmap bitmap, String filePath, int quality) {
if (bitmap == null || bitmap.isRecycled()) {
return null;
}
if (TextUtils.isEmpty(filePath)) {
filePath = getOutputMediaFilePath();
}
File file = null;
BufferedOutputStream bos = null;
try {
file = new File(filePath);//将要保存图片的路径
bos = new BufferedOutputStream(new FileOutputStream(file));
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, bos);
bos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bos != null) {
try {
bos.close();
} catch (Throwable e) {
e.printStackTrace();
}
}
}
return file;
}
public static String getOutputMediaFilePath() {
String imgPath = "";
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String fileName = timeStamp + System.nanoTime() + ".jpg";
String imgDirPath = Constants.external_files_dir + "DCIM/imgshort/";
File imgDir = new File(imgDirPath);
if (!imgDir.exists()) {
imgDir.mkdirs();
}
imgPath = imgDirPath + fileName;
return imgPath;
}
}
3、通过SAF文件访问框架访问非媒体类型文件(如pdf和word)
import java.io.InputStream;
/**
* 该工具类主要用于实现 访问共享目录下其他应用创建的非media文件
* 使用系统存储访问框架
* 需要存储权限
*/
public class SAFUtils {
//通过系统的文件浏览器选择一个文件,注意Intent的action和category都是固定不变的
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
//筛选,只显示可以“打开”的结果,如文件(而不是联系人或时区列表)
intent.addCategory(Intent.CATEGORY_OPENABLE);
//type属性可以用于对文件类型进行过滤,如过滤只显示图像类型文件,设置为"*/*"表示显示所有类型的文件,type属性必须要指定,否则会产生崩溃
intent.setType("image/*");// 文件类型:"text/plain"
startActivityForResult(intent, REQUEST_CODE_FOR_SINGLE_FILE);
private final String[] IMAGE_PROJECTION = {
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.SIZE,
MediaStore.Images.Media._ID};
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
Uri uri = null;
if (resultData != null) {
// 获取选择文件Uri
uri = resultData.getData();
//获取文件流,可以用来读取操作
InputStream inputStream = this.getContentResolver().openInputStream(uri);
// 获取图片信息
Cursor cursor = this.getContentResolver()
.query(uri, IMAGE_PROJECTION, null, null, null, null);
if (cursor != null && cursor.moveToFirst()) {
String displayName = cursor.getString(cursor.getColumnIndexOrThrow(IMAGE_PROJECTION[0]));
String size = cursor.getString(cursor.getColumnIndexOrThrow(IMAGE_PROJECTION[1]));
Log.i(TAG, "Uri: " + uri.toString());
Log.i(TAG, "Name: " + displayName);
Log.i(TAG, "Size: " + size);
}
cursor.close();
}
}
}
}