Android 9.0以上适配相关总结(存储,唯一标识)

问题:

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(即使对于同一用户来说,也是如此)

更多的适配,可以看华为的文档:华为的文档

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值