如何在Android Q中从contentResolver.openFileDescriptor(Uri uri, String s)获取媒体项的真实路径?

12

在API level Q中,MediaStore.MediaColumns.DATA常量被弃用。

应用程序可能没有文件系统权限直接访问此路径。代替直接尝试打开此路径,应用程序应使用ContentResolver#openFileDescriptor(Uri, String)获取访问权限。对于针对Build.VERSION_CODES.Q或更高版本的应用程序,此值将始终为NULL。


1
谁也不知道这个问题的解决方案??? - Paranoid
我对你的问题感到困惑。根据我的理解,我编辑了这个问题,但是我发现你拒绝了那个编辑。你能否解释一下你所问的问题与我在编辑中提出的问题有何不同? - Jacob O'Bryant
4
问题在于如何获取位于我们的Android设备内部的文件的真实路径,而不是内容URI,这是在Android 29中的一个难题。 - Paranoid
有没有解决方案? - Himanshu
3个回答

2

事实上,我们无法获取真正的路径。只需要使用contentResolver()打开流并将文件的整个内容复制到新文件中即可。要获取文件信息,我们需要调用getContentResolver().query()方法,然后就可以获得文件的DISPLAY_NAME和一些其他信息,例如FILE_SIZE。

以下是对于那些赞同此问题的人的简单代码示例:

public class MainFragment extends Fragment {

    private Button openGallery;
    private File selectedFile;
    private Context context;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        context = container.getContext();
        return inflater.inflate(R.layout.fragment_question, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);

        openGallery = view.findViewById(R.id.openGallery);
        openGallery.setOnClickListener(v->browseFile());
    }


    private void browseFile() {

        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
            openFiles();
        } else {
            requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 786);
        }
    }

    private void openFiles() {

        deleteFileFromCacheDir();

        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("*/*");
        intent.addCategory(Intent.CATEGORY_OPENABLE);

        if (intent.resolveActivity(context.getPackageManager()) != null) {
            startActivityForResult(intent, 786);
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (requestCode == 786 && resultCode == -1 && data != null) {

            Uri uri = data.getData();
            openStreamAndCopyContent(uri); // Here i am just copy the content of file and paste it into my new file. You can check the type of the file image/video/audio & do it whatever you want

            // Now here is your file

            if (selectedFile != null && selectedFile.exists()){
                // Do it whatever you want Or send it to server 
            } 

        }

    }

    private void openStreamAndCopyContent(Uri uri) {

        try {

            String fileName = "temp" + System.currentTimeMillis() + "." + MimeTypeMap.getSingleton().getExtensionFromMimeType(context.getContentResolver().getType(uri));
            selectedFile = new File(context.getCacheDir().getAbsolutePath() + File.separator + fileName);

            InputStream inputStream = context.getContentResolver().openInputStream(uri);

            if (inputStream != null) {
                Utility.copy(inputStream, selectedFile);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == 786) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                openFiles();
            } else if (grantResults[0] == PackageManager.PERMISSION_DENIED && getActivity() != null) {

                if (!ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE)) {

                    new AlertDialog.Builder(context).setTitle(R.string.permission_required).setMessage(R.string.permission_message)
                            .setPositiveButton(R.string.open_settings, (dialog, which) ->
                                    context.startActivity(new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                                            Uri.parse("package:" + BuildConfig.APPLICATION_ID)))).setNegativeButton(R.string.close, null).show();
                }
            }
        }
    }


    private void deleteFileFromCacheDir() {

        if (selectedFile != null && selectedFile.exists()) {
            if (selectedFile.delete()) {
                selectedFile = null;
            }
        }
    }

    @Override
    public void onDestroyView() {

        deleteFileFromCacheDir();

        super.onDestroyView();
    }



    }

能否获取原始文件的名称? - Sudhir Singh Khanger
1
@SudhirKhanger,一旦你在onActivityResult()方法中获取了URI,就很简单了。只需调用getContext().getContentResolver().query()方法,在查询方法中传递Uri和列数组,在你的情况下只需传递DISPLAY_NAME列即可获取该文件的名称。有关编码示例,请查看如何在Google上使用查询方法。 - Paranoid
请问您能否发布您所使用的头文件? - Amanpreet Kaur
@AmanpreetKaur 你想要检查什么?我现在没有这段代码。告诉我你想要检查什么,我会尽力帮助你。 - Paranoid
@Paranoid 我已经解决了这个问题。谢谢你的回复。如果有人需要,我一定会在这里发帖。 - Amanpreet Kaur

1
我认为这是不可能的,从编程角度来看,你根本不需要那条路径。
你无法使用真实路径读取/更改/删除文件。如你的问题所述,应该使用ContentResolver进行这些操作。
如果你只想向用户展示路径,则建议提供一个选项来使用Intent.createChooser打开/共享文件,或者仅显示MediaColumns.RELATIVE_PATH

0
在我的情况下,我使用了cacheDir
val fileDescriptorPath = fileDescriptor(myUri)
//use path and then delete it
fileDescriptorPath?.delete()

private fun fileDescriptor(uri: Uri?): File? {
    if (uri == null) return null
    var input: FileInputStream? = null
    var output: FileOutputStream? = null
    val filePath: String = File(context.cacheDir, "tmp.fileExtension").absolutePath
   return try {
       val parcelFileDescriptor: ParcelFileDescriptor = context.contentResolver.openFileDescriptor(uri, "r")!!
       val fileDescriptor = parcelFileDescriptor.fileDescriptor
        input = FileInputStream(fileDescriptor)
        output = FileOutputStream(filePath)
        var read: Int
        val bytes = ByteArray(1024)
        while (input.read(bytes).also { read = it } != -1) {
            output.write(bytes, 0, read)
        }
        parcelFileDescriptor.close()
        File(filePath)
    } catch (ignored: IOException) {
        Log.v("pathFileDescriptor", "IOException: ${ignored.message}")
       null
    } finally {
        input?.close()
        output?.close()
    }
}

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