问题:
Android 9.0手机(小米6)允许了权限
READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE
仍旧不能忘内部存储写入文件(自定义的路径)
Android Q(API 29 10.0)变更了存储权限,提出了“沙盒"的概念,直接来说就是应用专属文件夹,由于是专属,所以在此路径下不需要权限,访问共享目录仍旧需要权限。
针对上述问题,索性按照Android Q来做相应的适配吧。
谷歌推荐应用在沙盒内存储文件:Context.getExternalFilesDir(String type),type可以传null,/storage/emulated/0/Android/data/包名/files/
缓存Context.getExternalCacheDir(),即/storage/emulated/0/Android/data/包名/cache/
比如存储图片
Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
就会存在下面的路径下
/storage/emulated/0/Android/data/包名/files/Pictures
兼容性适配:
targetSdkVersion<=28
这样在申请READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE后会自动转成android.permission.READ_MEDIA_AUDIO,android.permission.READ_MEDIA_IMAGES,android.permission.READ_MEDIA_VIDEO
尝试适配过程遇到的问题:
targetSdkVersion=29时,在10.0模拟器上,
用了框架rxpermissions解决了权限问题,用EasyPermission有点问题(可能使用方式不对吧,因为之前的项目中一直在用它)。
1、
compileSdkVersion 29
targetSdkVersion 29
2、使用Rxpermissions常规申请存储权限
READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE
3、存储路径变更,视需求:
私有/cache,files
共享目录:Download,Picture,Movies等等
有关路径根据实际情况调整,要不要随着应用的卸载而被清空
public static final String PATH = MApplication.getInstance().getExternalFilesDir(null).getParent()+ File.separator ;
//存储到公有图片路径
public static final String IMAGE_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getPath() + File.separator;
//存储到私有图片路径
public static final String PRIVATE_IMAGE_PATH = MApplication.getInstance().getExternalFilesDir(Environment.DIRECTORY_PICTURES).getPath()+ File.separator;
//存储到公有下载路径
public static final String DOWNLOAD_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath()+ File.separator;
public static final String CACHE = MApplication.getInstance().getExternalCacheDir() + File.separator;
public static final String get_devid="get_devid";
public static final String LOG_PATH = PATH+"log"+ File.separator;
public static final String VIDEO_CACHE = CACHE+ "videocache" + File.separator ;
//存储到公有视频路径
public static final String VIDEO = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getPath() + File.separator ;
public static final String nomediaFile = CACHE + ".nomedia";//.nomedia文件作用:应用中的图片不被系统图库扫描 “.nomedia”文件放在任何一个文件夹下都会把该文件夹下所有媒体文件(图片,mp3,视频)隐藏起来不会在系统图库,铃声中出现.
public static final String FILE_START_NAME = "VID_";
public static final String VIDEO_EXTENSION = ".mp4";
//存储到公有音频,音乐路径
public static final String AUDIO_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getPath()+ File.separator;
写文件操作,分Q以及以上和Q以下
Q以下,还是用createNewFile(),再使用文件流操作,Q以及以上使用MediaStore 的API进行文件流操作
try {
if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.P){
File textFile = new File(externalDownloadPath+"test.txt");
textFile.createNewFile();
FileOutputStream fos = new FileOutputStream(textFile);
final String textContent = "这是测试文件内容";
fos.write(textContent.getBytes());
fos.close();
tv_externalPublicDownloadPath.setText("外部文件公有路径:"+textFile.getAbsolutePath());
}else{
String path = FileUtil.insertMediaFileByContent(MediaStore.Downloads.EXTERNAL_CONTENT_URI,this,"text/plain","test","","这是测试文件内容");
String realPath = FileUtil.getPath(this,Uri.parse(path));
tv_externalPublicDownloadPath.setText("外部文件公有路径:"+realPath);
}
} catch (Exception e) {
e.printStackTrace();
}
/**
* 保存多媒体文件到公共集合目录
* 通过文本内容存储
* @param uri:多媒体数据库的Uri
* @param mimeType:需要保存文件的mimeType
* @param displayName:显示的文件名字
* @param description:文件描述信息
* @return 返回插入数据对应的uri
*/
public static String insertMediaFileByContent(Uri uri, Context context, String mimeType, String displayName, String description,String fileContent) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, displayName);
values.put(MediaStore.Images.Media.DESCRIPTION, description);
values.put(MediaStore.Images.Media.MIME_TYPE, mimeType);
Uri url = null;
String stringUrl = null; /* value to be returned */
ContentResolver cr = context.getContentResolver();
try {
url = cr.insert(uri, values);
if (url == null) {
return null;
}
ParcelFileDescriptor parcelFileDescriptor = cr.openFileDescriptor(url, "w");
if(parcelFileDescriptor!=null){
FileOutputStream fileOutputStream = new FileOutputStream(parcelFileDescriptor.getFileDescriptor());
fileOutputStream.write(fileContent.getBytes());
fileOutputStream.flush();
fileOutputStream.close();
}
} catch (Exception e) {
Log.e(TAG, "Failed to insert media file", e);
if (url != null) {
cr.delete(url, null, null);
url = null;
}
}
if (url != null) {
stringUrl = url.toString();
}
return stringUrl;
}
/**
* 保存多媒体文件到公共集合目录
* 通过文件流存储
* @param uri:多媒体数据库的Uri
* @param mimeType:需要保存文件的mimeType
* @param displayName:显示的文件名字
* @param description:文件描述信息
* @return 返回插入数据对应的uri
*/
public static String insertMediaFileByStream(Uri uri, Context context, String mimeType, String displayName, String description,InputStream inputStream) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DISPLAY_NAME, displayName);
values.put(MediaStore.Images.Media.DESCRIPTION, description);
values.put(MediaStore.Images.Media.MIME_TYPE, mimeType);
Uri url = null;
String stringUrl = null; /* value to be returned */
ContentResolver cr = context.getContentResolver();
try {
url = cr.insert(uri, values);
if (url == null) {
return null;
}
byte[] buffer = new byte[1024];
ParcelFileDescriptor parcelFileDescriptor = cr.openFileDescriptor(url, "w");
if(parcelFileDescriptor!=null){
FileOutputStream fileOutputStream = new FileOutputStream(parcelFileDescriptor.getFileDescriptor());
//InputStream inputStream = context.getResources().getAssets().open(saveFileName);
while (true) {
int numRead = inputStream.read(buffer);
if (numRead == -1) {
break;
}
fileOutputStream.write(buffer, 0, numRead);
}
inputStream.close();
fileOutputStream.flush();
fileOutputStream.close();
}
} catch (Exception e) {
Log.e(TAG, "Failed to insert media file", e);
if (url != null) {
cr.delete(url, null, null);
url = null;
}
}
if (url != null) {
stringUrl = url.toString();
}
return stringUrl;
}
唯一标识:
谷歌推荐使用以下方式
String uniqueID = UUID.randomUUID.toString()
由于是random,所以每次启动都不一样,可对首次产生的id进行持久化存储,以后每次启动都使用存储的值。
华为的适配文档上有这样介绍:
开发者可以尝试使用Android ID替换Device ID。
(1)获取代码:Settings.System.getString(context.getContentResolver(),
Settings.Secure.ANDROID_ID); (2)Android ID和Device ID的区别:
(a)手机恢复出厂设置,Android ID可以重置,但是Device ID无法重置; (b)对于安装在运行 Android 8.0
的设备上的应用,ANDROID_ID 的值现在将根据应用签署密钥和用户确定作用域。应用签署密钥、用户和设备的每个组合都具有唯一的
ANDROID_ID 值。因此,在相同设备上运行但具有不同签署密钥的应用将不会再看到相同的 Android
ID(即使对于同一用户来说,也是如此)
更多的适配,可以看华为的文档:华为的文档