前言
最近一直被Android10相关的适配搞得焦头烂额,之前也听说过android10中的所有的视频,音频以及图片等资源为了统一的管理,统一放在一个共有的文件下,也就是所谓的沙箱。看了一下其他人写的文章,也提供了一种简单粗暴的方式也就是在清单文件application下配置#android:requestLegacyExternalStorage="true"#,但如果android11出来又得适配了,android11是禁止除了共有文件夹下新建视频,音频以及图片等文件。不然就会报找不到文件相关的错误,好了说到底我们还是得花点时间去适配android10以上的沙箱适配。
进入正题,接下来我们就来适配Android10相关的拍照以及选择相册并兼容android10以下的绝大部分的适配。
相册相关的适配
进入系统相册的操作都是一样的这里就不赘述:
private fun openGallery() {
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
startActivityForResult(intent, OPEN_GALLERY_PIC_CODE)
}
主要的还是要对返回的data进行处理,也就是把uri转换成文件File的路径path
适配AndroidQ及以上uri转path
/**
* 适配Android 4.4及以上,根据uri获取图片的绝对路径
*
* @param context 上下文对象
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
@SuppressLint("NewApi")
public static String getRealPathFromUriAboveApiAndroidK(Context context, Uri uri) {
String filePath = null;
if (DocumentsContract.isDocumentUri(context, uri)) {
// 如果是document类型的 uri, 则通过document id来进行处理
String documentId = DocumentsContract.getDocumentId(uri);
if (isMediaDocument(uri)) {
// 使用':'分割
String id = documentId.split(":")[1];
String selection = MediaStore.Images.Media._ID + "=?";
String[] selectionArgs = {id};
filePath = getDataColumn(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs);
} else if (isDownloadsDocument(uri)) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(documentId));
filePath = getDataColumn(context, contentUri, null, null);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
// 如果是 content 类型的 Uri
filePath = getDataColumn(context, uri, null, null);
} else if ("file".equals(uri.getScheme())) {
// 如果是 file 类型的 Uri,直接获取图片对应的路径
filePath = uri.getPath();
}
return filePath;
}
适配android KITKAT及以上uri转path
/**
* 适配Android 4.4及以上,根据uri获取图片的绝对路径
*
* @param context 上下文对象
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
@SuppressLint("NewApi")
public static String getRealPathFromUriAboveApiAndroidK(Context context, Uri uri) {
String filePath = null;
if (DocumentsContract.isDocumentUri(context, uri)) {
// 如果是document类型的 uri, 则通过document id来进行处理
String documentId = DocumentsContract.getDocumentId(uri);
if (isMediaDocument(uri)) {
// 使用':'分割
String id = documentId.split(":")[1];
String selection = MediaStore.Images.Media._ID + "=?";
String[] selectionArgs = {id};
filePath = getDataColumn(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, selection, selectionArgs);
} else if (isDownloadsDocument(uri)) {
Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(documentId));
filePath = getDataColumn(context, contentUri, null, null);
}
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
// 如果是 content 类型的 Uri
filePath = getDataColumn(context, uri, null, null);
} else if ("file".equals(uri.getScheme())) {
// 如果是 file 类型的 Uri,直接获取图片对应的路径
filePath = uri.getPath();
}
return filePath;
}
适配android KITKAT及以下uri转path
/**
* 适配Android 4.4以下(不包括api19),根据uri获取图片的绝对路径
*
* @param context 上下文对象
* @param uri 图片的Uri
* @return 如果Uri对应的图片存在, 那么返回该图片的绝对路径, 否则返回null
*/
public static String getRealPathFromUriBelowApiAndroidK(Context context, Uri uri) {
return getDataColumn(context, uri, null, null);
}
/**
* 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.
*/
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 column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
检查Uri
/**
* @param uri the Uri to check
* @return Whether the Uri authority is MediaProvider
*/
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 DownloadsProvider
*/
private static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
Bitmap转Uri Android10以上适配
/**
* copy或者下载文件到公有目录,保存Bitmap同理,如Download,MIME_TYPE类型可以自行参考对应的文件类型
* @param context context
* @param bitmap 源文件
* @param fileName 保存的文件名
*/
public static Uri copyToDownloadAndroidQ(Context context, Bitmap bitmap, String fileName){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
ContentValues values = new ContentValues();
values.put(MediaStore.Downloads.DISPLAY_NAME, fileName);
values.put(MediaStore.Images.Media.TITLE, fileName);
values.put(MediaStore.MediaColumns.MIME_TYPE, "images/*");
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
ContentResolver resolver = context.getContentResolver();
Uri insertUri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
if(insertUri == null) {
return null;
}
InputStream is = null;
OutputStream os = null;
try {
os = resolver.openOutputStream(insertUri);
if(os == null){
return null;
}
int read;
is = new ByteArrayInputStream(baos.toByteArray());
byte[] buffer = new byte[1024*1024];
while ( (read = is.read(buffer)) != -1){
os.write(buffer,0,read);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
close(is,os);
}
return insertUri;
}
private static void close(InputStream is ,OutputStream os){
if (is != null){
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
图片转换成uri然后转化成文件路径
/**
* 专为Android4.4设计的从Uri获取文件绝对路径
*/
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)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
}
// 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 ("image".equals(type)) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ("video".equals(type)) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ("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 ("content".equalsIgnoreCase(uri.getScheme())) {
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
/**
* 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.
*/
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 column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} finally {
if (cursor != null)
cursor.close();
}
return null;
}
适配Bitmap转uri转换成文件路径最终转换成文件File
public static File getFile(Bitmap bitmap, Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
String fileName = "/DCIM/CATCH/" + System.currentTimeMillis() + ".jpg";
Uri uri = Android10FileUtils.copyToDownloadAndroidQ(context,bitmap,fileName);
String path = FileUtils.getPath(context,uri);
File file = new File(path);
if (!file.exists()){
file.mkdirs();
}
return file;
}else{
Uri uri = Uri.parse(MediaStore.Images.Media.insertImage(context.getContentResolver(), bitmap, null,null));
return FileUtils.uriToFile(uri,context);
}
}
public static File uriToFile(Uri uri, Context context) {
String mediaStoreData = "";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
mediaStoreData = MediaStore.Images.ImageColumns.RELATIVE_PATH;
} else {
mediaStoreData = MediaStore.Images.ImageColumns.DATA;
}
String path = null;
if ("file".equals(uri.getScheme())) {
path = uri.getEncodedPath();
if (path != null) {
path = Uri.decode(path);
ContentResolver cr = context.getContentResolver();
StringBuffer buff = new StringBuffer();
buff.append("(").append(mediaStoreData).append("=").append("'" + path + "'").append(")");
Cursor cur = cr.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[]{MediaStore.Images.ImageColumns._ID,
mediaStoreData
}, buff.toString(), null, null);
int index = 0;
int dataIdx = 0;
for (cur.moveToFirst(); !cur.isAfterLast(); cur.moveToNext()) {
index = cur.getColumnIndex(MediaStore.Images.ImageColumns._ID);
index = cur.getInt(index);
dataIdx = cur.getColumnIndex(mediaStoreData);
path = cur.getString(dataIdx);
}
cur.close();
if (index == 0) {
} else {
Uri u = Uri.parse("content://media/external/images/media/" + index);
System.out.println("temp uri is :" + u);
}
}
if (path != null) {
File file = new File(path);
if (!file.exists()){
file.mkdirs();
}
return file;
}
} else if ("content".equals(uri.getScheme())) {
// 4.2.2以后
String[] proj = {mediaStoreData};
Cursor cursor = context.getContentResolver().query(uri, proj, null, null, null);
if (cursor.moveToFirst()) {
int columnIndex = cursor.getColumnIndexOrThrow(mediaStoreData);
path = cursor.getString(columnIndex);
}
cursor.close();
File file = new File(path);
if (!file.exists()){
file.mkdirs();
}
return file;
} else {
//Log.i(TAG, "Uri Scheme:" + uri.getScheme());
}
return null;
}
当然了拍照和位图Bitmap相关的适配是一样的。
7.0以上Android版本的路径配置
第一步
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="[包名].fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_camera_paths" />
</provider>
@xml/file_camera_paths 文件配置
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!--代表外部存储区域的根目录下的文件 Environment.getExternalStorageDirectory()/DCIM/camerademo目录-->
<external-path name="hm_DCIM" path="." />
<!--代表外部存储区域的根目录下的文件 Environment.getExternalStorageDirectory()/Pictures/camerademo目录-->
<external-path name="hm_Pictures" path="Pictures/camerademo" />
<!--代表app 私有的存储区域 Context.getFilesDir()目录下的images目录 /data/user/0/com.hm.camerademo/files/images-->
<files-path name="hm_private_files" path="images" />
<!--代表app 私有的存储区域 Context.getCacheDir()目录下的images目录 /data/user/0/com.hm.camerademo/cache/images-->
<cache-path name="hm_private_cache" path="images" />
<!--代表app 外部存储区域根目录下的文件 Context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)目录下的Pictures目录-->
<!--/storage/emulated/0/Android/data/com.hm.camerademo/files/Pictures-->
<external-files-path name="hm_external_files" path="Pictures" />
<!--代表app 外部存储区域根目录下的文件 Context.getExternalCacheDir目录下的images目录-->
<!--/storage/emulated/0/Android/data/com.hm.camerademo/cache/images-->
<external-cache-path name="hm_external_cache" path="images" />
<root-path path="" name="camera_photos" />
</paths>
拍照相关的配置
拍照首先需要申请拍照权限
申请完后调用camera就可以拍照,拍照主要就是在调用相机的时需要自己去配置文件存储路径。
private fun openCamera(){
Intent captureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE)
captureIntent.putExtra(MediaStore.EXTRA_OUTPUT, getPhotoUri())
captureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
startActivityForResult(captureIntent, CAMERA_REQUEST_CODE)
}
private fun getPhotoUri():Uri{
return if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.Q){
getUriAboveAndroidQ()
}else{
getUriBelowAndroidQ()
}
}
// 适配android10 创建文件
private fun getUriAboveAndroidQ(): Uri? {
val contentValues = ContentValues()
val fileName = "/IMG_" + System.currentTimeMillis() + ".jpg"
contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, fileName)
contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, "DCIM/Pictures")
contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/JPEG")
return contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
}
//适配Android10版本一下的uri配置
private fun getUriBelowAndroidQ():Uri{
val photoUri = if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.N){
FileProvider.getUriForFile(this, "$packageName.fileprovider", getFilePath())
}else{
Uri.fromFile(getFilePath())
}
return photoUri
}
//向下兼容
private fun getFilePath(): File? {
return File(
checkDirPath(Environment.getExternalStorageDirectory().path + "/DCIM/CATCH/"),
System.currentTimeMillis().toString() + ".jpg"
)
}
/**
* 检查文件是否存在
*/
private fun checkDirPath(dirPath: String?): String? {
if (TextUtils.isEmpty(dirPath)) {
return ""
}
val dir = File(dirPath)
if (!dir.exists()) {
dir.mkdirs()
}
return dirPath
}
回调处理这里我就不赘述了 主要是对Android10以上关于图片配置相关的处理
既然关于Android 10那么我们也讲一下关于定位方面的适配,Android10以上需要在清淡文件中添加一个新的定位权限
<!--定位权限,Android 10 新增后台定位权限-->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q){
RxPermissions(this).request(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_BACKGROUND_LOCATION)...
}else{
RxPermissions(this).request(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION)...
}
网友评论