Android: guardar la imagen en la galería

91

Tengo una aplicación con una galería de imágenes y quiero que el usuario pueda guardarla en su propia galería. He creado un menú de opciones con una sola voz "guardar" para permitir eso, pero el problema es ... ¿cómo puedo guardar la imagen en la galería?

este es mi código:

@Override
        public boolean onOptionsItemSelected(MenuItem item) {
            // Handle item selection
            switch (item.getItemId()) {
            case R.id.menuFinale:

                imgView.setDrawingCacheEnabled(true);
                Bitmap bitmap = imgView.getDrawingCache();
                File root = Environment.getExternalStorageDirectory();
                File file = new File(root.getAbsolutePath()+"/DCIM/Camera/img.jpg");
                try 
                {
                    file.createNewFile();
                    FileOutputStream ostream = new FileOutputStream(file);
                    bitmap.compress(CompressFormat.JPEG, 100, ostream);
                    ostream.close();
                } 
                catch (Exception e) 
                {
                    e.printStackTrace();
                }



                return true;
            default:
                return super.onOptionsItemSelected(item);
            }
        }

no estoy seguro de esta parte del código:

File root = Environment.getExternalStorageDirectory();
                File file = new File(root.getAbsolutePath()+"/DCIM/Camera/img.jpg");

¿Es correcto guardar en la galería? desafortunadamente el código no funciona :(

Christian Giupponi
fuente
has resuelto este problema? ¿Puedes compartir conmigo?
user3233280
También tengo el mismo problema stackoverflow.com/questions/21951558/…
user3233280
Para aquellos de ustedes que todavía tienen problemas para guardar el archivo, podría deberse a que su URL contiene caracteres ilegales como "?", ":" Y "-" Elimínelos y debería funcionar. Este es un error común en dispositivos externos y emuladores de Android. Vea más sobre esto aquí: stackoverflow.com/questions/11394616/…
ChallengeAccepted
La respuesta aceptada está un poco desactualizada en 2019. He escrito una respuesta actualizada aquí: stackoverflow.com/questions/36624756/…
Bao Lei

Respuestas:

168
MediaStore.Images.Media.insertImage(getContentResolver(), yourBitmap, yourTitle , yourDescription);

El código anterior agregará la imagen al final de la galería. Si desea modificar la fecha para que aparezca al principio o cualquier otro metadato, consulte el código a continuación (Cortesy of SK, samkirton ):

https://gist.github.com/samkirton/0242ba81d7ca00b475b9

/**
 * Android internals have been modified to store images in the media folder with 
 * the correct date meta data
 * @author samuelkirton
 */
public class CapturePhotoUtils {

    /**
     * A copy of the Android internals  insertImage method, this method populates the 
     * meta data with DATE_ADDED and DATE_TAKEN. This fixes a common problem where media 
     * that is inserted manually gets saved at the end of the gallery (because date is not populated).
     * @see android.provider.MediaStore.Images.Media#insertImage(ContentResolver, Bitmap, String, String)
     */
    public static final String insertImage(ContentResolver cr, 
            Bitmap source, 
            String title, 
            String description) {

        ContentValues values = new ContentValues();
        values.put(Images.Media.TITLE, title);
        values.put(Images.Media.DISPLAY_NAME, title);
        values.put(Images.Media.DESCRIPTION, description);
        values.put(Images.Media.MIME_TYPE, "image/jpeg");
        // Add the date meta data to ensure the image is added at the front of the gallery
        values.put(Images.Media.DATE_ADDED, System.currentTimeMillis());
        values.put(Images.Media.DATE_TAKEN, System.currentTimeMillis());

        Uri url = null;
        String stringUrl = null;    /* value to be returned */

        try {
            url = cr.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

            if (source != null) {
                OutputStream imageOut = cr.openOutputStream(url);
                try {
                    source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut);
                } finally {
                    imageOut.close();
                }

                long id = ContentUris.parseId(url);
                // Wait until MINI_KIND thumbnail is generated.
                Bitmap miniThumb = Images.Thumbnails.getThumbnail(cr, id, Images.Thumbnails.MINI_KIND, null);
                // This is for backward compatibility.
                storeThumbnail(cr, miniThumb, id, 50F, 50F,Images.Thumbnails.MICRO_KIND);
            } else {
                cr.delete(url, null, null);
                url = null;
            }
        } catch (Exception e) {
            if (url != null) {
                cr.delete(url, null, null);
                url = null;
            }
        }

        if (url != null) {
            stringUrl = url.toString();
        }

        return stringUrl;
    }

    /**
     * A copy of the Android internals StoreThumbnail method, it used with the insertImage to
     * populate the android.provider.MediaStore.Images.Media#insertImage with all the correct
     * meta data. The StoreThumbnail method is private so it must be duplicated here.
     * @see android.provider.MediaStore.Images.Media (StoreThumbnail private method)
     */
    private static final Bitmap storeThumbnail(
            ContentResolver cr,
            Bitmap source,
            long id,
            float width, 
            float height,
            int kind) {

        // create the matrix to scale it
        Matrix matrix = new Matrix();

        float scaleX = width / source.getWidth();
        float scaleY = height / source.getHeight();

        matrix.setScale(scaleX, scaleY);

        Bitmap thumb = Bitmap.createBitmap(source, 0, 0,
            source.getWidth(),
            source.getHeight(), matrix,
            true
        );

        ContentValues values = new ContentValues(4);
        values.put(Images.Thumbnails.KIND,kind);
        values.put(Images.Thumbnails.IMAGE_ID,(int)id);
        values.put(Images.Thumbnails.HEIGHT,thumb.getHeight());
        values.put(Images.Thumbnails.WIDTH,thumb.getWidth());

        Uri url = cr.insert(Images.Thumbnails.EXTERNAL_CONTENT_URI, values);

        try {
            OutputStream thumbOut = cr.openOutputStream(url);
            thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);
            thumbOut.close();
            return thumb;
        } catch (FileNotFoundException ex) {
            return null;
        } catch (IOException ex) {
            return null;
        }
    }
}
sfratini
fuente
22
Esto guarda la imagen, pero hasta el final de la galería, cuando toma una foto con la cámara, se guarda en la parte superior. ¿Cómo puedo guardar la imagen en la parte superior de la galería?
eric.itzhak
19
Tenga en cuenta que también debe agregar <uses-allow android: name = "android.permission.WRITE_EXTERNAL_STORAGE" /> a su manifext.xml.
Kyle Clegg
3
Las imágenes no se guardan en la parte superior de la galería porque insertImage internamente no agrega metadatos de fecha. Por favor, vea este GIST: gist.github.com/0242ba81d7ca00b475b9.git es una copia exacta del método insertImage pero agrega la fecha meta para asegurar que la imagen se agregue al frente de la galería.
S-K
1
@ S-K'No puedo acceder a esa URL. Actualícelo y actualizaré mi respuesta para que tenga ambas opciones. Saludos
sfratini
6
Aquí está el enlace GIST correcto mencionado anteriormente (necesario para eliminar el .gital final)
minipif
48

De hecho, puedes guardar tu foto en cualquier lugar. Si quieres guardar en un espacio público, para que cualquier otra aplicación pueda acceder, usa este código:

storageDir = new File(
    Environment.getExternalStoragePublicDirectory(
        Environment.DIRECTORY_PICTURES
    ), 
    getAlbumName()
);

La imagen no va al álbum. Para hacer esto, necesita llamar a un escaneo:

private void galleryAddPic() {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File f = new File(mCurrentPhotoPath);
    Uri contentUri = Uri.fromFile(f);
    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
}

Puede encontrar más información en https://developer.android.com/training/camera/photobasics.html#TaskGallery

Sigrist
fuente
1
Esta es una solución simple y agradable, ya que no necesitamos cambiar toda la implementación y podemos crear una carpeta personalizada para las aplicaciones.
Hugo Gresse
2
Enviar una transmisión puede ser una pérdida de recursos cuando solo puede escanear un archivo: stackoverflow.com/a/5814533/43051 .
Jérémy Reynaud
2
¿Dónde pasa realmente el mapa de bits?
Daniel Reyhanian
22

He intentado muchas cosas para que esto funcione en Marshmallow y Lollipop. Finalmente terminé moviendo la imagen guardada a la carpeta DCIM (la nueva aplicación Google Photo escanea imágenes solo si aparentemente están dentro de esta carpeta)

public static File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
         .format(System.currentTimeInMillis());
    File storageDir = new File(Environment
         .getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/Camera/");
    if (!storageDir.exists())
        storageDir.mkdirs();
    File image = File.createTempFile(
            timeStamp,                   /* prefix */
            ".jpeg",                     /* suffix */
            storageDir                   /* directory */
    );
    return image;
}

Y luego el código estándar para escanear archivos que también puede encontrar en el sitio de Google Developers .

public static void addPicToGallery(Context context, String photoPath) {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File f = new File(photoPath);
    Uri contentUri = Uri.fromFile(f);
    mediaScanIntent.setData(contentUri);
    context.sendBroadcast(mediaScanIntent);
}

Recuerde que esta carpeta no puede estar presente en todos los dispositivos del mundo y que, a partir de Marshmallow (API 23), debe solicitar el permiso para WRITE_EXTERNAL_STORAGE al usuario.

MatPag
fuente
1
Gracias por la información sobre Google Photos.
Jérémy Reynaud
1
Esta es una y única solución que se explica bien. Nadie más mencionó que el archivo debe estar en la carpeta DCIM. ¡¡¡Gracias!!!
Predrag Manojlovic
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)hizo el truco para mí. ¡Gracias!
saltandpepper
2
getExternalStoragePublicDirectory()ahora está obsoleto en la API 29. Necesito usar MediaStore
riggaroo
@riggaroo Sí, tienes razón Rebecca, actualizaré la respuesta lo antes posible
MatPag
13

Según este curso , la forma correcta de hacerlo es:

Environment.getExternalStoragePublicDirectory(
        Environment.DIRECTORY_PICTURES
    )

Esto le dará la ruta raíz del directorio de la galería.

Cédric Julien
fuente
Intenté este nuevo código pero falló java.lang.NoSuchFieldError: android.os.Environment.DIRECTORY_PICTURES
Christian Giupponi
ok gracias, entonces no hay forma de poner una imagen en la galería con android <2.2?
Christian Giupponi
Perfecto: un enlace directamente al sitio web para desarrolladores de Android. Esto funcionó y fue una solución simple.
Phil
1
Buena respuesta, pero sería mejor con la adición del método "galleryAddPic" de las otras respuestas aquí, ya que normalmente querrá que la aplicación Galería observe nuevas imágenes.
Andrew Koster
11
private void galleryAddPic() {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    File f = new File(mCurrentPhotoPath);
    Uri contentUri = Uri.fromFile(f);
    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
}
nitin sol
fuente
6

Puede crear un directorio dentro de la carpeta de la cámara y guardar la imagen. Después de eso, simplemente puede realizar su escaneo. Instantáneamente mostrará su imagen en la galería.

String root = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString()+ "/Camera/Your_Directory_Name";
File myDir = new File(root);
myDir.mkdirs();
String fname = "Image-" + image_name + ".png";
File file = new File(myDir, fname);
System.out.println(file.getAbsolutePath());
if (file.exists()) file.delete();
    Log.i("LOAD", root + fname);
    try {
        FileOutputStream out = new FileOutputStream(file);
        finalBitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
        out.flush();
        out.close();
    } catch (Exception e) {
       e.printStackTrace();
    }

MediaScannerConnection.scanFile(context, new String[]{file.getPath()}, new String[]{"image/jpeg"}, null);
javatar
fuente
en este criterio esta es la mejor respuesta
Noor Hossain
1

Vengo aquí con la misma duda, pero para Xamarin para Android, he usado la respuesta de Sigrist para hacer este método después de guardar mi archivo:

private void UpdateGallery()
{
    Intent mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
    Java.IO.File file = new Java.IO.File(_path);
    Android.Net.Uri contentUri = Android.Net.Uri.FromFile(file);
    mediaScanIntent.SetData(contentUri);
    Application.Context.SendBroadcast(mediaScanIntent);
} 

y resolvió mi problema, Thx Sigrist. Lo puse aquí porque no encontré una respuesta sobre esto para Xamarin y espero que pueda ayudar a otras personas.

Slaters
fuente
1

En mi caso, las soluciones anteriores no funcionaron, tuve que hacer lo siguiente:

sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(f)));
dc10
fuente
es realmente bueno saber acerca de esta opción, pero desafortunadamente no funciona en algunos dispositivos con Android 6, por lo que es ContentProviderpreferible una solución
Siarhei
0
 String filePath="/storage/emulated/0/DCIM"+app_name;
    File dir=new File(filePath);
    if(!dir.exists()){
        dir.mkdir();
    }

Este código está en el método onCreate. Este código es para crear un directorio de app_name. Ahora, se puede acceder a este directorio usando la aplicación de administrador de archivos predeterminada en Android. Utilice esta cadena filePath siempre que sea necesario para configurar su carpeta de destino. Estoy seguro de que este método también funciona en Android 7 porque lo probé, por lo que también puede funcionar en otras versiones de Android.

Bhavik Mehta
fuente