Android10.0 临时解决方案

如果适配兼容10.0的文件存储比较麻烦,可以采用临时方案:

<manifest ... >
  <!-- This attribute is "false" by default on apps targeting
       Android 10 or higher. -->
    <application android:requestLegacyExternalStorage="true" ... >
      ...
    </application>
</manifest>

自定义图片保存位置

Android10.0对文件存储方式改了,但是依然可以自定义存储位置,不过这个位置也是指定的文件夹下面自己生成新的文件夹以下是具体代码示例:

String status = Environment.getExternalStorageState();
        // 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储
        ContentValues contentValues = new ContentValues();
        contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
        contentValues.put(MediaStore.Images.Media.RELATIVE_PATH,"Pictures/img");
        if (status.equals(Environment.MEDIA_MOUNTED)) {
           return getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,contentValues );
        } else {
            return getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, contentValues);
        }

在Android10.0上面使用MediaStore.Images.Media.RELATIVE_PATH来自定义位置(10.0之下还是要旧方式即可),后面是文件的路径,目录结构是以Pictures或者DCIM为开始,意思为只能创建在这两个目录下面,这两个是约定的公共目录(所以当保存其他类型的文件时候,这个目录可能就不是这个了)。当这个不在这两个目录下面时候,会出现类似与以下的错误:

Process: com.donkingliang.photograph, PID: 18751
    java.lang.IllegalArgumentException: Primary directory dq not allowed for content://media/external/images/media; allowed directories are [DCIM, Pictures]
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:170)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:140)
        at android.content.ContentProviderProxy.insert(ContentProviderNative.java:481)
        at android.content.ContentResolver.insert(ContentResolver.java:1844)
        at com.donkingliang.photograph.MainActivity.createImageUri(MainActivity.java:181)
        at com.donkingliang.photograph.MainActivity.openCamera(MainActivity.java:140)
        at com.donkingliang.photograph.MainActivity.checkPermissionAndCamera(MainActivity.java:81)
        at com.donkingliang.photograph.MainActivity.access$000(MainActivity.java:32)
        at com.donkingliang.photograph.MainActivity$1.onClick(MainActivity.java:67)
        at android.view.View.performClick(View.java:7161)
        at android.view.View.performClickInternal(View.java:7138)
        at android.view.View.access$3500(View.java:811)
        at android.view.View$PerformClick.run(View.java:27419)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:224)
        at android.app.ActivityThread.main(ActivityThread.java:7520)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

Android中公共文件夹

由于Android10.0文件存储改版后,文件要存放到指定的位置。位置分为私有位置和共享位置。假如要存放到共享位置的外置存储空间时候。存放位置是要放在规定的位置的,这些空间主要有以下几种:

The system automatically scans an external storage volume and adds media files to the following well-defined collections:
Images, including photographs and screenshots, which are stored in the DCIM/ and Pictures/ directories. The system adds these files to the MediaStore.Images table.
Videos, which are stored in the DCIM/, Movies/, and Pictures/ directories. The system adds these files to the MediaStore.Video table.
Audio files, which are stored in the Alarms/, Audiobooks/, Music/, Notifications/, Podcasts/, and Ringtones/ directories, as well as audio playlists that are in the Music/ or Movies/ directories. The system adds these files to the MediaStore.Audio table.
Downloaded files, which are stored in the Download/ directory. On devices that run Android 10 (API level 29) and higher, these files are stored in the MediaStore.Downloads table. This table isn’t available on Android 9 (API level 28) and lower.
The media store also includes a collection called MediaStore.Files. Its contents depend on whether your app uses scoped storage, available on apps that target Android 10 or higher:
If scoped storage is enabled, the collection shows only the photos, videos, and audio files that your app has created.
If scoped storage is unavailable or not being used, the collection shows all types of media files.

通过Uri获取文件名字

以前是通过File获取文件名字,改版后没有办法直接获取名字,通过Uri转文件再获取也比较麻烦,这里提供一个简单的方式

build.gradle:
 implementation 'androidx.documentfile:documentfile:1.0.1'

Mantivity.java:

 DocumentFile documentFile = DocumentFile.fromSingleUri(getBaseContext(), uri);
String fileName = documentFile .getName();
 Log.e("Y","文件名字:"+fileName);

通过Uri判断文件是否存在

以前是通过本地路径获取FIle后然后判断文件是否存在,但是Uri转换成File再进行判断会十分麻烦,因此这里还是使用Uri进行判断。什么时候会用到这个方式呢,比如缓存文件的判断,我们不能把Uri存储到本地,只能通过Uri转换成String存储到本地,然后使用的时候再转换为Uri进行读取。

build.gradle:
 implementation 'androidx.documentfile:documentfile:1.0.1'

Mantivity.java:

 DocumentFile documentFile = DocumentFile.fromSingleUri(getBaseContext(), uri);
 boolean exists = documentFile .exists();
 Log.e("Y","文件是否存在:"+exists);

Uri和String转换

Uri uri;
String localPath;
//localPath = uri.getPath();//这个不好用
 localPath = uri.toString();
uri = Uri.parser(localPath);

Uri转InputStream

Uri uri;
InputStream inputStream = getContext().getContentResolver().openInputStream(uri);

权限相关

Android10开始使用了分区存储,在这时候使用ContentProvider读取MediaStore相关信息时候,不用申请存储权限也可以,大概率也不会崩溃,但是10.0之下的系统就会崩溃,因为没有存储权限的问题,相关链接参考:
访问共享存储空间中的媒体文件 这里引用相关文档内容:

存储权限
如果您仅访问自己的媒体文件,则不需要任何权限
在搭载 Android 10 或更高版本的设备上,您无需任何存储相关权限即可访问和修改您的应用拥有的媒体文件,包括 MediaStore.Downloads 集合中的文件。例如,如果您正在开发一款相机应用,则无需请求存储相关权限,因为您的应用拥有您将写入媒体库的图片。
访问其他应用的媒体文件
如需访问其他应用创建的媒体文件,您必须声明适当的存储相关权限,并且文件必须位于以下任一媒体集合中:
MediaStore.Images
MediaStore.Video
MediaStore.Audio
只要文件可通过 MediaStore.Images、MediaStore.Video 或 MediaStore.Audio 查询进行查看,则表示该文件也可以通过 MediaStore.Files 查询进行查看。
以下代码段演示了如何声明适当的存储权限:

<!-- Required only if your app needs to access images or photos
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<!-- Required only if your app needs to access videos
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />

<!-- Required only if your app needs to access audio files
     that other apps created. -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 android:maxSdkVersion="29" />

注意:如果您同时请求 READ_MEDIA_IMAGES 权限和 READ_MEDIA_VIDEO
权限,系统会显示单个提及这两项权限的运行时权限对话框。 在旧版设备上运行的应用需要额外权限 如果您的应用在搭载 Android 9
或更低版本的设备上使用,或者您的应用暂时停用分区存储,您必须请求 READ_EXTERNAL_STORAGE
权限才能访问任何媒体文件。如果要修改媒体文件,您还必须请求 WRITE_EXTERNAL_STORAGE 权限。

访问其他应用的下载内容时需要使用存储访问框架 如果应用想要访问 MediaStore.Downloads
集合中某个并非由其创建的文件,您必须使用存储访问框架。如需详细了解如何使用此框架,请参阅有关如何访问文档和其他文件的指南。

媒体位置信息权限 如果您的应用以 Android 10(API 级别 29)或更高版本为目标平台,为了使您的应用从照片中检索未编辑的
Exif 元数据,您需要在应用的清单中声明 ACCESS_MEDIA_LOCATION 权限,然后在运行时请求此权限。