在 Android Nougat 上拍摄照片后,BitmapFactory 无法从 Uri 解码位图
BitmapFactory can't decode a Bitmap from Uri after photos taken on Android Nougat
我尝试拍照然后使用照片。这是我做的。
我的设备是 Nexus 6P (Android 7.1.1).
首先,我创建了一个 Uri
:
Uri mPicPath = UriUtil.fromFile(this, UriUtil.createTmpFileForPic());
//Uri mPicPath = UriUtil.fromFile(this, UriUtil.createFileForPic());
然后,我开始 Intent
:
Intent intent = ActivityUtils.getTakePicIntent(mPicPath);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, RequestCode.TAKE_PIC);
}
最后,我在 onActivityResult
上处理了这个 Uri
:
if (requestCode == RequestCode.TAKE_PIC) {
if (resultCode == RESULT_OK && mPicPath != null) {
Bitmap requireBitmap = BitmapFactory.decodeFile(mPicPath.getPath());
//path is like this: /Download/Android/data/{@applicationId}/files/Pictures/JPEG_20170216_173121268719051242.jpg
requireBitmap.recycle();//Here NPE was thrown.
}
}
同时,这里是UriUtil
:
public class UriUtil {
public static File createFileForPic() throws IOException {
String fileName = "JPEG_" + new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.getDefault()).format(new Date()) + ".jpg";
File storageDic = SPApplication.getInstance().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
return new File(storageDic, fileName);
}
public static File createTmpFileForPic() throws IOException {
String fileName = "JPEG_" + new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.getDefault()).format(new Date());
File storageDic = SPApplication.getInstance().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
return File.createTempFile(fileName, ".jpg", storageDic);
}
public static Uri fromFile(@NonNull Context context, @NonNull File file) {
if (context == null || file == null) {
throw new RuntimeException("context or file can't be null");
}
if (ActivityUtils.requireSDKInt(Build.VERSION_CODES.N)) {
return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".file_provider", file);
} else {
return Uri.fromFile(file);
}
}
}
和getTakePicIntent(Uri)
:
public static Intent getTakePicIntent(Uri mPicPath) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mPicPath);
if (!ActivityUtils.requireSDKInt(Build.VERSION_CODES.KITKAT_WATCH)) {//in pre-KitKat devices, manually grant uri permission.
List<ResolveInfo> resInfoList = SPApplication.getInstance().getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
SPApplication.getInstance().grantUriPermission(packageName, mPicPath, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
} else {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
return intent;
}
和requireSDKInt
:
public static boolean requireSDKInt(int sdkInt) {
return Build.VERSION.SDK_INT >= sdkInt;
}
除 Android 牛轧糖 (7.x.x) 外,其他 Android API 均有效。即使提供了 'FileProvider','requireBitmap' 也总是返回为 'null'。
读取日志后,FileNotFoundException
从 BitmapFactory
抛出。就像:
BitmapFactory: Unable to decode stream: java.io.FileNotFoundException: /Download/Android/data/{@applicationId}/files/Pictures/JPEG_20170216_1744551601425984925.jpg (No such file or directory)
好像都明白了,但还是没看懂。
怎么可能?明明我创建了一个File
!我该如何解决?有什么想法吗?
看起来很像 {@applicationID} 实际上应该包含您的应用程序的包 ID。实际上,该文件夹不存在,因此无法写入或读取该文件。
看起来像
SPApplication.getInstance().getExternalFilesDir(Environment.DIRECTORY_PICTURES);未返回有效路径。
Nougat 和以前的更新正在使用此代码
要从牛轧糖中的数据库获取真实路径,请使用此函数并将您在数据字段中获取的 uri 传递到 onActivityResult 中,然后从路径中获取文件。
public String getPath(Uri uri) {
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
cursor.moveToFirst();
String document_id = cursor.getString(0);
document_id = document_id.substring(document_id.lastIndexOf(":") + 1);
cursor.close();
cursor = getContentResolver().query(
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null);
cursor.moveToFirst();
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
cursor.close();
return path;
}
试试这个函数,也许你可以从中获取路径。这对我有用
@TargetApi(Build.VERSION_CODES.KITKAT)
public static String getRealPathFromURI_API19(Context context, Uri uri) {
if (HelperFunctions.isExternalStorageDocument(uri)) {
// ExternalStorageProvider
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];
}
} else if (HelperFunctions.isDownloadsDocument(uri)) {
// DownloadsProvider
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"),
Long.valueOf(id));
return HelperFunctions.getDataColumn(context, contentUri, null, null);
} else if (HelperFunctions.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 HelperFunctions.getDataColumn(context, contentUri, selection,
selectionArgs);
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (HelperFunctions.isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return HelperFunctions.getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
这是我上面使用的静态函数 HelperFunction class。
public class HelperFunction{
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public 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 Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri
.getAuthority());
}
}
你应该使用 FileProvider
。
您可以参考此 commit 进行必要的更改。
我试过你的代码。这是我尝试的示例。https://github.com/raghunandankavi2010/SamplesAndroid/tree/master/WhosebugTest。
看看这个博客https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html
在博客 commonsware 中提到你不应该做 new File (mPicPath.getPath())
。
相反,您应该在 onActivityResult
中使用以下内容
try {
InputStream ims = getContentResolver().openInputStream(mPicPath);
// just display image in imageview
imageView.setImageBitmap(BitmapFactory.decodeStream(ims));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
还有 xml
<external-files-path name="external_files" path="path" />
注意:它是您拥有的内容 uri。在我的 phone 上,我得到如下 uri。仅在 Nexus6p 上测试过。
content://com.example.raghu.Whosebugtest.fileProvider/external_files/Pictures/JPEG_20170424_161429691143693160.jpg
有关文件提供程序的更多信息https://developer.android.com/reference/android/support/v4/content/FileProvider.html
尝试Glide。
1. 添加Glide依赖到app/build.gradle
repositories {
mavenCentral() // jcenter() works as well because it pulls from Maven Central
}
dependencies {
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.android.support:support-v4:19.1.0'
}
2. 使用 Glide 加载图片
Glide.with(context).load(new File(uri.getPath())).placeholder(R.drawable.placeholder).into(imageView);
或
Glide.load(new File(uri.getPath())) // Uri of the picture
.transform(new CircleTransform(..))
.into(imageView);
我尝试拍照然后使用照片。这是我做的。
我的设备是 Nexus 6P (Android 7.1.1).
首先,我创建了一个 Uri
:
Uri mPicPath = UriUtil.fromFile(this, UriUtil.createTmpFileForPic());
//Uri mPicPath = UriUtil.fromFile(this, UriUtil.createFileForPic());
然后,我开始 Intent
:
Intent intent = ActivityUtils.getTakePicIntent(mPicPath);
if (intent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(intent, RequestCode.TAKE_PIC);
}
最后,我在 onActivityResult
上处理了这个 Uri
:
if (requestCode == RequestCode.TAKE_PIC) {
if (resultCode == RESULT_OK && mPicPath != null) {
Bitmap requireBitmap = BitmapFactory.decodeFile(mPicPath.getPath());
//path is like this: /Download/Android/data/{@applicationId}/files/Pictures/JPEG_20170216_173121268719051242.jpg
requireBitmap.recycle();//Here NPE was thrown.
}
}
同时,这里是UriUtil
:
public class UriUtil {
public static File createFileForPic() throws IOException {
String fileName = "JPEG_" + new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.getDefault()).format(new Date()) + ".jpg";
File storageDic = SPApplication.getInstance().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
return new File(storageDic, fileName);
}
public static File createTmpFileForPic() throws IOException {
String fileName = "JPEG_" + new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.getDefault()).format(new Date());
File storageDic = SPApplication.getInstance().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
return File.createTempFile(fileName, ".jpg", storageDic);
}
public static Uri fromFile(@NonNull Context context, @NonNull File file) {
if (context == null || file == null) {
throw new RuntimeException("context or file can't be null");
}
if (ActivityUtils.requireSDKInt(Build.VERSION_CODES.N)) {
return FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".file_provider", file);
} else {
return Uri.fromFile(file);
}
}
}
和getTakePicIntent(Uri)
:
public static Intent getTakePicIntent(Uri mPicPath) {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mPicPath);
if (!ActivityUtils.requireSDKInt(Build.VERSION_CODES.KITKAT_WATCH)) {//in pre-KitKat devices, manually grant uri permission.
List<ResolveInfo> resInfoList = SPApplication.getInstance().getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
SPApplication.getInstance().grantUriPermission(packageName, mPicPath, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
} else {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
}
return intent;
}
和requireSDKInt
:
public static boolean requireSDKInt(int sdkInt) {
return Build.VERSION.SDK_INT >= sdkInt;
}
除 Android 牛轧糖 (7.x.x) 外,其他 Android API 均有效。即使提供了 'FileProvider','requireBitmap' 也总是返回为 'null'。
读取日志后,FileNotFoundException
从 BitmapFactory
抛出。就像:
BitmapFactory: Unable to decode stream: java.io.FileNotFoundException: /Download/Android/data/{@applicationId}/files/Pictures/JPEG_20170216_1744551601425984925.jpg (No such file or directory)
好像都明白了,但还是没看懂。
怎么可能?明明我创建了一个File
!我该如何解决?有什么想法吗?
看起来很像 {@applicationID} 实际上应该包含您的应用程序的包 ID。实际上,该文件夹不存在,因此无法写入或读取该文件。 看起来像 SPApplication.getInstance().getExternalFilesDir(Environment.DIRECTORY_PICTURES);未返回有效路径。
Nougat 和以前的更新正在使用此代码
要从牛轧糖中的数据库获取真实路径,请使用此函数并将您在数据字段中获取的 uri 传递到 onActivityResult 中,然后从路径中获取文件。
public String getPath(Uri uri) {
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
cursor.moveToFirst();
String document_id = cursor.getString(0);
document_id = document_id.substring(document_id.lastIndexOf(":") + 1);
cursor.close();
cursor = getContentResolver().query(
android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
null, MediaStore.Images.Media._ID + " = ? ", new String[]{document_id}, null);
cursor.moveToFirst();
String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
cursor.close();
return path;
}
试试这个函数,也许你可以从中获取路径。这对我有用
@TargetApi(Build.VERSION_CODES.KITKAT)
public static String getRealPathFromURI_API19(Context context, Uri uri) {
if (HelperFunctions.isExternalStorageDocument(uri)) {
// ExternalStorageProvider
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];
}
} else if (HelperFunctions.isDownloadsDocument(uri)) {
// DownloadsProvider
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"),
Long.valueOf(id));
return HelperFunctions.getDataColumn(context, contentUri, null, null);
} else if (HelperFunctions.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 HelperFunctions.getDataColumn(context, contentUri, selection,
selectionArgs);
} else if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (HelperFunctions.isGooglePhotosUri(uri))
return uri.getLastPathSegment();
return HelperFunctions.getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}
这是我上面使用的静态函数 HelperFunction class。
public class HelperFunction{
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is ExternalStorageProvider.
*/
public static boolean isExternalStorageDocument(Uri uri) {
return "com.android.externalstorage.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is DownloadsProvider.
*/
public static boolean isDownloadsDocument(Uri uri) {
return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is MediaProvider.
*/
public 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 Google Photos.
*/
public static boolean isGooglePhotosUri(Uri uri) {
return "com.google.android.apps.photos.content".equals(uri
.getAuthority());
}
}
你应该使用 FileProvider
。
您可以参考此 commit 进行必要的更改。
我试过你的代码。这是我尝试的示例。https://github.com/raghunandankavi2010/SamplesAndroid/tree/master/WhosebugTest。
看看这个博客https://commonsware.com/blog/2016/03/15/how-consume-content-uri.html
在博客 commonsware 中提到你不应该做 new File (mPicPath.getPath())
。
相反,您应该在 onActivityResult
try {
InputStream ims = getContentResolver().openInputStream(mPicPath);
// just display image in imageview
imageView.setImageBitmap(BitmapFactory.decodeStream(ims));
} catch (FileNotFoundException e) {
e.printStackTrace();
}
还有 xml
<external-files-path name="external_files" path="path" />
注意:它是您拥有的内容 uri。在我的 phone 上,我得到如下 uri。仅在 Nexus6p 上测试过。
content://com.example.raghu.Whosebugtest.fileProvider/external_files/Pictures/JPEG_20170424_161429691143693160.jpg
有关文件提供程序的更多信息https://developer.android.com/reference/android/support/v4/content/FileProvider.html
尝试Glide。
1. 添加Glide依赖到app/build.gradle
repositories {
mavenCentral() // jcenter() works as well because it pulls from Maven Central
}
dependencies {
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'com.android.support:support-v4:19.1.0'
}
2. 使用 Glide 加载图片
Glide.with(context).load(new File(uri.getPath())).placeholder(R.drawable.placeholder).into(imageView);
或
Glide.load(new File(uri.getPath())) // Uri of the picture
.transform(new CircleTransform(..))
.into(imageView);