AndroidQ 版本适配方案整理

一、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();
            }
        }
    }
}

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值