前言

安卓文件永久存储分内部存储和外部存储。这里存储指ROM(断电仍记忆)而非运行内存(RAM断电记忆丢失)。

分类

  • 内部存储(internal storage)
  • 外部存储(external storage)
  • external storage
  • SDCard存储

介绍

内部存储

内部存储(app私有空间)不是内存,物理上为焊于手机中的闪存空间一部分(系统特殊位),绝对路径/data/data/app-package-name/。内部存储文件默仅被你应用访问,卸载则内部存储文件被删。内部存储文件设可读,其它app知应用包名可访应用数据;文件私有则知包名亦不可访。内部存储空间是系统本身和系统应用程序主要数据存储地,十分有限,耗尽手机报废。尽量避用内部存储空间。Shared PreferencesSQLite数据库都于内部存储空间。内部存储用Context获取和操作。

该目录自创目录filescachedatabaseslibshared_prefs,这些目录随app存储需求创,无存储需求不创。

外部存储

external storage

焊于手机中的闪存空间一部分,绝对路径/mnt/shell/emulated/0/(不同系统有细微区别,但对调系统提供函数获路径无影响),通该路径可由file:///sdcard/xxx获,文件xxx即储存/mnt/shell/emulated/0/目录。

PC自带硬盘为内部存储,U盘或移动硬盘为外部存储。故容易这样理解安卓手机,认为机身固有存储为内部存储,扩展T卡为外部存储。如16GB版Nexus 4有16G内部存储,普通消费者可这样理解,但编程不能,这16GB仍外部存储。

所有安卓设备都有外部存储和内部存储,两名称源于早期设备,那时设备内部存储确实固定,外部存储确实可像U盘移动。后来很多中高端机都将机身存储扩展到8G+,存储概念分内部internal外部external,事实都在手机内部。故不管安卓手机有可移动SDCard否,总有外部存储和内部存储。关键都通相同api访可移动SDCard或手机自带外部存储。手机连电脑识别部分为外部存储。

SDCard

外部存储空间,自己所插SDCard。该空间不定总存,用前需判挂载否,外插SDCard根目录绝路径/storage/sdcard1/。为简化只分内部存储空间和外部存储空间( 焊于手机内),可只认/data目录存储空间为内部存储空间,/mnt/shell/emulated/0目录空间为外部存储空间。

引申

外部存储分公有私有。Google官方建议app数据应存储于外部存储私有目录app包名下,卸载app后一并删相关数据。直于/storage/sdcard目录创一应用文件夹则删应用时不删该文件夹。

公有

Environment.getExternalStorageDirectory()
外部存储第一层对象
复制代码
Environment.getExternalStoragePublicDirectory(String type)

DIRECTORY_ALARMS // 警报铃声
DIRECTORY_DCIM // 相机拍摄图和视频
DIRECTORY_DOWNLOADS // 下载文件
DIRECTORY_MOVIES // 电影
DIRECTORY_MUSIC // 音乐
DIRECTORY_NOTIFICATIONS // 通知音
DIRECTORY_PICTURES // 下载图
DIRECTORY_PODCASTS // 博客音频文件
DIRECTORY_RINGTONES // 铃声
复制代码

私有

Context.getExternalFilesDir()
/Android/data/应用包名/files/目录(通放一些长时保存数据)
复制代码
Context.getExternalCacheDir()
/Android/data/应用包名/cache/目录(通放临缓数据)
复制代码

示图



目录

外部存储

公有

华为、锤子正常,小米创失败

String fullPath = "Environment.getExternalStorageDirectory().getAbsolutePath()"+ File.separator;
File file = new File(fullPath , fileName);
if (!file.getParentFile().exists()) {
    // 父目录不存则创父目录
    file.getParentFile().mkdirs();
}
if (file.exists()) {
    // 已存删旧文件
    file.delete();
}
file.createNewFile();
复制代码
File file = new File("/sdcard/Dfs/");
if (!file.exists()) {
    file.mkdirs();
}
// 文件夹创成后存数据
File file = new File(path + fileName);
复制代码

私有

华为、锤子、小米都正常

String fullPath = "context.getExternalFilesDir("DFS").getAbsolutePath()"+ File.separator;
File file = new File(fullPath , fileName);
if (!file.getParentFile().exists()) {
    // 父目录不存则创父目录
    file.getParentFile().mkdirs();
}
if (file.exists()) {
    // 已存删旧文件
    file.delete();
}
file.createNewFile();
复制代码

内部存储

华为、锤子、小米都正常

File file = new File("context.getFilesDir().getAbsoluteFile()", fileName);
if (!file.getParentFile().exists()) {
    // 父目录不存则创父目录
    file.getParentFile().mkdirs();
}
if (file.exists()) {
    // 已存删旧文件
    file.delete();
}
file.createNewFile();
复制代码

工具类

StorageHelper

package util;

import android.content.Context;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.support.annotation.RequiresApi;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Created on 2018/6/29.
 *
 * @desc 存储
 */
public class StorageHelper {
    /**
     * SDCard挂载否
     *
     * @return 挂载否
     */
    private static boolean isMounted() {
        // Environment.MEDIA_MOUNTED(SDCard已挂载)
        // Environment.getExternalStorageState()(SDCard挂载状)
        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    }

    /**
     * SDCard路径
     *
     * @return 路径
     */
    private static String getSDCardPath() {
        String path = null;
        if (isMounted()) {
            path = Environment.getExternalStorageDirectory().getAbsolutePath();
        }
        return path;
    }

    /**
     * SDCard大小(单位兆)
     *
     * @return 大小
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
    public static long getSize() {
        if (isMounted()) {
            // statFs算文件系统存储空间大小
            // 参数SDCard根目录
            StatFs stat = new StatFs(getSDCardPath());
            // 块数
            long count = stat.getBlockCountLong();
            // 块大小(单位字节)
            long size = stat.getBlockSizeLong();
            return count * size / 1024 / 1024;
        }
        return 0;
    }

    /**
     * SDCard可用大小
     *
     * @return 可用大小
     */
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
    public static long getAvailableSize() {
        if (isMounted()) {
            StatFs stat = new StatFs(getSDCardPath());
            // 可用块数
            long count = stat.getAvailableBlocksLong();
            long size = stat.getBlockSizeLong();
            return count * size / 1024 / 1024;
        }
        return 0;
    }

    /**
     * 数据存到SDCard
     *
     * @param data     数据
     * @param dir      路径
     * @param fileName 文件名
     * @return 存否
     */
    public static boolean saveDataToSDCard(byte[] data, String dir, String fileName) {
        boolean flag = false;
        if (isMounted()) {
            // 当前文件夹存否(不存则创)
            String path = getSDCardPath() + File.separator + dir;
            File file = new File(path);
            // 文件夹不存
            if (!file.exists()) {
                file.mkdirs();
            }
            // 文件夹创成后存数据
            file = new File(path + File.separator + fileName);
            BufferedOutputStream bos = null;
            try {
                bos = new BufferedOutputStream(new FileOutputStream(file));
                bos.write(data);
                bos.flush();
                flag = true;
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                try {
                    if (bos != null) {
                        bos.close();
                    }
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        return flag;
    }

    /**
     * 读SDCard数据
     *
     * @param dir      路径
     * @param fileName 文件名
     * @return 数据
     */
    public static byte[] getDataFromSDCard(String dir, String fileName) {
        byte[] b = null;
        if (isMounted()) {
            String path = getSDCardPath() + File.separator + dir + File.separator + fileName;
            File file = new File(path);
            if (file.exists()) {
                try {
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));
                    byte[] buf = new byte[1024 * 8];
                    int n;
                    while ((n = bis.read(buf)) != -1) {
                        baos.write(buf, 0, n);
                        baos.flush();
                    }
                    b = baos.toByteArray();
                } catch (FileNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        return b;
    }

    /**
     * 据类型获公有路径
     *
     * @param type 类型
     * @return 公有路径
     */
    private static String getPublicPath(String type) {
        if (isMounted()) {
            return Environment.getExternalStoragePublicDirectory(type).getAbsolutePath();
        }
        return null;
    }

    /**
     * 数据存到公有路径
     *
     * @param data     数据
     * @param type     类型
     * @param fileName 文件名
     * @return 存否
     */
    public static boolean saveDataToPublicPath(byte[] data, String type, String fileName) {
        boolean flag = false;
        if (isMounted()) {
            // 公有路径
            File file = new File(getPublicPath(type));
            if (!file.exists()) {
                file.mkdirs();
            }
            try {
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(file, fileName)));
                bos.write(data);
                bos.flush();
                flag = true;
                bos.close();
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return flag;
    }

    /**
     * 私有文件路径
     *
     * @param context 上下文对象
     * @param dir     目录
     * @return 私有文件路径
     */
    private static String getPrivateFilePath(Context context, String dir) {
        if (isMounted()) {
            return context.getExternalFilesDir(dir).getAbsolutePath();
        }
        return null;
    }

    /**
     * 数据存到私有文件路径
     *
     * @param data     数据
     * @param context  上下文对象
     * @param dir      目录
     * @param fileName 文件名
     * @return 存否
     */
    public static boolean saveDataToPrivateFilePath(byte[] data, Context context, String dir, String fileName) {
        boolean flag = false;
        if (isMounted()) {
            File file = new File(getPrivateFilePath(context, dir));
            if (!file.exists()) {
                file.mkdirs();
            }
            try {
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File(file, fileName)));
                bos.write(data);
                bos.flush();
                flag = true;
                bos.close();
            } catch (FileNotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        return flag;
    }

    /**
     * 数据存到私有缓存路径
     *
     * @param data     数据
     * @param fileName 文件名
     * @param context  上下文对象
     * @return 存否
     */
    public static boolean saveDataToPrivateCachePath(byte[] data, String fileName, Context context) {
        BufferedOutputStream bos = null;
        if (isMounted()) {
            File file = context.getExternalCacheDir();
            try {
                bos = new BufferedOutputStream(new FileOutputStream(new File(file, fileName)));
                bos.write(data);
                bos.flush();
                return true;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    bos.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    /**
     * @param context 上下文对象
     * @param dir     目录
     * @return 文件路径
     */
    public static String getFilePath(Context context, String dir) {
        String directoryPath;
        // SDCard可用否
        if (isMounted()) {
            directoryPath = context.getExternalFilesDir(dir).getAbsolutePath();
            /*directoryPath = context.getExternalCacheDir().getAbsolutePath();*/
        } else {
            // 无内存卡即存机身内存
            directoryPath = context.getFilesDir() + File.separator + dir;
            /*directoryPath = context.getCacheDir() + File.separator + dir;*/
        }
        File file = new File(directoryPath);
        if (!file.exists()) {
            // 文件目录存否
            file.mkdirs();
        }
        return directoryPath;
    }
}
复制代码

FileUtils

package util;

import android.annotation.SuppressLint;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.Log;

import java.io.Closeable;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.FileChannel;
import java.util.Locale;

import value.Magic;

/**
 * Created on 2017/12/6.
 *
 * @desc file manipulation
 */
public class FileUtils {
    /**
     * TAG for log messages.
     */
    private static final String TAG = "FileUtils";

    private FileUtils() {

    }

    /**
     * 据uri获真路径
     *
     * @param context    上下文
     * @param contentUri contentUri
     * @return 据URI真路径
     */
    public static String getRealPathFromUri(Context context, Uri contentUri) {
        Cursor cursor = null;
        try {
            String[] pro = {MediaStore.Images.Media.DATA};
            cursor = context.getContentResolver().query(contentUri, pro, null, null, null);
            int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            cursor.moveToFirst();
            return cursor.getString(columnIndex);
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
    }

    /**
     * 文件存否
     *
     * @param filePath filePath
     * @return 据路径存否
     */
    public static boolean isFileExistByPath(final String filePath) {
        return isFileExist(getFileByPath(filePath));
    }

    /**
     * 文件存否
     *
     * @param file file
     * @return 存否
     */
    private static boolean isFileExist(final File file) {
        return file != null && file.exists();
    }

    /**
     * 据文件路径获文件
     *
     * @param filePath filePath
     * @return file
     */
    private static File getFileByPath(final String filePath) {
        return isSpace(filePath) ? null : new File(filePath);
    }

    private static boolean isSpace(final String s) {
        if (s == null) {
            return true;
        }
        for (int i = 0, len = s.length(); i < len; ++i) {
            if (!Character.isWhitespace(s.charAt(i))) {
                return false;
            }
        }
        return true;
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is ExternalStorageProvider.
     * @author paulburke
     */
    private static boolean isExternalStorageDocument(Uri uri) {
        return "com.android.externalstorage.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is DownloadsProvider.
     * @author paulburke
     */
    private static boolean isDownloadsDocument(Uri uri) {
        return "com.android.providers.downloads.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is MediaProvider.
     * @author paulburke
     */
    private static boolean isMediaDocument(Uri uri) {
        return "com.android.providers.media.documents".equals(uri.getAuthority());
    }

    /**
     * @param uri The Uri to check.
     * @return Whether the Uri authority is Google Photos.
     */
    private static boolean isGooglePhotosUri(Uri uri) {
        return "com.google.android.apps.photos.content".equals(uri.getAuthority());
    }

    /**
     * Get the value of the data column for this Uri.
     * This is useful for MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context       The context.
     * @param uri           The Uri to query.
     * @param selection     (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     * @author paulburke
     */
    private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {column
        };
        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
            if (cursor != null && cursor.moveToFirst()) {
                final int columnIndex = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(columnIndex);
            }
        } catch (IllegalArgumentException ex) {
            Log.i(TAG, String.format(Locale.getDefault(), "getDataColumn: _data - [%s]", ex.getMessage()));
        } finally {
            if (cursor != null) {
                cursor.close();
            }
        }
        return null;
    }

    /**
     * Get a file path from a Uri.
     * This will get the the path for Storage Access Framework Documents, as well as the _data field for the MediaStore and other file-based ContentProviders.
     * Callers should check whether the path is local before assuming it represents a local file.
     * 4.4选本地文件返uri如content://com.android.providers.media.documents/document/video%3A415296转绝对路径storage/emulated/0/DCIM/Camera/VID_20180312_160348.mp4
     *
     * @param context The context.
     * @param uri     The Uri to query.
     * @author paulburke
     */
    @SuppressLint("NewApi")
    public static String getPath(final Context context, final Uri uri) {
        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
        // DocumentProvider
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];
                if (Magic.STRING_PRIMARY.equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }
                // TODO handle non-primary volumes
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {
                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];
                Uri contentUri = null;
                if (Magic.STRING_IMAGE.equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if (Magic.STRING_VIDEO.equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if (Magic.STRING_AUDIO.equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }
                final String selection = "_id=?";
                final String[] selectionArgs = new String[]{split[1]
                };
                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // MediaStore (and general)
        else if (Magic.STRING_CONTENT.equalsIgnoreCase(uri.getScheme())) {
            // Return the remote address
            if (isGooglePhotosUri(uri)) {
                return uri.getLastPathSegment();
            }
            return getDataColumn(context, uri, null, null);
        }
        // File
        else if (Magic.STRING_FILE.equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }
        return null;
    }

    /**
     * Copies one file into the other with the given paths.
     * In the event that the paths are the same, trying to copy one file to the other will cause both files to become null.
     * Simply skipping this step if the paths are identical.
     */
    public static void copyFile(@NonNull String pathFrom, @NonNull String pathTo) throws IOException {
        if (pathFrom.equalsIgnoreCase(pathTo)) {
            return;
        }
        FileChannel outputChannel = null;
        FileChannel inputChannel = null;
        try {
            inputChannel = new FileInputStream(new File(pathFrom)).getChannel();
            outputChannel = new FileOutputStream(new File(pathTo)).getChannel();
            inputChannel.transferTo(0, inputChannel.size(), outputChannel);
            inputChannel.close();
        } finally {
            if (inputChannel != null) {
                inputChannel.close();
            }
            if (outputChannel != null) {
                outputChannel.close();
            }
        }
    }

    /**
     * 删文件或文件夹
     *
     * @param fileName 所删文件名
     * @return 成true败false
     */
    public static boolean delete(String fileName) {
        File file = new File(fileName);
        if (!file.exists()) {
            return false;
        } else {
            if (file.isFile()) {
                return deleteFile(fileName);
            } else {
                return deleteDirectory(fileName);
            }
        }
    }

    /**
     * 删单文件
     *
     * @param fileName 所删文件名
     * @return 成true败false
     */
    private static boolean deleteFile(String fileName) {
        File file = new File(fileName);
        // 路径对应文件存在且是文件则直删
        if (file.exists() && file.isFile()) {
            if (file.delete()) {
                System.out.println("删单文件" + fileName + "成功");
                return true;
            } else {
                System.out.println("删单文件" + fileName + "失败");
                return false;
            }
        } else {
            System.out.println("删单文件失败:" + fileName + "不存在");
            return false;
        }
    }

    /**
     * 删目录及目录下文件
     *
     * @param dir 所删目录文件路径
     * @return 成true败false
     */
    private static boolean deleteDirectory(String dir) {
        // dir不以文件分隔符结尾则自动添文件分隔符
        if (!dir.endsWith(File.separator)) {
            dir = dir + File.separator;
        }
        File dirFile = new File(dir);
        // dir对应文件不存或非目录则退出
        if ((!dirFile.exists()) || (!dirFile.isDirectory())) {
            LogUtils.e("删目录失败:" + dir + "不存在");
            return false;
        }
        boolean flag = true;
        // 删文件夹所有文件(含子目录)
        File[] files = dirFile.listFiles();
        for (File file : files) {
            // 删子文件
            if (file.isFile()) {
                flag = deleteFile(file.getAbsolutePath());
                if (!flag) {
                    break;
                }
            }
            // 删子目录
            else if (file.isDirectory()) {
                flag = deleteDirectory(file.getAbsolutePath());
                if (!flag) {
                    break;
                }
            }
        }
        if (!flag) {
            LogUtils.e("删目录失败");
            return false;
        }
        // 删当前目录
        if (dirFile.delete()) {
            LogUtils.e("删目录" + dir + "成功");
            return true;
        } else {
            return false;
        }
    }

    /**
     * 复制文件至新路径(Android N+)
     *
     * @param context    上下文
     * @param contentUri 内容标识符
     * @return 新路径
     */
    public static String copyFileToNewPathAfterAndroidN(Context context, Uri contentUri) {
        // copy file and send new file path
        String fileName = getFileName(contentUri);
        if (!TextUtils.isEmpty(fileName)) {
            File copyFile = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "video" + File.separator + fileName);
            copy(context, contentUri, copyFile);
            return copyFile.getAbsolutePath();
        }
        return null;
    }

    private static String getFileName(Uri uri) {
        if (uri == null) {
            return null;
        }
        String fileName = null;
        String path = uri.getPath();
        int cut = path.lastIndexOf('/');
        if (cut != -1) {
            fileName = path.substring(cut + 1);
        }
        return fileName;
    }

    private static void copy(Context context, Uri srcUri, File dstFile) {
        try {
            InputStream inputStream = context.getContentResolver().openInputStream(srcUri);
            if (inputStream == null) {
                return;
            }
            OutputStream outputStream = new FileOutputStream(dstFile);
            try {
                IoUtils.copy(inputStream, outputStream);
            } catch (Exception e) {
                e.printStackTrace();
            }
            inputStream.close();
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 路径
     * 7.0uri转图路径遇java.lang.IllegalArgumentException: column '_data' does not exist
     *
     * @param context         上下文
     * @param contentResolver 内容提供者
     * @param uri             标识符
     * @return 路径
     */
    private static String getFromMediaUri(Context context, ContentResolver contentResolver, Uri uri) {
        if (uri == null) {
            return null;
        }
        FileInputStream input = null;
        FileOutputStream output = null;
        try {
            ParcelFileDescriptor pfd = contentResolver.openFileDescriptor(uri, "r");
            if (pfd == null) {
                return null;
            }
            FileDescriptor fd = pfd.getFileDescriptor();
            input = new FileInputStream(fd);
            String tempFilename = getTempFilename(context);
            output = new FileOutputStream(tempFilename);
            int read;
            byte[] bytes = new byte[4096];
            while ((read = input.read(bytes)) != -1) {
                output.write(bytes, 0, read);
            }
            return new File(tempFilename).getAbsolutePath();
        } catch (Exception ignored) {
            ignored.getStackTrace();
        } finally {
            closeSilently(input);
            closeSilently(output);
        }
        return null;
    }

    private static String getTempFilename(Context context) throws IOException {
        File outputDir = context.getCacheDir();
        File outputFile = File.createTempFile("image", "tmp", outputDir);
        return outputFile.getAbsolutePath();
    }

    private static void closeSilently(@Nullable Closeable c) {
        if (c == null) {
            return;
        }
        try {
            c.close();
        } catch (Throwable t) {
            // Do nothing
        }
    }
}
复制代码

辅助

package util;

import android.util.Log;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * Created on 2018/8/16.
 *
 * @desc IoUtils
 */
public class IoUtils {
    private static final int BUFFER_SIZE = 1024 * 2;

    private IoUtils() {
        // Utility class.
    }

    public static int copy(InputStream input, OutputStream output) throws Exception, IOException {
        byte[] buffer = new byte[BUFFER_SIZE];
        BufferedInputStream in = new BufferedInputStream(input, BUFFER_SIZE);
        BufferedOutputStream out = new BufferedOutputStream(output, BUFFER_SIZE);
        int count = 0, n;
        try {
            while ((n = in.read(buffer, 0, BUFFER_SIZE)) != -1) {
                out.write(buffer, 0, n);
                count += n;
            }
            out.flush();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                Log.e(e.getMessage(), String.valueOf(e));
            }
            try {
                in.close();
            } catch (IOException e) {
                Log.e(e.getMessage(), String.valueOf(e));
            }
        }
        return count;
    }
}
复制代码