Выбор изображения из галереи дает ошибку Android N - PullRequest
0 голосов
/ 14 ноября 2018

У меня проблемы с получением Uri из намерения в Android N. Насколько я знаю на Android 24 и выше, чтобы получить внешний Uri, вам нужно FileProvider объявлено в Manifest. Это все сделано и работает с камерой, но когда я пытаюсь получить изображение из галереи, я получаю сообщение об ошибке 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, и это хорошо работает на Зефире, но на телефоне Nougat я получаю эту ошибку:

java.lang.RuntimeException: сбой при доставке результата ResultInfo {who = null, request = 5, result = -1, data = Intent { DAT = содержание: //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 без ошибок?

Ответы [ 2 ]

0 голосов
/ 14 ноября 2018

Я сделал несколько проверок и, как всегда, решение оказалось проще, чем я ожидал. Это моя функция executeCrop. У меня есть два варианта при нажатии на вид. Либо получить изображение из галереи или захватить его с помощью камеры. Я вспомнил, что проблема с опцией галереи возникла только после того, как я исправил проблему с камерой. Таким образом, проблема может быть где-то внутри кода обрезки. Вот что я сделал:

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. До того, как я сделал эти изменения, приложение зависало при выборе изображения из галереи. Я понятия не имею, почему это работает сейчас, но если кто-то знает ответ, я был бы рад услышать это.

0 голосов
/ 14 ноября 2018

Используйте этот код для создания нового намерения выбрать изображение:

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>
...