非法参数异常:列“_data”不存在。

35

在 Nougat 版本中,此函数不起作用。

String path = getRealPathFromURI(this, getIntent().getParcelableExtra(Intent.EXTRA_STREAM));


public String getRealPathFromURI(Context context, Uri contentUri) {
    Cursor cursor = null;
    try {
        String[] proj = {MediaStore.Images.Media.DATA};
        cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
        if (cursor == null) return contentUri.getPath();
        int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
        cursor.moveToFirst();
        return cursor.getString(column_index);
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
}

崩溃日志:

java.lang.RuntimeException: Unable to start activity ComponentInfo{class path}: java.lang.IllegalArgumentException: column '_data' does not exist
   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2659)
   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2724)
   at android.app.ActivityThread.-wrap12(ActivityThread.java)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1473)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:154)
   at android.app.ActivityThread.main(ActivityThread.java:6123)
   at java.lang.reflect.Method.invoke(Method.java)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)
Caused by java.lang.IllegalArgumentException: column '_data' does not exist
   at android.database.AbstractCursor.getColumnIndexOrThrow(AbstractCursor.java:333)
   at android.database.CursorWrapper.getColumnIndexOrThrow(CursorWrapper.java:87)
   at com.package.SaveImageActivity.getRealPathFromURI()
   at com.package.SaveImageActivity.onCreate()
   at android.app.Activity.performCreate(Activity.java:6672)
   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1140)
   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2612)
   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2724)
   at android.app.ActivityThread.-wrap12(ActivityThread.java)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1473)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:154)
   at android.app.ActivityThread.main(ActivityThread.java:6123)
   at java.lang.reflect.Method.invoke(Method.java)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:867)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:757)

这个函数在Android N之前的设备中能够正常工作。我读了一篇文章file://方案现在不能附加到targetSdkVersion 24(Android Nougat)上的Intent,但是没有找到任何解决方案。请帮忙。


1
为什么你要试图从URI获取文件路径?通常应该尝试将URI作为流来处理,因为并非所有的URI都由具有路径的本地文件支持。如果您想访问该文件以读取exif数据,则可以将其作为流打开,并使用此支持库来读取exif数据。https://android-developers.googleblog.com/2016/12/introducing-the-exifinterface-support-library.html - startoftext
我知道这是一个晚了的请求,但是使用您的方法,有各种设备显示ORIENTATION_UNDEFINED的图像,它们可以是横向或纵向,具体取决于API版本。对于模拟器上的API 26(Android 8),它会旋转90度,而在API 29及以上(Android 9及以上)上,则显示纵向图像,没有任何旋转。您有任何解决方案吗? - Harshit Saxena
5个回答

51

根据CommonsWare给出的答案,解决方案代码如下:

public static String getFilePathFromURI(Context context, Uri contentUri) {
    //copy file and send new file path 
    String fileName = getFileName(contentUri);
    if (!TextUtils.isEmpty(fileName)) {
        File copyFile = new File(TEMP_DIR_PATH + File.separator + fileName);
        copy(context, contentUri, copyFile);
        return copyFile.getAbsolutePath();
    }
    return null;
}

public static String getFileName(Uri uri) {
    if (uri == null) return null;
    String fileName = null;
    String path = uri.getPath();
    int cut = path.lastIndexOf('/');
    if (cut != -1) {
        fileName = path.substring(cut + 1);
    }
    return fileName;
}

public static void copy(Context context, Uri srcUri, File dstFile) {
    try {
        InputStream inputStream = context.getContentResolver().openInputStream(srcUri);
        if (inputStream == null) return;
        OutputStream outputStream = new FileOutputStream(dstFile);
        IOUtils.copyStream(inputStream, outputStream);
        inputStream.close();
        outputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

希望这可以帮助你。

IOUtils.copy的起源是来自此网站:https://www.developerfeed.com/copy-bytes-inputstream-outputstream-android/(可能需要稍微更改一下异常,但是它可以按照所需的方式工作)。


2
TEMP_DIR_PATH是什么?IOUtils又是什么?@vidha - Redturbo
4
对于 TEMP_DIR_PAT,我使用了 Environment.getExternalStorageDirectory().getPath() + "/YourAppName"。 (说明:这是一行代码,用Java语言编写。其中,TEMP_DIR_PAT是一个字符串变量名,表示临时目录的路径;Environment.getExternalStorageDirectory()是Android系统提供的方法,用于获取外部存储器的根目录;getPath()是该方法的一个子方法,用于获取路径字符串;YourAppName是自定义的应用程序名称,用于组成完整的目录路径。将两个字符串拼接在一起,就可以得到一个完整的临时目录路径。) - Manohar
2
看起来这段代码抛出了一个FileNotException异常: https://dev59.com/Aabja4cB1Zd3GeqPmtk-#47350375 - mochadwi
1
@mochadwi,绝对需要存储权限的代码。 - vidha
1
我还实现了清单和运行时权限。这段代码运行得非常顺畅,感谢@vidha。当我创建一个已经存在的文件时,我的代码出现了问题,我直接删除了它而不是将其保留。之后进行copy(in, out)操作时,它自然找不到源文件。 - mochadwi
显示剩余6条评论

24

在 Android N 及之前的设备中,此函数能正常工作。

对于非本地文件,例如那些由 MediaStore 索引的内容,它仅适用于非常少量的 Uri 值,并且可能没有结果。对于可移动存储上的文件等情况,也可能没有可用结果。

所以请帮忙。

使用 ContentResolveropenInputStream() 获取与 Uri 标识的内容关联的 InputStream 。理想情况下,直接使用该流进行需要的操作即可。或者,使用该 InputStream 并在您控制的文件上使用一些 FileOutputStream 来复制内容,然后使用该文件。


如果我想要将文件路径和保存文件路径保存在数据库中怎么办? - vidha
如果我想要从Uri中获取文件并显示图像怎么办? - vidha
1
@vidha:正如我在答案中所写的,您需要将数据复制到自己的文件中,然后进行管理。 - CommonsWare
1
我遇到了同样的问题,但不知道如何解决,请帮忙。 @Vidha你能在这里提供你的代码吗? - Bajrang Hudda
@BajrangHudda 请查看下面的答案。 - vidha
这是一篇很好的博客文章,其中有一个类似于CommonsWare建议方法的示例:https://medium.com/@sriramaripirala/android-10-open-failed-eacces-permission-denied-da8b630a89df - DiscDev

21

//以下代码在Android N中可以正常工作:

private static String getFilePathForN(Uri uri, Context context) {
    Uri returnUri = uri;
    Cursor returnCursor = context.getContentResolver().query(returnUri, null, null, null, null);
    /*
     * Get the column indexes of the data in the Cursor,
     *     * move to the first row in the Cursor, get the data,
     *     * and display it.
     * */
    int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
    int sizeIndex = returnCursor.getColumnIndex(OpenableColumns.SIZE);
    returnCursor.moveToFirst();
    String name = (returnCursor.getString(nameIndex));
    String size = (Long.toString(returnCursor.getLong(sizeIndex)));
    File file = new File(context.getFilesDir(), name);
    try {
        InputStream inputStream = context.getContentResolver().openInputStream(uri);
        FileOutputStream outputStream = new FileOutputStream(file);
        int read = 0;
        int maxBufferSize = 1 * 1024 * 1024;
        int bytesAvailable = inputStream.available();

        //int bufferSize = 1024;
        int bufferSize = Math.min(bytesAvailable, maxBufferSize);

        final byte[] buffers = new byte[bufferSize];
        while ((read = inputStream.read(buffers)) != -1) {
            outputStream.write(buffers, 0, read);
        }
        Log.e("File Size", "Size " + file.length());
        inputStream.close();
        outputStream.close();
        Log.e("File Path", "Path " + file.getPath());
        Log.e("File Size", "Size " + file.length());
    } catch (Exception e) {
        Log.e("Exception", e.getMessage());
    }
    return file.getPath();
}

2
你应该为你的代码添加一份说明,解释它正在做什么以及为什么它有效而相关代码则无效。 - Markoorn
非法参数异常:列“_data”不存在,此问题发生在Android Nougat以上版本中,该方法问题已解决。 - Satyawan Hajare
唯一完美解决方案。已经挣扎了10个小时。 - NS.
如果文件大小很大,那么处理起来需要很长时间。 - Anil Bhomi
非常感谢你,兄弟。我在过去的两天里一直在苦苦挣扎,但是你的帮助解决了我的问题。 - Mohd Sakib Syed

3

@Redturbo 我无法在评论区写下评论,所以我在这里写

IOUtils.copyStream(inputStream, outputStream);

TEMP_DIR_PATH - 任何目录路径,例如:

 File rootDataDir = context.getFilesDir();
 File copyFile = new File( rootDataDir + File.separator + fileName + ".jpg");

如果你可以写评论,你会写什么? - Nico Haase

0

根据Android R存储框架指南,进一步补充Vidha的答案,如果您不想永久保存复制的文件到存储中:

    public static String getFilePathFromURI(Context context, Uri contentUri) {
    File folder;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
        folder = new File (Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS)+ "/"+ "yourAppFolderName" );
    } else {
        folder = new File(Environment.getExternalStorageDirectory() + "/"+ "yourAppFolderName");
    }
    // if you want to save the copied image temporarily for further process use .TEMP folder otherwise use your app folder where you want to save
    String TEMP_DIR_PATH = folder.getAbsolutePath() + "/.TEMP_CAMERA.xxx";
    //copy file and send new file path
    String fileName = getFilename();
    if (!TextUtils.isEmpty(fileName)) {
        File dir = new File(TEMP_DIR_PATH);
        dir.mkdirs();
        File copyFile = new File(dir, fileName);
        copy(context, contentUri, copyFile);
        return copyFile.getAbsolutePath();
    }
    return null;
}

public static void copy(Context context, Uri srcUri, File dstFile) {
    try {
        InputStream inputStream = context.getContentResolver().openInputStream(srcUri);
        if (inputStream == null) return;
        OutputStream outputStream = new FileOutputStream(dstFile);
        IOUtils.copyStream(inputStream, outputStream);
        inputStream.close();
        outputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public static String getFilename() {
    // if file is not to be saved, this format can be used otherwise current fileName from Vidha's answer can be used
    String ts = (new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US)).format(new Date());
    return ".TEMP_" + ts + ".xxx";
}

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