I am trying to save a bitmap that is created by the user to the default 'Pictures' folder on the device. I will start with the option that seems to be the more common method:
public void saveImageToExternalStorage(String filename, Bitmap image) {
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator;
try {
File directory = new File(path);
if (!directory.exists()) {
directory.mkdirs();
}
File file = new File(directory, filename + ".jpeg");
file.createNewFile();
OutputStream outputStream = new FileOutputStream(file);
image.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.flush();
outputStream.close();
} catch (IOException exception) {
exception.printStackTrace();
}
}
This is supposed to save to the 'Pictures' folder, but Environment.getExternalStorageDirectory().getAbsolutePath() seems to create a new file structure like this file://storage/emulated/11/Pictures/ and save the picture to that. I have also tried Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) with the same result. But this is not the default 'Pictures' folder on any of my test devices or emulators. Which led me to look for other methods, and I found this:
public void saveImageToExternalStorage(String filename, Bitmap image) {
try {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.TITLE, filename);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
Uri filepath = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
OutputStream outputStream = getContentResolver().openOutputStream(filepath);
image.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.close();
} catch (IOException exception) {
exception.printStackTrace();
}
}
This actually saves to the default file structure, which apparently looks like this: content://media/external/images/media/4282. But there seems to be no way to specify the filename of the image (Media.TITLE just sets the title attribute, not the actual filename), it saves as a (seemingly) random string of numbers. I looked at the API Guide for MediaStore.Images.Media and there does not seem to be any other variable that would set the filename. This also does not seem to be the correct way of saving, according to the Android Developer Guides. I would like to know if there is any way of saving to this folder, while also setting my own filename. And if this method could produce unforeseen problems on other devices.
EDIT:
For anyone interested this is my current code, based on the answer by #CommonsWare:
public void saveImageToExternalStorage(Context context, String filename, Bitmap image) throws IOException {
File directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
File file = new File(directory, filename + ".jpeg");
FileOutputStream outputStream = new FileOutputStream(file);
mBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream);
outputStream.flush();
outputStream.getFD().sync();
outputStream.close();
MediaScannerConnection.scanFile(context, new String[] {file.getAbsolutePath()}, null, null);
}
This is supposed to save to the 'Pictures' folder
getExternalStorageDirectory() returns the root of external storage, not some location inside of it (e.g., Pictures/, DCIM/, Movies/).
I have also tried Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) with the same result
That will give you the proper directory, or whatever the device thinks the proper directory is (usually named Pictures/ in external storage for whoever the current user is).
but Environment.getExternalStorageDirectory().getAbsolutePath() seems to create a new file structure like this file://storage/emulated/11/Pictures/
The 11 is a bit unusual, and getAbsolutePath() does not return something with a file:// scheme, but otherwise that seems about right.
But this is not the default 'Pictures' folder on any of my test devices or emulators.
I do not know how you have determined this.
I would like to know if there is any way of saving to this folder, while also setting my own filename.
Start with your first sample, switching to DIRECTORY_PICTURES. Then:
have outputStream be a FileOutputStream
call outputStream.getFD().sync() after flush() and before close()
use MediaScannerConnection and its scanFile() method to arrange for the MediaStore to index the image, so it is available to on-device galleries, desktop OS file managers, etc.
With Android 6.0 Marshmallow (API >= 23), Google introduced a new permission model.
You have to request permission at run-time:
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
requestPermissions(permissions, WRITE_REQUEST_CODE);
and handle the result in onRequestPermissionsResult(),
File path = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
File file = new File(path, "db.JPG");
try {
file.createNewFile();
}
catch (IOException e) {
Toast.makeText(this.getApplicationContext(),
"createNewFile:"+e.toString(),
Toast.LENGTH_LONG).show();
}
then
if (Build.VERSION.SDK_INT < 19) {
sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
Uri.parse("file://" + filename)));
}
else {
sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE,
Uri.parse("file://" + filename)));
}
to show in the gallery.
Related
I followed the answers of Saving an image from ImageView into internal storage but I still can't save anything.. My code is here :
public void buttonPickImage(View view) {
FileOutputStream fos;
bitmap = ((BitmapDrawable)imageView.getDrawable()).getBitmap();
Random rng = new Random();
int n = rng.nextInt(1000);
try {
File sdCard = Environment.getExternalStorageDirectory();
File dir = new File(sdCard.getAbsolutePath() + "/BAC");
bool = dir.mkdir();
File file = new File(dir, "BAC_"+n+".jpg");
fos = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.JPEG,100,fos);
fos.flush();
fos.close();
Toast.makeText(getApplicationContext(),"Image sauvegardée"+bool,Toast.LENGTH_SHORT).show();
}catch (java.io.IOException e){
e.printStackTrace();
Toast.makeText(getApplicationContext(),"IOException: " + e.getMessage(),Toast.LENGTH_SHORT).show();
}
}
With this method I get the IOExeception with messae : java.io.FileNotFoundException: /storage/emulated/0/BAC/BAC_396.jpg: open failed: ENOENT (No such file or directory)
I also tried this to save it to internal storage but its not working for me :
https://www.tutorialspoint.com/how-to-write-an-image-file-in-internal-storage-in-android
With this method, program runs but boolean mkdir gives me false.
Thanks for helping me
Finally got it working using Media Store instead of getExternalStorageDirectory as
This method was deprecated in API level 29.
To improve user privacy, direct access to shared/external storage devices is deprecated. When an app targets Build.VERSION_CODES.Q, the path returned from this method is no longer directly accessible to apps. Apps can continue to access content stored on shared/external storage by migrating to alternatives such as Context#getExternalFilesDir(String), MediaStore, or Intent#ACTION_OPEN_DOCUMENT.
MediaStore is also useful as it allows you to get the image in your android gallery app.
So my solution is :
ImageView imageView = findViewById(R.id.image);
Bitmap bitmap = ((BitmapDrawable)imageView.getDrawable()).getBitmap();
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.TITLE, "any_picture_name");
values.put(MediaStore.Images.Media.BUCKET_ID, "test");
values.put(MediaStore.Images.Media.DESCRIPTION, "test Image taken");
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
OutputStream outstream;
try {
outstream = getContentResolver().openOutputStream(uri);
bitmap.compress(Bitmap.CompressFormat.JPEG, 70, outstream);
outstream.close();
Toast.makeText(getApplicationContext(),"Success",Toast.LENGTH_LONG).show();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(),e.getMessage(),Toast.LENGTH_LONG).show();
}
Still thank you #blackapps for explaining me some basics things about the IOexception, mkdir and toasts. It'll be useful anyway.
I'm trying to found a way (compatible with android kitkat and next) to write photos on the SD Card and make them visible to the gallery app.
If I use Environment.getExternalStoragePublicDirectory , samsung devices return a path in internal memory (/storage/emulated/0/ ... )
If I use Context.getExternalFilesDirs , there are two results : the first one on internal storage, and the second one on SD Card. Ok, I can write inside and the photo is on the SDCard. But I can't see it in the Galery app :'(
I have tried to write directly on /storage/externalSdCard/DCIM/ but of course I can't since I'm running kitkat.
Ok, I can write inside and the photo is on the SDCard. But I can't see it in the Galery app
First, when you are done writing to the file, call flush(), then getFD().sync(), then close(), all on your FileOutputStream.
Then, use MediaScannerConnection and its scanFile() method to get the newly-written file indexed by the MediaStore.
void saveImage() {
File filename;
try {
String path = Environment.getExternalStorageDirectory().toString();
new File(path + "/folder/subfolder").mkdirs();
filename = new File(path + "/folder/subfolder/image.jpg");
FileOutputStream out = new FileOutputStream(filename);
bitMapImg.compress(Bitmap.CompressFormat.JPEG, 90, out);
out.flush();
out.close();
MediaStore.Images.Media.insertImage(getContentResolver(), filename.getAbsolutePath(), filename.getName(), filename.getName());
Toast.makeText(getApplicationContext(), "File is Saved in " + filename, 1000).show();
} catch (Exception e) {
e.printStackTrace();
}
}
I was looking for a way to save a bitmap to a folder (with my app's name), in such a way the default gallery will recognize it.
So I managed to save the image, but I can't see it either from the gallery or my PC (using explorer)
this is my code:
// Save bitmap to internal memory
private void savePhoto(Bitmap bmp){
String appName = "myApp";
String file_path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)+ File.separator + appName;
File dir = new File(file_path);
if(!dir.exists())
dir.mkdirs();
// Image file
File file = new File(dir, "IMG" + "_" + System.currentTimeMillis() + ".jpg");
FileOutputStream out = null;
try
{
out = new FileOutputStream(file);
bmp.compress(Bitmap.CompressFormat.JPEG, 100, out);
out.flush();
out.close();
out = null;
} catch (Exception e)
{
e.printStackTrace();
}
}
I can see it using some File Manager installed on my galaxy, but it doesn't help a lot.
I know I should inform the gallery using a media scanner (which is another bridge I need to cross) but may someone help me understand way I can't even find the file..should I change its visibility somehow?
and one more thing: I read at another question regarding the issue, that I should add metadata to the image/folder so that the gallery would show it. Is it necessary?
Many thanks!!!
Apparently it did have something to do with the media scanner, I added this line
MediaScannerConnection.scanFile(this, new String[] { file.getAbsolutePath() }, null, null);
and now it works.
from the documentation:
MediaScannerConnection provides a way for applications to pass a newly created or downloaded media file to the media scanner service. The media scanner service will read metadata from the file and add the file to the media content provider.
try to replace this getExternalStoragePublicDirectory(....)
with this one getExternalStorageDirectory();
I am working on an app in which I would like to save some Bitmaps to the SD Card. I have looked at a lot of examples and other questions, and from that I have made the following code:
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream);
String dirPath = Environment.getExternalStorageDirectory().toString() + "/myFolder";
File dir = new File(dirPath);
dir.mkdirs();
String fileName = "bitmapname.jpg";
File file = new File(dirPath, fileName);
FileOutputStream fileOutPutStream;
try {
boolean created = file.createNewFile();
Log.d("Checks", "File created: " + created);
fileOutPutStream = new FileOutputStream(file);
fileOutPutStream.write(byteArrayOutputStream.toByteArray());
fileOutPutStream.close();
} catch (FileNotFoundException e) {
Log.d("Checks", "FileNotFoundException");
e.printStackTrace();
} catch (IOException e) {
Log.d("Checks", "IOException");
Log.d("Checks", e.getMessage());
e.printStackTrace();
}
I don't see what's wrong with this code. It doesn't give any errors and my app runs without crashing. However, when I connect my phone to my computer and open the SD Card I do not see the folder "myFolder" and I can not find the saved image anywhere. Do you guys have any ideas as to why this is?
EDIT: I noticed that I can see the saved bitmaps in the Android gallery, and they are indeed in a folder called "myFolder". However, I still don't see them when I connect my phone to my computer and browse my sd card.
From my experience I had similar issued when I forgot the fileOutPutStream.flush(); before the close().
Are you sure you are setting the permission to write to SD card? Try setting this one:
WRITE_EXTERNAL_STORAGE
Edit:
Ok, try this:
Environment.getExternalStorageDirectory().getAbsolutePath()
Instead of:
Environment.getExternalStorageDirectory().toString()
Or even create a directory like this:
File dir = new File(Environment.getExternalStorageDirectory() +
File.separator +
"myFolder");
dir.mkdirs();
I got this strange problem when I want to save images to Android from my app. The images get damaged now Android cant use them. I cant see whats wrong in my code.
Here's my download method:
public void createExternalStoragePublicPicture(String DownloadUrl, String fileName) {
// Create a path where we will place our picture in the user's
// public pictures directory. Note that you should be careful about
// what you place here, since the user often manages these files. For
// pictures and other media owned by the application, consider
// Context.getExternalMediaDir().
Log.i("Download", DownloadUrl);
Log.i("File", fileName);
File path = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
File file = new File(path, fileName);
try {
// Make sure the Pictures directory exists.
path.mkdirs();
// Very simple code to copy a picture from the application's
// resource into the external file. Note that this code does
// no error checking, and assumes the picture is small (does not
// try to copy it in chunks). Note that if external storage is
// not currently mounted this will silently fail.
URL url = new URL(DownloadUrl);
URLConnection ucon = url.openConnection();
InputStream is = ucon.getInputStream();
OutputStream os = new FileOutputStream(file);
byte[] data = new byte[is.available()];
is.read(data);
os.write(data);
is.close();
os.close();
// 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);
}
});
} catch (IOException e) {
// Unable to create file, likely because external storage is
// not currently mounted.
Log.w("ExternalStorage", "Error writing " + file, e);
}
}
Any ideas what is going wrong here?
Possibly, the is.available() does not work as you expect. Generally you should not rely on available() as it is not strictly specified what value it returns.
See InputStream: "It is particularly important to realize that you must not use this method to size a container and assume that you can read the entirety of the stream [...]")
Use the common "loop"-approach:
int c;
while ( (c = is.read(buf)) >= 0 ) {
os.write(buf,0,c)
}
This makes sure you really read all of the bytes from the source file.