【参考文档】
谷歌官方预览
PictureSelector 2.0 Android Q 适配之旅
AndroidQ(10)分区存储完美适配
从 Android 10 开始已经无法完全标识一个设备,曾经用 mac 地址、IMEI 等设备信息标识设备的方法,从 Android 10 开始统统失效。而且无论你的 APP 是否是配过 Android 10。
从 Android10 开始普通应用不再允许请求权限 android.permission.READ_PHONE_STATE。而且,无论你的 App 是否适配过 Android Q(既 targetSdkVersion 是否大于等于 29),均无法再获取到设备 IMEI 等设备信息。
受影响的 API
Build.getSerial();
TelephonyManager.getImei();
TelephonyManager.getMeid();
TelephonyManager.getDeviceId();
TelephonyManager.getSubscriberId();
TelephonyManager.getSimSerialNumber();
targetSdkVersion<29 的应用,其在获取设备 ID 时,会直接返回 null
targetSdkVersion>=29 的应用,其在获取设备 ID 时,会直接跑出异常 SecurityException
如果您的 App 希望在 Android 10 以下的设备中仍然获取设备 IMEI 等信息,可按以下方式进行适配:
从 Android10 开始,默认情况下,在搭载 Android 10 或更高版本的设备上,系统会传输随机分配的 MAC 地址。(既从 Android 10 开始,普通应用已经无法获取设备的真正 mac 地址,标识设备已经无法使用 mac 地址)
Google 给出的解决方案是:如果您的应用有 追踪非登录用户重装 的需求,可用 ANDROID_ID 来标识设备。
ANDROID_ID 的生成规则为:签名 + 设备信息 + 设备用户
ANDROID_ID 重置规则:设备恢复出厂设置时,ANDROID_ID 将被重置
String androidId = Settings.Secure.getString(this.getContentResolver(), Settings.Secure.ANDROID_ID);
也就是从 Android 10 开始已经无法完全标识一个设备,曾经用 mac 地址、IMEI 等设备信息标识设备的方法,从 Android 10 开始统统失效。而且无论你的 APP 是否是配过 Android 10。
为了使用户更改的管理 Sdcard 中的文件,解决文件混乱的问题。从 Android 10 开始(API level 29),Android 将对外部存储进行一定的限制。默认情况下,对于外部存储,App 只能通过 Context.getExternalFilesDir () 访问自己的特定文件目录;以及系统特定的文件类型目录(例:照片、屏幕快照、视频 等)。
对于 App 专属 内部存储路径与外部存储路径的访问,将不再需要 READ_EXTERNAL_STORAGE 与 WRITE_EXTERNAL_STORAGE 权限:
内部存储路径 /data/data/<包名>/
外部存储路径 /storage/Android/data/<包名>/
读取其他 APP 创建的共享文件,例:相册、屏幕快照 等,则需要申请 READ_EXTERNAL_STORAGE 权限:
图片:Photos、Screenshots 使用 MediaStore.Images API 访问
视频 使用 MediaStore.Video API
访问
音频 使用 MediaStore.Audio API
访问
读取手机的 Downloads 文件夹,不需要任何权限,需要使用 API Storage Access Framework
主要包括:
在后台运行时访问设备位置信息
Android 10 引入了 ACCESS_BACKGROUND_LOCATION 权限。若应用在后台运行时,访问手机位置,需要动态申请该权限,用户则可以选择拒绝。官方给出的数据,大部分用户对位置信息是比较敏感的。而且大部分用户是不允许应用在后台使用位置信息的。
从后台启动 Activity 的限制
屏幕录制
不需要手动申请权限,但官方 API 内部会向用户弹窗申请权限
摄像头和麦克风
Android 9 摄像头和麦克风 后台权限已经移除了
在访问用户步数或其他物理活动类别是需要获取运行时权限
剪切板隐私限制
从 Android P 开始,除非你的应用是默认输入法,否则它无法访问用户的剪贴板数据;但向剪切板写入数据不影响。
//用于保存拍照图片的uri
private Uri mCameraUri;
// 用于保存图片的文件路径,Android 10以下使用图片路径访问图片
private String mCameraImagePath;
// 是否是Android 10以上手机
private boolean isAndroidQ = Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q;
/**
* 调起相机拍照
*/
private void openCamera() {
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// 判断是否有相机
if (captureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
Uri photoUri = null;
if (isAndroidQ) {
// 适配android 10
photoUri = createImageUri();
} else {
try {
photoFile = createImageFile();
} catch (IOException e) {
e.printStackTrace();
}
if (photoFile != null) {
mCameraImagePath = photoFile.getAbsolutePath();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
//适配Android 7.0文件权限,通过FileProvider创建一个content类型的Uri
photoUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", photoFile);
} else {
photoUri = Uri.fromFile(photoFile);
}
}
}
mCameraUri = photoUri;
if (photoUri != null) {
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
startActivityForResult(captureIntent, CAMERA_REQUEST_CODE);
}
}
}
/**
* 创建图片地址uri,用于保存拍照后的照片 Android 10以后使用这种方法
*/
private Uri createImageUri() {
String status = Environment.getExternalStorageState();
// 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储
if (status.equals(Environment.MEDIA_MOUNTED)) {
return getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new ContentValues());
} else {
return getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, new ContentValues());
}
}
/**
* 创建保存图片的文件
*/
private File createImageFile() throws IOException {
String imageName = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
if (!storageDir.exists()) {
storageDir.mkdir();
}
File tempFile = new File(storageDir, imageName);
if (!Environment.MEDIA_MOUNTED.equals(EnvironmentCompat.getStorageState(tempFile))) {
return null;
}
return tempFile;
公共文件的操作需要用到ContentResolver和Cursor
//将文件保存到公共的媒体文件夹
//这里的filepath不是绝对路径,而是某个媒体文件夹下的子路径,和沙盒子文件夹类似
//这里的filename单纯的指文件名,不包含路径
public void saveSignImage(String filePath,String fileName, Bitmap bitmap) {
try {
//设置保存参数到ContentValues中
ContentValues contentValues = new ContentValues();
//设置文件名
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, fileName);
//兼容Android Q和以下版本
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//android Q中不再使用DATA字段,而用RELATIVE_PATH代替
//RELATIVE_PATH是相对路径不是绝对路径
//DCIM是系统文件夹,关于系统文件夹可以到系统自带的文件管理器中查看,不可以写没存在的名字
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, "DCIM/signImage");
//contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, "Music/signImage");
} else {
contentValues.put(MediaStore.Images.Media.DATA, Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getPath());
}
//设置文件类型
contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/JPEG");
//执行insert操作,向系统文件夹中添加文件
//EXTERNAL_CONTENT_URI代表外部存储器,该值不变
Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
if (uri != null) {
//若生成了uri,则表示该文件添加成功
//使用流将内容写入该uri中即可
OutputStream outputStream = getContentResolver().openOutputStream(uri);
if (outputStream != null) {
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, outputStream);
outputStream.flush();
outputStream.close();
}
}
} catch (Exception e) {
}
}
原文链接:https://blog.csdn.net/yehui928186846/article/details/101706238
//在公共文件夹下查询图片
//这里的filepath在androidQ中表示相对路径
//在androidQ以下是绝对路径
public Bitmap querySignImage(String filePath) {
try {
//兼容androidQ和以下版本
String queryPathKey = android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q ? MediaStore.Images.Media.RELATIVE_PATH : MediaStore.Images.Media.DATA;
//查询的条件语句
String selection = queryPathKey + "=? ";
//查询的sql
//Uri:指向外部存储Uri
//projection:查询那些结果
//selection:查询的where条件
//sortOrder:排序
Cursor cursor = getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
new String[]{MediaStore.Images.Media._ID, queryPathKey, MediaStore.Images.Media.MIME_TYPE, MediaStore.Images.Media.DISPLAY_NAME},
selection,
new String[]{filePath},
null);
//是否查询到了
if (cursor != null && cursor.moveToFirst()) {
//循环取出所有查询到的数据
do {
//一张图片的基本信息
int id = cursor.getInt(cursor.getColumnIndex(MediaStore.Images.Media._ID));//uri的id,用于获取图片
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.RELATIVE_PATH));//图片的相对路径
String type = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.MIME_TYPE));//图片类型
String name = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME));//图片名字
//根据图片id获取uri,这里的操作是拼接uri
Uri uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "" + id);
//官方代码:
Uri contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id);
if (uri != null) {
//通过流转化成bitmap对象
InputStream inputStream = getContentResolver().openInputStream(uri);
return BitmapFactory.decodeStream(inputStream);
}
} while (cursor.moveToNext());
}
if (cursor != null)
cursor.close();
} catch (Exception e) {
}
return null;
}
————————————————
原文链接:https://blog.csdn.net/yehui928186846/article/details/101706238
Method | Result |
---|---|
Environment.getDataDirectory() | /data |
Environment.getDownloadCacheDirectory() | /cache |
Environment.getRootDirectory() | /system |
Method | Result |
---|---|
Environment.getExternalStorageDirectory() | /storage/sdcard0 |
Environment.getExternalStoragePublicDirectory(DIRECTORY_ALARMS) | /storage/sdcard0/Alarms |
Environment.getExternalStoragePublicDirectory(DIRECTORY_DCIM) | /storage/sdcard0/DCIM |
Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS) | /storage/sdcard0/Download |
Environment.getExternalStoragePublicDirectory(DIRECTORY_MOVIES) | /storage/sdcard0/Movies |
Environment.getExternalStoragePublicDirectory(DIRECTORY_MUSIC) | /storage/sdcard0/Music |
Environment.getExternalStoragePublicDirectory(DIRECTORY_NOTIFICATIONS) | /storage/sdcard0/Notifications |
Environment.getExternalStoragePublicDirectory(DIRECTORY_PICTURES) | /storage/sdcard0/Pictures |
Environment.getExternalStoragePublicDirectory(DIRECTORY_PODCASTS) | /storage/sdcard0/Podcasts |
Environment.getExternalStoragePublicDirectory(DIRECTORY_RINGTONES) | /storage/sdcard0/Ringtones |
Method | Result |
---|---|
getCacheDir() | /data/data/package/cache |
getFilesDir() | /data/data/package/files |
getFilesDir().getParent() | /data/data/package |
Method | Result |
---|---|
getExternalCacheDir() | /storage/sdcard0/Android/data/package/cache |
getExternalFilesDir(null) | /storage/sdcard0/Android/data/package/files |
getExternalFilesDir(DIRECTORY_ALARMS) | /storage/sdcard0/Android/data/package/files/Alarms |
getExternalFilesDir(DIRECTORY_DCIM) | /storage/sdcard0/Android/data/package/files/DCIM |
getExternalFilesDir(DIRECTORY_DOWNLOADS) | /storage/sdcard0/Android/data/package/files/Download |
getExternalFilesDir(DIRECTORY_MOVIES) | /storage/sdcard0/Android/data/package/files/Movies |
getExternalFilesDir(DIRECTORY_MUSIC) | /storage/sdcard0/Android/data/package/files/Music |
getExternalFilesDir(DIRECTORY_NOTIFICATIONS) | /storage/sdcard0/Android/data/package/files/Notifications |
getExternalFilesDir(DIRECTORY_PICTURES) | /storage/sdcard0/Android/data/package/files/Pictures |
getExternalFilesDir(DIRECTORY_PODCASTS) | /storage/sdcard0/Android/data/package/files/Podcasts |
getExternalFilesDir(DIRECTORY_RINGTONES) | /storage/sdcard0/Android/data/package/files/Ringtones |
Android Q不再需要申请文件读写权限,默认可以读写自己沙盒文件和公共媒体文件。所以,Q以上不需要再动态申请文件读写权限。
/废弃方法
//不再用以下代码获取文件根目录了
Environment.getExternalStorageDirectory();
Environment.getExternalStoragePublicDirectory();
//获取沙盒下的文件目录
//沙盒下的图片文件夹
File filePictures = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
package android.os;
public class Environment {
private static final String TAG = "Environment";
public static String DIRECTORY_NOTIFICATIONS = "Notifications";
public static String DIRECTORY_PICTURES = "Pictures";
public static String DIRECTORY_MOVIES = "Movies";
public static String DIRECTORY_DOWNLOADS = "Download";
...
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
String imageFileName = "JPEG_" + sdf.format(new Date()) + ".jpg";
File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.Q){
directory=getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
}
File storageDir = new File(directory, "compress");