如何将位图保存到Android相册。

21

很遗憾,我找到的解决方法在Android 5.1.1上不起作用。

我有一个名为source的位图。 我需要将其直接保存到手机相册中。我的清单文件包含<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

你能给我一个可行的方法来实现这个吗?

9个回答

38

在 API 29(Android Q)之前,有几种不同的方法可以实现,但它们都涉及到一个或多个在 Q 中已弃用的 API。 在2019年,这是一种既向后兼容又向前兼容的方法:

(并且由于现在是2019年,所以我将用Kotlin编写)

    /// @param folderName can be your app's name
    private fun saveImage(bitmap: Bitmap, context: Context, folderName: String) {
        if (android.os.Build.VERSION.SDK_INT >= 29) {
            val values = contentValues()
            values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + folderName)
            values.put(MediaStore.Images.Media.IS_PENDING, true)
            // RELATIVE_PATH and IS_PENDING are introduced in API 29.

            val uri: Uri? = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
            if (uri != null) {
                saveImageToStream(bitmap, context.contentResolver.openOutputStream(uri))
                values.put(MediaStore.Images.Media.IS_PENDING, false)
                context.contentResolver.update(uri, values, null, null)
            }
        } else {
            val directory = File(Environment.getExternalStorageDirectory().toString() + separator + folderName)
            // getExternalStorageDirectory is deprecated in API 29

            if (!directory.exists()) {
                directory.mkdirs()
            }
            val fileName = System.currentTimeMillis().toString() + ".png"
            val file = File(directory, fileName)
            saveImageToStream(bitmap, FileOutputStream(file))
            if (file.absolutePath != null) {
                val values = contentValues()
                values.put(MediaStore.Images.Media.DATA, file.absolutePath)
                // .DATA is deprecated in API 29
                context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
            }
        }
    }

    private fun contentValues() : ContentValues {
        val values = ContentValues()
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/png")
        values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
        values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
        return values
    }

    private fun saveImageToStream(bitmap: Bitmap, outputStream: OutputStream?) {
        if (outputStream != null) {
            try {
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
                outputStream.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }

在调用此函数之前,您需要先获取WRITE_EXTERNAL_STORAGE权限。


非常好的例子,但是在Android Q中如何将保存的图像加载到ImageView中呢?以前使用file.absolutpath很容易。现在我只有/external/images/media/1833这样的URI。您知道如何在Android Q下获取真实的文件路径吗? - Frank
@Frank,你可以使用Ucrop方法File Utils获取文件路径。 - Rahul Mandaliya

17

使用这个:

private void saveImage(Bitmap finalBitmap, String image_name) {

        String root = Environment.getExternalStorageDirectory().toString();
        File myDir = new File(root);
        myDir.mkdirs();
        String fname = "Image-" + image_name+ ".jpg";
        File file = new File(myDir, fname);
        if (file.exists()) file.delete();
        Log.i("LOAD", root + fname);
        try {
            FileOutputStream out = new FileOutputStream(file);
            finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
            out.flush();
            out.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

1
您不必创建外部目录,也不必检查文件是否存在以删除它,请在finally块中关闭流或使用+JDK7 Try-With-Resources。 - GabrielBB
它可以工作。但是画廊没有显示保存的图片。 - J.Dragon

15

使用此代码,我们可以帮助您将图像存储到名为saved_images的特定文件夹中,并且该文件夹中的图像会立即显示在库中。

private void SaveImage(Bitmap finalBitmap) {

String root = Environment.getExternalStoragePublicDirectory(
    Environment.DIRECTORY_PICTURES).toString();
File myDir = new File(root + "/saved_images");
myDir.mkdirs();
Random generator = new Random();

int n = 10000;
n = generator.nextInt(n);
String fname = "Image-"+ n +".jpg";
File file = new File (myDir, fname);
if (file.exists ()) file.delete ();
try {
    FileOutputStream out = new FileOutputStream(file);
    finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
    // sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, 
    //     Uri.parse("file://"+ Environment.getExternalStorageDirectory())));
    out.flush();
    out.close();

} catch (Exception e) {
    e.printStackTrace();
}
// Tell the media scanner about the new file so that it is
// immediately available to the user.
MediaScannerConnection.scanFile(this, new String[]{file.toString()}, null,
    new MediaScannerConnection.OnScanCompletedListener() {
        public void onScanCompleted(String path, Uri uri) {
            Log.i("ExternalStorage", "Scanned " + path + ":");
            Log.i("ExternalStorage", "-> uri=" + uri);
        }
    });
}

2
这个解决方案是否使用了任何权限?因为文件夹没有被创建,而且我在日志中得到的uri=null。 - Fernando Torres

10
自Android Q以来,保存图片到画廊的方式发生了变化。感谢@BaoLei的帮助,这里提供了我的答案(如果有人需要的话)。
    private void saveImage(Bitmap bitmap) {
        if (android.os.Build.VERSION.SDK_INT >= 29) {
            ContentValues values = contentValues();
            values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + getString(R.string.app_name));
            values.put(MediaStore.Images.Media.IS_PENDING, true);

            Uri uri = this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            if (uri != null) {
                try {
                    saveImageToStream(bitmap, this.getContentResolver().openOutputStream(uri));
                    values.put(MediaStore.Images.Media.IS_PENDING, false);
                    this.getContentResolver().update(uri, values, null, null);
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }

            }
        } else {
            File directory = new File(Environment.getExternalStorageDirectory().toString() + '/' + getString(R.string.app_name));

            if (!directory.exists()) {
                directory.mkdirs();
            }
            String fileName = System.currentTimeMillis() + ".png";
            File file = new File(directory, fileName);
            try {
                saveImageToStream(bitmap, new FileOutputStream(file));
                ContentValues values = new ContentValues();
                values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
                this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }

        }
    }

    private ContentValues contentValues() {
        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
        values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
        }
        return values;
    }

    private void saveImageToStream(Bitmap bitmap, OutputStream outputStream) {
        if (outputStream != null) {
            try {
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
                outputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

我怎样才能在这里更改 >Q 的文件名? - Bhelasagar Meher
String fileName = System.currentTimeMillis() + ".png"; 文件名 = System.currentTimeMillis() + ".png"; - maulikdhameliya

6

这里是一个完全可用的 Kotlin 解决方案:

fun saveToGallery(context: Context, bitmap: Bitmap, albumName: String) {
    val filename = "${System.currentTimeMillis()}.png"
    val write: (OutputStream) -> Boolean = {
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        val contentValues = ContentValues().apply {
            put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
            put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
            put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DCIM}/$albumName")
        }

        context.contentResolver.let {
            it.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)?.let { uri ->
                it.openOutputStream(uri)?.let(write)
            }
        }
    } else {
        val imagesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString() + File.separator + albumName
        val file = File(imagesDir)
        if (!file.exists()) {
            file.mkdir()
        }
        val image = File(imagesDir, filename)
        write(FileOutputStream(image))
    }
}

对我来说,它在版本大于Q的情况下非常有效,对于较低版本,我必须使用以下代码: `val imagesDir = 图片路径 val file = File(imagesDir) if (!file.exists()) { file.mkdir() } val image = File(imagesDir, filename) write(FileOutputStream(image)) MediaScannerConnection.scanFile( context, arrayOf(image.absolutePath), arrayOf("image/png"), null ) bmpUri = Uri.fromFile(image)` - Chuy Porras

5

一行搞定 MediaStore.Images.Media.insertImage(applicationContext.getContentResolver(), IMAGE ,"nameofimage" , "description");


我尝试了这个方法,它有点可行(不会出错),但是它没有将位图发送到照片库。也许,你知道我的方法可能出了什么问题吗?我已经设置了权限,并且从模拟器上的相机应用程序拍摄的照片通常会出现在图库中。提前感谢! - JorgeAmVF
1
由于 insertImage 已被弃用,是否有其他替代方法? - iamvinitk

4

现在我们有android10和android11,因此这是一个更新版本,可以在所有android设备上使用。

在调用此函数之前,请确保您拥有WRITE_EXTERNAL_STORAGE权限。

    private fun saveMediaToStorage(bitmap: Bitmap) {
        val filename = "${System.currentTimeMillis()}.jpg"
        var fos: OutputStream? = null
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            contentResolver?.also { resolver ->
                val contentValues = ContentValues().apply {
                    put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
                    put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
                    put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
                }
                val imageUri: Uri? =
                    resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
                fos = imageUri?.let { resolver.openOutputStream(it) }
            }
        } else {
            val imagesDir =
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
            val image = File(imagesDir, filename)
            fos = FileOutputStream(image)
        }
        fos?.use {
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
            toast("Saved to Photos")
        }
    }

这是Kotlin编写的代码,我认为非常简单明了易懂。但如果您有问题,请在下方评论,我会进行解释。

参考文献:Android将位图保存到相册教程


简单而最好。 - undefined

2

我想在我的应用程序中添加基于@Bao Lei答案的Java代码。

    private void saveImage(Bitmap bitmap, Context context, String folderName) throws FileNotFoundException {
        if (android.os.Build.VERSION.SDK_INT >= 29) {
            ContentValues values = new ContentValues();
            values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + folderName);
            values.put(MediaStore.Images.Media.IS_PENDING, true);
            // RELATIVE_PATH and IS_PENDING are introduced in API 29.

            Uri uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            if (uri != null) {
                saveImageToStream(bitmap, context.getContentResolver().openOutputStream(uri));
                values.put(MediaStore.Images.Media.IS_PENDING, false);
                context.getContentResolver().update(uri, values, null, null);
            }
        } else {
            dir = new File(getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES),"");
            // getExternalStorageDirectory is deprecated in API 29

            if (!dir.exists()) {
                dir.mkdirs();
            }

            java.util.Date date = new java.util.Date();
            imageFile = new File(dir.getAbsolutePath()
                    + File.separator
                    + new Timestamp(date.getTime()).toString()
                    + "Image.jpg");

            imageFile = new File(dir.getAbsolutePath()
                    + File.separator
                    + new Timestamp(date.getTime()).toString()
                    + "Image.jpg");
            saveImageToStream(bitmap, new FileOutputStream(imageFile));
            if (imageFile.getAbsolutePath() != null) {
                ContentValues values = new ContentValues();
                values.put(MediaStore.Images.Media.DATA, imageFile.getAbsolutePath());
                // .DATA is deprecated in API 29
                context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
            }
        }
    }

    private ContentValues contentValues() {
        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
        values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
        values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
        return values;
    }

    private void saveImageToStream(Bitmap bitmap, OutputStream outputStream) {
        if (outputStream != null) {
            try {
                bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
                outputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

这个在我的应用程序中运作良好。


0

对于媒体扫描,您可以简单地执行以下操作


  val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
  intent.data = Uri.fromFile(path) // path must be of File type
  context.sendBroadcast(intent)


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