Android Gallery on Android 4.4 (KitKat) returns different URI for Intent.ACTION_GET_CONTENT

后端 未结 19 2262
温柔的废话
温柔的废话 2020-11-22 02:44

Before KitKat (or before the new Gallery) the Intent.ACTION_GET_CONTENT returned a URI like this

content://media/external/images/media/39

相关标签:
19条回答
  • 2020-11-22 03:06

    Try this:

    if (Build.VERSION.SDK_INT <19){
        Intent intent = new Intent(); 
        intent.setType("image/jpeg");
        intent.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(Intent.createChooser(intent, getResources().getString(R.string.select_picture)),GALLERY_INTENT_CALLED);
    } else {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("image/jpeg");
        startActivityForResult(intent, GALLERY_KITKAT_INTENT_CALLED);
    }
    
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode != Activity.RESULT_OK) return;
        if (null == data) return;
        Uri originalUri = null;
        if (requestCode == GALLERY_INTENT_CALLED) {
            originalUri = data.getData();
        } else if (requestCode == GALLERY_KITKAT_INTENT_CALLED) {
            originalUri = data.getData();
            final int takeFlags = data.getFlags()
                    & (Intent.FLAG_GRANT_READ_URI_PERMISSION
                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            // Check for the freshest data.
            getContentResolver().takePersistableUriPermission(originalUri, takeFlags);
        }
    
        loadSomeStreamAsynkTask(originalUri);
    
    }
    

    Probably need

    @SuppressLint("NewApi")

    for

    takePersistableUriPermission

    0 讨论(0)
  • 2020-11-22 03:07

    Just wanted to say that this answer is brilliant and I'm using it for a long time without problems. But some time ago I've stumbled upon a problem that DownloadsProvider returns URIs in format content://com.android.providers.downloads.documents/document/raw%3A%2Fstorage%2Femulated%2F0%2FDownload%2Fdoc.pdf and hence app is crashed with NumberFormatException as it's impossible to parse its uri segments as long. But raw: segment contains direct uri which can be used to retrieve a referenced file. So I've fixed it by replacing isDownloadsDocument(uri) if content with following:

    final String id = DocumentsContract.getDocumentId(uri);
    if (!TextUtils.isEmpty(id)) {
    if (id.startsWith("raw:")) {
        return id.replaceFirst("raw:", "");
    }
    try {
        final Uri contentUri = ContentUris.withAppendedId(
                Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
        return getDataColumn(context, contentUri, null, null);
    } catch (NumberFormatException e) {
        Log.e("FileUtils", "Downloads provider returned unexpected uri " + uri.toString(), e);
        return null;
    }
    }
    
    0 讨论(0)
  • 2020-11-22 03:09

    I've tried several of the answers here, and I think I have a solution that will work every time and manages permissions as well.

    It is based on the clever solution from LEO. This post should contain all the code you need to make this work, and it should work on any phone and Android version ;)

    In order to have the ability to pick a file from an SD card, you'll need this in your manifest:

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    

    Constants:

    private static final int PICK_IMAGE = 456; // Whatever number you like
    public static final int MY_PERMISSIONS_REQUEST_READ_EXTERNAL = 28528; // Whatever number you like
    public static final String FILE_TEMP_NAME = "temp_image"; // Whatever file name you like
    

    Check permission and launchImagePick if possible

    if (ContextCompat.checkSelfPermission(getThis(),
            Manifest.permission.READ_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED) {
    
        ActivityCompat.requestPermissions(getThis(),
                new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
                MY_PERMISSIONS_REQUEST_READ_EXTERNAL);
    }
    else {
        launchImagePick();
    }
    

    Permission response

    @Override
    public void onRequestPermissionsResult(int requestCode,
                                           @NonNull
                                             String permissions[],
                                           @NonNull
                                             int[] grantResults) {
    
        if (manageReadExternalPermissionResponse(this, requestCode, grantResults)) {
            launchImagePick();
        }
    }
    

    Manage permission response

    public static boolean manageReadExternalPermissionResponse(final Activity activity, int requestCode, int[] grantResults) {
    
        if (requestCode == MY_PERMISSIONS_REQUEST_READ_EXTERNAL) {
    
            // If request is cancelled, the result arrays are empty.
    
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
    
                // Permission was granted, yay! Do the
                // contacts-related task you need to do.
                return true;
    
            } else if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_DENIED) {
    
                boolean showRationale = ActivityCompat.shouldShowRequestPermissionRationale(activity,
                        Manifest.permission.READ_EXTERNAL_STORAGE);
    
                if (!showRationale) {
                    // The user also CHECKED "never ask again".
                    // You can either enable some fall back,
                    // disable features of your app
                    // or open another dialog explaining
                    // again the permission and directing to
                    // the app setting.
    
                } else {
                    // The user did NOT check "never ask again".
                    // This is a good place to explain the user
                    // why you need the permission and ask if he/she wants
                    // to accept it (the rationale).
                }
            } else {
                // Permission denied, boo! Disable the
                // functionality that depends on this permission.
            }
        }
        return false;
    }
    

    Launch image pick

    private void launchImagePick() {
    
        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*");
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        startActivityForResult(intent, PICK_IMAGE);
    
        // see onActivityResult
    }
    

    Manage Image pick response

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PICK_IMAGE) {
    
            if (resultCode == Activity.RESULT_OK) {
                if (data != null && data.getData() != null) {
    
                    try {
                         InputStream inputStream = getContentResolver().openInputStream(data.getData())
                         if (inputStream != null) {
    
                            // No special persmission needed to store the file like that
                            FileOutputStream fos = openFileOutput(FILE_TEMP_NAME, Context.MODE_PRIVATE);
    
                            final int BUFFER_SIZE = 1 << 10 << 3; // 8 KiB buffer
                            byte[] buffer = new byte[BUFFER_SIZE];
                            int bytesRead = -1;
                            while ((bytesRead = inputStream.read(buffer)) > -1) {
                                fos.write(buffer, 0, bytesRead);
                            }
                            inputStream.close();
                            fos.close();
    
                            File tempImageFile = new File(getFilesDir()+"/"+FILE_TEMP_NAME);
    
                            // Do whatever you want with the File
    
                            // Delete when not needed anymore
                            deleteFile(FILE_TEMP_NAME);
                        }
                    }
                    catch (Exception e) {
                        e.printStackTrace();
                    }
                } else {
                    // Error display
                }
            } else {
                // The user did not select any image
            }
        }
    }
    

    That's all folks; this works for me on all the telephones I have.

    0 讨论(0)
  • 2020-11-22 03:16

    Question

    How to get an actual file path from a URI

    Answer

    To my knowledge, we don't need to get the file path from a URI because for most of the cases we can directly use the URI to get our work done (like 1. getting bitmap 2. Sending a file to the server, etc.)

    1. Sending to the server

    We can directly send the file to the server using just the URI.

    Using the URI we can get InputStream, which we can directly send to the server using MultiPartEntity.

    Example

    /**
     * Used to form Multi Entity for a URI (URI pointing to some file, which we got from other application).
     *
     * @param uri     URI.
     * @param context Context.
     * @return Multi Part Entity.
     */
    public MultipartEntity formMultiPartEntityForUri(final Uri uri, final Context context) {
        MultipartEntity multipartEntity = new MultipartEntity(HttpMultipartMode.BROWSER_COMPATIBLE, null, Charset.forName("UTF-8"));
        try {
            InputStream inputStream = mContext.getContentResolver().openInputStream(uri);
            if (inputStream != null) {
                ContentBody contentBody = new InputStreamBody(inputStream, getFileNameFromUri(uri, context));
                multipartEntity.addPart("[YOUR_KEY]", contentBody);
            }
        }
        catch (Exception exp) {
            Log.e("TAG", exp.getMessage());
        }
        return multipartEntity;
    }
    
    /**
     * Used to get a file name from a URI.
     *
     * @param uri     URI.
     * @param context Context.
     * @return File name from URI.
     */
    public String getFileNameFromUri(final Uri uri, final Context context) {
    
        String fileName = null;
        if (uri != null) {
            // Get file name.
            // File Scheme.
            if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
                File file = new File(uri.getPath());
                fileName = file.getName();
            }
            // Content Scheme.
            else if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
                Cursor returnCursor =
                        context.getContentResolver().query(uri, null, null, null, null);
                if (returnCursor != null && returnCursor.moveToFirst()) {
                    int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
                    fileName = returnCursor.getString(nameIndex);
                    returnCursor.close();
                }
            }
        }
        return fileName;
    }
    

    2. Getting a BitMap from a URI

    If the URI is pointing to image then we will get bitmap, else null:

    /**
     * Used to create bitmap for the given URI.
     * <p>
     * 1. Convert the given URI to bitmap.
     * 2. Calculate ratio (depending on bitmap size) on how much we need to subSample the original bitmap.
     * 3. Create bitmap bitmap depending on the ration from URI.
     * 4. Reference - http://stackoverflow.com/questions/3879992/how-to-get-bitmap-from-an-uri
     *
     * @param context       Context.
     * @param uri           URI to the file.
     * @param bitmapSize Bitmap size required in PX.
     * @return Bitmap bitmap created for the given URI.
     * @throws IOException
     */
    public static Bitmap createBitmapFromUri(final Context context, Uri uri, final int bitmapSize) throws IOException {
    
        // 1. Convert the given URI to bitmap.
        InputStream input = context.getContentResolver().openInputStream(uri);
        BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
        onlyBoundsOptions.inJustDecodeBounds = true;
        onlyBoundsOptions.inDither = true;//optional
        onlyBoundsOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
        BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
        input.close();
        if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) {
            return null;
        }
    
        // 2. Calculate ratio.
        int originalSize = (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth) ? onlyBoundsOptions.outHeight : onlyBoundsOptions.outWidth;
        double ratio = (originalSize > bitmapSize) ? (originalSize / bitmapSize) : 1.0;
    
        // 3. Create bitmap.
        BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
        bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
        bitmapOptions.inDither = true;//optional
        bitmapOptions.inPreferredConfig = Bitmap.Config.ARGB_8888;//optional
        input = context.getContentResolver().openInputStream(uri);
        Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
        input.close();
    
        return bitmap;
    }
    
    /**
     * For Bitmap option inSampleSize - We need to give value in power of two.
     *
     * @param ratio Ratio to be rounded of to power of two.
     * @return Ratio rounded of to nearest power of two.
     */
    private static int getPowerOfTwoForSampleRatio(final double ratio) {
        int k = Integer.highestOneBit((int) Math.floor(ratio));
        if (k == 0) return 1;
        else return k;
    }
    

    Comments

    1. Android doesn't provide any methods to get file path from a URI, and in most of the above answers we have hard coded some constants, which may break in feature release (sorry, I may be wrong).
    2. Before going directly going to a solution of the getting file path from a URI, try if you can solve your use case with a URI and Android default methods.

    Reference

    1. https://developer.android.com/guide/topics/providers/content-provider-basics.html
    2. https://developer.android.com/reference/android/content/ContentResolver.html
    3. https://hc.apache.org/httpcomponents-client-ga/httpmime/apidocs/org/apache/http/entity/mime/content/InputStreamBody.html
    0 讨论(0)
  • 2020-11-22 03:16

    This is what I do:

    Uri selectedImageURI = data.getData();    imageFile = new File(getRealPathFromURI(selectedImageURI)); 
    
    private String getRealPathFromURI(Uri contentURI) {
      Cursor cursor = getContentResolver().query(contentURI, null, null, null, null);
      if (cursor == null) { // Source is Dropbox or other similar local file path
          return contentURI.getPath();
          } else { 
          cursor.moveToFirst(); 
          int idx = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA); 
          return cursor.getString(idx); 
      }
    }
    

    NOTE: managedQuery() method is deprecated, so I am not using it.

    This answer is from m3n0R on question android get real path by Uri.getPath() and I claim no credit. I just thought that people who haven't solved this issue yet could use this.

    0 讨论(0)
  • 2020-11-22 03:18

    I've combine multiple answers into one working solution that results with file path

    Mime type is irrelevant for the example purpose.

                Intent intent;
                if(Build.VERSION.SDK_INT >= 19){
                    intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
                    intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
                }else{
                    intent = new Intent(Intent.ACTION_GET_CONTENT);
                }
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                intent.setType("application/octet-stream");
                if(isAdded()){
                    startActivityForResult(intent, RESULT_CODE);
                }
    

    Handling result

        @Override
        public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if( requestCode == RESULT_CODE && resultCode == Activity.RESULT_OK) {
            Uri uri = data.getData();
            if (uri != null && !uri.toString().isEmpty()) {
                if(Build.VERSION.SDK_INT >= 19){
                    final int takeFlags = data.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION;
                    //noinspection ResourceType
                    getActivity().getContentResolver()
                            .takePersistableUriPermission(uri, takeFlags);
                }
                String filePath = FilePickUtils.getSmartFilePath(getActivity(), uri);
                // do what you need with it...
            }
        }
    }
    

    FilePickUtils

    import android.annotation.SuppressLint;
    import android.content.ContentUris;
    import android.content.Context;
    import android.database.Cursor;
    import android.net.Uri;
    import android.os.Build;
    import android.os.Environment;
    import android.provider.DocumentsContract;
    import android.provider.MediaStore;
    
    public class FilePickUtils {
        private static String getPathDeprecated(Context ctx, Uri uri) {
            if( uri == null ) {
                return null;
            }
            String[] projection = { MediaStore.Images.Media.DATA };
            Cursor cursor = ctx.getContentResolver().query(uri, projection, null, null, null);
            if( cursor != null ){
                int column_index = cursor
                        .getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
                cursor.moveToFirst();
                return cursor.getString(column_index);
            }
            return uri.getPath();
        }
    
        public static String getSmartFilePath(Context ctx, Uri uri){
    
            if (Build.VERSION.SDK_INT < 19) {
                return getPathDeprecated(ctx, uri);
            }
            return  FilePickUtils.getPath(ctx, uri);
        }
    
        @SuppressLint("NewApi")
        public static String getPath(final Context context, final Uri uri) {
            final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
            // DocumentProvider
            if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
                // ExternalStorageProvider
                if (isExternalStorageDocument(uri)) {
                    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];
                    }
    
                    // TODO handle non-primary volumes
                }
                // DownloadsProvider
                else if (isDownloadsDocument(uri)) {
                    final String id = DocumentsContract.getDocumentId(uri);
                    final Uri contentUri = ContentUris.withAppendedId(
                            Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
    
                    return getDataColumn(context, contentUri, null, null);
                }
                // MediaProvider
                else if (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 getDataColumn(context, contentUri, selection, selectionArgs);
                }
            }
            // MediaStore (and general)
            else if ("content".equalsIgnoreCase(uri.getScheme())) {
                return getDataColumn(context, uri, null, null);
            }
            // File
            else if ("file".equalsIgnoreCase(uri.getScheme())) {
                return uri.getPath();
            }
    
            return null;
        }
    
        /**
         * Get the value of the data column for this Uri. This is useful for
         * MediaStore Uris, and other file-based ContentProviders.
         *
         * @param context The context.
         * @param uri The Uri to query.
         * @param selection (Optional) Filter used in the query.
         * @param selectionArgs (Optional) Selection arguments used in the query.
         * @return The value of the _data column, which is typically a file path.
         */
        public static String getDataColumn(Context context, Uri uri, String selection,
                                           String[] selectionArgs) {
            Cursor cursor = null;
            final String column = "_data";
            final String[] projection = {
                    column
            };
    
            try {
                cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                        null);
                if (cursor != null && cursor.moveToFirst()) {
                    final int column_index = cursor.getColumnIndexOrThrow(column);
                    return cursor.getString(column_index);
                }
            } finally {
                if (cursor != null)
                    cursor.close();
            }
            return null;
        }
    
    
        /**
         * @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());
        }
    
    }
    
    0 讨论(0)
提交回复
热议问题