BitmapFactory无法在Android Nougat拍照后从Uri解码位图

15

我试图拍摄一张照片,然后使用这张照片。以下是我的操作步骤:

我的设备是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 Nougat(7.x.x)之外,各种不同的Android API都可以正常工作。即使提供了“FileProvider”,“requireBitmap”始终返回为“null”。

在阅读日志后,从“BitmapFactory”中抛出了“FileNotFoundException”。就像这样:

BitmapFactory: Unable to decode stream: java.io.FileNotFoundException: /Download/Android/data/{@applicationId}/files/Pictures/JPEG_20170216_1744551601425984925.jpg (No such file or directory)

看起来一切都很清楚,但我仍然不理解。

怎么会这样?明明我创建了一个文件!我该怎么解决呢?有什么想法吗?


读取日志后,BitmapFactory 抛出 FileNotFoundException 异常。是的,但它也会提到使用的路径!为什么你省略了它? decodeFile(mUri.getPath())。请告诉我 mUri.getPath() 的值。所有这些在 6.0 上都能正常工作吗? - greenapps
File.createTempFile。不要创建临时文件,只需创建文件名即可。该文件将由相机应用程序创建。 - greenapps
Uri mPicPathmUri.getPath())?????mUri 是从哪里来的? - greenapps
"/Download/Android/data/{@applicationId}/files/Pictures/........" 这是一个不存在的无意义路径。在使用之前,请检查 storageDic 目录是否存在。完整路径永远不能以 /Download/... 开头。SPApplication.getInstance().getExternalFilesDir() 的实现有误。 - greenapps
@SilentKnight 请尝试使用此链接 https://github.com/raghunandankavi2010/SamplesAndroid/tree/master/StackOverFlowTest。 - Raghunandan
显示剩余3条评论
6个回答

16
我试过了你的代码,这是我的尝试样本。https://github.com/raghunandankavi2010/SamplesAndroid/tree/master/StackOverFlowTest
看看这篇博客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,您已经拥有它。在我的手机上,我得到的URI如下所示。仅在Nexus6p上测试过。

content://com.example.raghu.stackoverflowtest.fileProvider/external_files/Pictures/JPEG_20170424_161429691143693160.jpg

关于文件提供者的更多信息 https://developer.android.com/reference/android/support/v4/content/FileProvider.html


2
是的,终于,只有 BitmapFactory.decodeStream(inputStream) 起作用了。 - SilentKnight
当使用类似 "content://..." 的路径时,我可以使用 Glide 加载所选择的 png 文件,而使用 BitmapFactory.loadFile(uri),则会返回一个文件未找到的异常,提示“令人奇怪的是”,路径未找到只有一个斜杠 "/... content"。为什么 BitmapFactory 会删除一个斜杠,只是为了失败呢?这是个神秘的谜。 - carl

3

尝试使用此函数,也许您可以从中获取路径。这对于我很有效。

@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类。
 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());
}

}


1
提醒一下,您忘记了 HelperFunctions.getDataColumn() 函数的代码。没有它是无法正常工作的。 - PGMacDesign

2

看起来 {@applicationID} 实际上应该包含你的应用程序的包ID。由于文件夹不存在,因此无法写入或读取文件。 看起来 SPApplication.getInstance().getExternalFilesDir(Environment.DIRECTORY_PICTURES); 没有返回有效路径。


2

您应该使用FileProvider

您可以参考这个commit来进行必要的更改。


2

这段代码适用于Nougat及以前的版本

要在Nougat中从数据库获取真实路径,请使用此函数,并将您在onActivityResult中获得的数据字段中的URI传递给它,然后从该路径获取文件。

    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;
}

2

尝试使用Glide.

1. 在 app/build.gradle 中添加 Glide 依赖。

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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接