从图库中选择图像会出现错误 Android N

Posted

技术标签:

【中文标题】从图库中选择图像会出现错误 Android N【英文标题】:Picking an image from gallery gives error Android N 【发布时间】:2019-04-17 08:55:12 【问题描述】:

我在从 android N 的意图中检索 Uri 时遇到问题。 据我所知,在 Android 24 及更高版本上要获得外部 Uri,您需要在 Manifest 中声明 FileProvider。一切都完成了,它可以与相机一起使用,但是当我尝试从图库中获取图像时,onActivityResult data.getData(); 出现错误@

这些是我的一些代码示例:

public void getPictureFromGallery()
    picUriCar = null;
    try 
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        startActivityForResult(intent, PIC_SELECT);
     catch (ActivityNotFoundException e) 
        Toast.makeText(MainActivity.this, getResources().getString(R.string.text_error_no_gallery_app),
                Toast.LENGTH_SHORT).show();
    

还有onActivityResult

else if(requestCode == PIC_SELECT)
     picUriCar = data.getData();
     if (picUriCar != null)
         performCrop();
     

据我所知data.getData() 返回一个 Uri,这在 Marshmallow 上运行正常,但在 Nougat 手机上我收到此错误:

java.lang.RuntimeException: 传递结果失败 ResultInfowho=null, request=5, result=-1, data=Intent dat=content://com.android.externalstorage.documents/document/4996-1EFF:DCIM/100ANDRO/DSC_0004.JPG flg=0x1 到活动 com.company.example/com.company.example.MainActivity: java.lang.SecurityException: Uid 10246 没有 uri 的权限 0@ 内容://com.android.externalstorage.documents/document/4996-1EFF%3ADCIM%2F100ANDRO%2FDSC_0004.JPG 在 android.app.ActivityThread.deliverResults(ActivityThread.java:4267) 在 android.app.ActivityThread.handleSendResult(ActivityThread.java:4310) 在 android.app.ActivityThread.-wrap20(ActivityThread.java) 在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1628) 在 android.os.Handler.dispatchMessage(Handler.java:110) 在 android.os.Looper.loop(Looper.java:203) 在 android.app.ActivityThread.main(ActivityThread.java:6361) 在 java.lang.reflect.Method.invoke(本机方法) 在 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924) 引起:java.lang.SecurityException: Uid 10246 没有权限访问uri 0 @ 内容://com.android.externalstorage.documents/document/4996-1EFF%3ADCIM%2F100ANDRO%2FDSC_0004.JPG 在 android.os.Parcel.readException(Parcel.java:1683) 在 android.os.Parcel.readException(Parcel.java:1636) 在 android.app.ActivityManagerProxy.startActivity(ActivityManagerNative.java:3213) 在 android.app.Instrumentation.execStartActivity(Instrumentation.java:1525) 在 android.app.Activity.startActivityForResult(Activity.java:4235) 在 android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:767) 在 android.app.Activity.startActivityForResult(Activity.java:4194) 在 android.support.v4.app.FragmentActivity.startActivityForResult(FragmentActivity.java:754) 在 com.company.example.MainActivity.performCrop(MainActivity.java:1654) 在 com.company.example.MainActivity.onActivityResult(MainActivity.java:1534) 在 android.app.Activity.dispatchActivityResult(Activity.java:6928) 在 android.app.ActivityThread.deliverResults(ActivityThread.java:4263) 在 android.app.ActivityThread.handleSendResult(ActivityThread.java:4310) 在 android.app.ActivityThread.-wrap20(ActivityThread.java) 在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1628) 在 android.os.Handler.dispatchMessage(Handler.java:110) 在 android.os.Looper.loop(Looper.java:203) 在 android.app.ActivityThread.main(ActivityThread.java:6361) 在 java.lang.reflect.Method.invoke(本机方法) 在 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1063) 在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:924)

我的问题是: 如何将data.getData() uri 传递给 picUriCar 而不会出现任何错误?

【问题讨论】:

【参考方案1】:

使用此代码创建选择图像的新意图:

Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
        intent.setType(image/*);
        startActivityForResult(Intent.createChooser(intent, ""), Constants.SELECT_PICTURE);

onActivityResult中使用此代码:

if (resultCode == Activity.RESULT_OK) 
        if (requestCode == Constants.SELECT_PICTURE) 
            Uri selectedImageUri = data.getData();
            try 
                if (isNewGooglePhotosUri(selectedImageUri)) 
                    resultFile = getPhotoFile(selectedImageUri);
                 else 
                    resultFile = getFilePathForGallery(selectedImageUri);
                
                if (resultFile == null) 
                    //error
                    return;
                
             catch (Exception e) 
                e.printStackTrace();
                //error
                return;
            
        
    

这里还有一些我在代码中使用的有用函数:

private File getFilePathForGallery(Uri contentUri) 
        String path = null;
        String[] proj = MediaStore.Images.Media.DATA;
        Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null);
        if (cursor.moveToFirst()) 
            int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
            path = cursor.getString(column_index);
        
        cursor.close();
        return new File(path);
    

  public static boolean isNewGooglePhotosUri(Uri uri) 
        return "com.google.android.apps.photos.contentprovider".equals(uri.getAuthority());
    

private File getPhotoFile(Uri selectedImageUri) 
        try 
            InputStream is = mActivityInstance.getContentResolver().openInputStream(selectedImageUri);
            if (is != null) 
                Bitmap pictureBitmap = BitmapFactory.decodeStream(is);
                ByteArrayOutputStream bytes = new ByteArrayOutputStream();
                pictureBitmap.compress(Bitmap.CompressFormat.JPEG, 80, bytes);
                File output = new File(FileManager.getImageCacheDir(mActivityInstance), System.currentTimeMillis() + ".jpg");
                output.createNewFile();
                FileOutputStream fo = new FileOutputStream(output);
                fo.write(bytes.toByteArray());
                fo.close();
                return output;
            
         catch (Exception e) 
            e.printStackTrace();
        
        return null;
    

FileManager 类的函数:

private static File getCacheDir(Context context) 
        File cacheDir = context.getExternalFilesDir(null);
        if (cacheDir != null) 
            if (!cacheDir.exists())
                cacheDir.mkdirs();
         else 
            cacheDir = context.getCacheDir();
        
        return cacheDir;
    

     public static File getImageCacheDir(Context context) 
        File imageCacheDir = new File(getCacheDir(context), "cache_folder");
        if (!imageCacheDir.exists())
            imageCacheDir.mkdirs();
        return imageCacheDir;
    

您还需要在您的 xml 文件夹中创建新的 xml 文件:

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path
        name="external_files"
        path="." />
</paths>

然后将新的provider 添加到清单文件中:

 <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="$applicationId.provider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/your_xml_file" />
        </provider>

【讨论】:

感谢您的回答。但我无法导入 FileManager。 FileManager 是来自特定库还是什么? @ТеодорМитев 我添加了 FileManager 类的函数来回答 好吧,我实现了代码,没有语法错误,但它似乎仍然不起作用。 picUriCar = FileProvider.getUriForFile(this, authority, resultFile); 这会抛出一个异常,上面写着:Java.lang.IllegalArgumentException: Failed to find configured root that contains /mnt/media_rw/4996-1EFF/DCIM/100ANDRO/DSC_0004.JPG 2018-11-14 16:47:44.151 24744-24744/com.company.example W/example : type=1400 audit(0.0:8811): avc: denied getattr for path="/mnt/media_rw" dev="tmpfs" ino=8056 scontext=u:r:untrusted_app:s0:c5 @ТеодорМитев 检查清单中的提供商。是否正确实施。我也不在我的代码中使用 FileProvider.getUriForFile 吗?你从哪里得到的? @ТеодорМитев 或提供堆栈跟踪和崩溃的代码【参考方案2】:

我做了一些检查,并且一如既往的解决方案比我预期的要简单。这是我的 performCrop 函数。单击视图时,我有两个选项。从图库中获取图像或使用相机捕获图像。我记得只有在我修复了相机问题后才会出现画廊选项问题。所以问题可能在裁剪代码中的某个地方。所以这就是我所做的:

public void performCrop(boolean cameraShot)
            try 
                Intent cropIntent = new Intent("com.android.camera.action.CROP");
                //indicate image type and Uri
                //cropIntent.setDataAndType(picUri, "image");
                cropIntent.setDataAndType(picUriCar, "image/*");
                //set crop properties
                cropIntent.putExtra("crop", "true");
                //indicate aspect of desired crop
                cropIntent.putExtra("aspectX", 2);
                cropIntent.putExtra("aspectY", 1);
                //indicate output X and Y
                cropIntent.putExtra("outputX", 1024);
                cropIntent.putExtra("outputY", 512);
                cropIntent.putExtra("scale", true);
                cropIntent.putExtra("return-data", false);

                Uri uri;
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || !cameraShot) 
                    uri = Uri.fromFile(croppedFileCar);
                else
                    String authority = "com.company.example.provider";
                    uri = FileProvider.getUriForFile(
                            MainActivity.this,
                            authority,
                            croppedFileCar);
                    cropIntent.setClipData(ClipData.newRawUri( "", uri ) );
                    cropIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                

请注意 - if (Build.VERSION.SDK_INT

所以基本上它背后的逻辑是,如果图片不是相机提供的(在onActivityResult中确定)或者API低于24,执行该行:

uri = Uri.fromFile(croppedFileCar);

否则使用 FileProvider。在我进行这些更改之前,应用程序在从图库中选择图像时崩溃了。我不知道为什么它现在有效,但如果有人知道答案,我会很高兴听到它。

【讨论】:

以上是关于从图库中选择图像会出现错误 Android N的主要内容,如果未能解决你的问题,请参考以下文章

Android从图库中获取图像会旋转

当我从图库中选择图像时应用程序崩溃

从图库中选择图像后出现空白屏幕

Android:如何在设置图像视图时检测从图库中选择的图像方向(纵向或横向)?

android从图库中裁剪图像nullpointerexception

android从图库中选择图像