How to Caching Bitmaps in a ContentProvider? - android

How do you comprehend this note
Note: A ContentProvider might be a more appropriate place to store
cached images if they are accessed more frequently, for example in an
image gallery application.
in this training article https://developer.android.com/training/displaying-bitmaps/cache-bitmap.html? Since I can't get Bitmap or File from Cursor, how can I cache Bitmaps via ContentProvider?

You actually can read and write Files using a ContentProvider.
To support this in your own ContentProvider you'll have to include your supported File MIME types in the getStreamTypes() method. Check the MIME types section of the Android ContentProvider tutorial here for more info.
You will also need to implement the openFile(Uri uri, String mode) method which is where you'll actually choose the File directory and name based on the Uri provided to the ContentResolver. Here's a sample implementation of that method:
#Override
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
File root = getContext().getFilesDir();
File path = new File(root, uri.getEncodedPath());
path.mkdirs();
File file = new File(path, "file_"+uri.getLastPathSegment());
int imode = 0;
if (mode.contains("w")) {
imode |= ParcelFileDescriptor.MODE_WRITE_ONLY;
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (mode.contains("r"))
imode |= ParcelFileDescriptor.MODE_READ_ONLY;
if (mode.contains("+"))
imode |= ParcelFileDescriptor.MODE_APPEND;
return ParcelFileDescriptor.open(file, imode);
}
You can use whatever logic you'd like here to choose your File directory. This code just uses the applications files directory, but for the purposes of Bitmap caching this should probably use a temp cache directory.
Finally, your code to access the ContentProvider file data should look something like this:
ContentResolver cr = getContext().getContentResolver();
InputStream inputStream = cr.openInputStream(uri);
Alternatively you'd use ContentResolver.openOutputStream(uri) to write your file data to the ContentProvider.
The Bitmap caching tutorial would require a fair bit of modifications to use a ContentProvider as the Disk cache, but I do believe this is what that note was referring to.

I highly recommend using the https://github.com/nostra13/Android-Universal-Image-Loader library for downloading and caching images
You can download the library as a JAR-file that is easily included into any Android project
Features from the official page:
->Multithread image loading
->Possibility of wide tuning ImageLoader's configuration (thread pool size, HTTP options, memory and disc cache, display image options, and others)
->Possibility of image caching in memory and/or on device's file sysytem (or SD card)
->Possibility to "listen" loading process
->Possibility to customize every display image call with separated options Widget support

The mentioned passage emphasizes that instead of finding and processing all data just when it is requested, You better implement a separate content model (or use built in ones) which silently indexes data and also saves the previews (thumbnails of images, or first 2 lines of a text files etc) on disk. This achieves:
Less RAM used, since pre-processed data is ready on disk. For example, a really long List view need not load all the previews as Drawable's at once in memory. It might just ask for cached data from the content model/provider in small batches to the in-memory cache as it scrolls.
Save CPU. In-memory cache is volatile and needs recreation again, while on disk cache saves us from this extra processing.

Related

Managing local images with persistent data

I'm struggling to understand what could be a simple workaround for associating images and persistent data objects in android. In more details, i've put up a simple room persistence architecture and now I need to add a field "image" to the java persisted object. I've tried to work with uri but my knowledge of Android is very poor, and what I get is that the uri I recover when picking an image with the android file manager is only valid until reboot, so if I would save the so obtained uri in the database, it would make no sense when recovered later. How should I manage?
Basically what I need is a simple way to link an object to a local image stored in the phone (or captured on the fly with the camera), no worries about image deletion by the user or anything, just a simple way.
For istance I tried to tinker with the google code example but i clearly failed because I don't know what i'm doing
private Bitmap getBitmapFromUri(Uri uri) throws IOException {
ParcelFileDescriptor parcelFileDescriptor =
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor);
parcelFileDescriptor.close();
return image;
}
this code results in compilation error, with required: parcedDescriptor... and VOID found, on the call of takePersistableUriPermission. I don't even know if that is a solution to my problem.
this is the code I use to get the uri from the local image, but I'm planning also to let the Camera snap a photo and pass it to for saving/linking it
// ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
// browser.
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
// Filter to only show results that can be "opened", such as a
// file (as opposed to a list of contacts or timezones)
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Filter to show only images, using the image MIME data type.
// If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
// To search for all documents available via installed storage providers,
// it would be "*/*".
intent.setType("image/*");
startActivityForResult(intent, READ_REQUEST_CODE);
the uri I recover when picking an image with the android file manager is only valid until reboot, so if I would save the so obtained uri in the database, it would make no sense when recovered later
That's not quite accurate.
A Uri that you pull in via ACTION_OPEN_DOCUMENT will be good for whatever activity gets the Uri via onActivityResult(). If you pass that Uri to another component, you can use FLAG_GRANT_READ_URI_PERMISSION to allow that component to read the content at that Uri. But once your process ends, your access to that content goes away.
Since you used ACTION_OPEN_DOCUMENT, you can use takePersistableUriPermission() request to have long-term access to the content, but that still only works if the content is still there. If the user deletes the content, or perhaps even moves it, you will lose access.
For istance I tried to tinker with the google code example but i clearly failed because I don't know what i'm doing
takePersistableUriPermission() does not return a ParcelFileDescriptor. Otherwise, that particular call seems OK.
With respect to loading the image, please use an existing image-loading library (e.g., Glide, Picasso).

How can I copy a remote image over http to gallery folder in android?

I want to copy a remote image, for example "http://example.com/example.jpg" to the android user phone built gallery...How can I do it?
To that, you should download the image and save it in internal memory.
You can download the image by yourself:
public static Bitmap getBitmap(String url) {
try {
InputStream is = (InputStream) new URL(url).getContent();
Bitmap d = BitmapFactory.decodeStream(is);
is.close();
return d;
} catch (Exception e) {
return null;
}
}
Code from here But you will have memory problems with large images. I strongly recommended you to use a build library like Android Universal Image Loader or Picasso from square
Here you can find an example of how to use the Android DownloadManager to download your file.
The destination path can be determined using the contants defined in the Environment class. Constant DIRECTORY_DCIM points to the parent directory under which all Activities can create a custom folder where they store their images. You could make your own child folder as destination folder
When your image finishes downloading, you will notice that it will not be listed in the default gallery application, this is because Android builds an index with all the media files and is still unaware of your new downloaded image. This index is updated each time you boot your Android device, but since it's a bit unconvienient to reboot your device each time a file is added, you can also codewise inform the indexing service that a new file is created and needs indexing using this piece of code:
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(file));
sendBroadcast(intent);
This scanning should also occur after a file has been erased.

Android Creating a memory resident input file that can be attached to an email

The final objective will be clear shortly.
I want to create a file object and instead of getting data from a real physical file I want to provide the buffer myself.
Then, I want to use this file, which does not really exist in the sdcard or anywhere outside my app, give it a name and send it by email as an attachment (using the EXTRA_STREAM).
I found the following bit of code, by Adriaan Koster (#adriaankoster), the post Write byte[] to File in Java
// convert byte[] to File
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
File fileFromBytes = (File) ois.readObject();
bis.close();
ois.close();
System.out.println(fileFromBytes);
I used it to create this function
private File fileFromBytes(byte[] buf) {
File f = null;
try {
ByteArrayInputStream bis = new ByteArrayInputStream(buf);
ObjectInputStream ois = new ObjectInputStream(bis);
f = (File) ois.readObject();
bis.close();
ois.close();
}
catch (Exception e) {}
return f;
}
and here is where I am stuck, because when I use it:
// When sent as body the mail is sent OK
// emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, dump());
// When I try to attach the mail is empty
emailIntent.putExtra(android.content.Intent.EXTRA_STREAM, fileFromBytes(dump().getBytes()));
I know from examples I've seen the second argument should be an URI, but: How do I create a virtual URI to fit my file?
EDIT:
The option to attach data directly from within the application is important to certain kind of applications. Namely, security & banking applications that do not want to move sensitive data around too much. Surely if the data does not reach the sdcard and goes directly to a mail attachment it is harder to sniff than within the application memory.
This is not my specific case, but I wanted to point out that this capability is important to have.
The first thing you'll want to do, I imagine, is create a ContentProvider. You can see an example implementation here
https://github.com/dskinner/AndroidWeb/blob/master/src/org/tsg/web/WebContentProvider.java
where in the above link's case, you would add this to your AndroidManifest.xml
<provider
android:name="org.tsg.web.WebContentProvider"
android:authorities="your.package.name" />
Now, you'll have a content uri available for use, content://your.package.name/.
The portion of the above ContentProvider your interested in, again I imagine, is the openFile method. When sharing data by intent across apps, certain things are expected. In your case, you're looking to share some byte data that's meant to be attached to the email.
So if you pass in a content uri to the email app such as content://your.package.name/foo with the appropriate intent flags, then openFile will get called on your ContentProvider. In this case, you can inspect the end of the uri segment to see foo was requested, and return appropriately.
The next issue you bring up is not having the file actually on disk. While I can't vouch for the method you used above (though it looks kosher), what you need to be returning is a ParcelFileDescriptor from your ContentProvider. If you look at the link I provided, you could possibly try to use that as a sample to get the file descriptor from your File object (my knowledge waivers here), but I imagine, the data simply wont be available at that point.
What you do bring up is security though. It's important to note that you can write data to disk privately so only the app has access to the data. I believe, but you might want to double check on this, if that data is private to the app, you can expose it via the ContentProvider and possibly lock down who and how the provider gets used, who can call it, etc. You may want to dig into android docs for that portion or look at some other SO questions.
Anyway, good luck.
Create the file in the application's cache directory. It will be created in the internal filesystem. Use 'getCacheDir()' API for getting the path to the cache dir. Write the data into this dir and then get the URI from the File object using ' Uri.fromFile (File file) '. When you are finished with the file, delete it.
Your application's cache is only available to your app, hence its safe to use for your purpose.
You can do some encryption if the data is too critical.
I think in order to do this, you are going to have to expose a ContentProvider, which will allow you handle a URI. The email application should then openInputStream on your URI, at which point you return an InputStream on your in-memory data.
I've not tried it, but in theory this should work.
i was busy with adding attachment to mail and i can send mail with attachment.
if you want to take a look: can not send mail with attachment in Android

When to clear the cache dir in Android?

I have an application that displays pictures from the internet (showcase for designer work). I start caching my content in the internal cache directory, but the app content could take about 150 MB in cache size. And what android docs says :
You should always maintain the cache files yourself and stay within a
reasonable limit of space consumed, such as 1MB. When the user
uninstalls your application, these files are removed.
So I took a look at the Currents app (Galaxy Nexus) and the cache size for the application is 110 MB. But what's weird is that applications like Google Currents & Google Maps cache the content in something called (USB Storage Data) :
So what is this 'USB Storage Data' that the previous application uses. And if you implement caching in your application, Do you loop over all your application files in cache to get the size every time you need to insert something and then compare and clear it? Or do you keep caching the content until Android decides its time to clean some application cache directory ?
I'm really interested to know what is the flow of managing cache in Android, or at least what other applications do with large content to cache.
Before I get to your question, here's a brief explanation of the two storage types:
Cache
This is an app-specific directory on the filesystem. The intent for this directory is store temporary data your application may need to keep around between sessions, but may not be vital to keep them forever. You typically access this directory with Context.getCacheDir(). This will show up as "Cache" on your app settings.
Files
Like the cache directory, your app also has an app-specific directory for holding files. Files in this directory will exist until the app explicitly deletes them or the app is uninstalled. You typically access this directory with Context.getFilesDir(). This can show up as various things on the app info screen, but in your screenshot this is "USB Storage Data".
NOTE: If you want to explicitly place on external media (typically SD card), you can use Context.getExternalFilesDir(String type).
The Difference
Both directories are specific only to your application (other apps do not have access). One of the differences between the cache and files directory is that if the system gets low on storage, the first place it is going to free resources is from your cache directory. The system will not clear any data from the files directory. Another difference is that the cache directory can typically be cleared manually from the app info screen. The files directory typically can as well, but clearing the files directory will also clear the cache directory.
Which one do I use?
It depends on how vital that data is compared to the lifetime of your app. If you only need data for one session and you doubt you'll ever need to use that data again, then don't use either. Just keep it in memory until you don't need it. If you suspect you'll need to reuse the data between multiple sessions, but you don't have to keep a hard copy, use the cache directory. If you must have this data no matter what, or if it's rather large data that needs persistent storage, use the files directory. Here's some examples I can think of:
Cache - A recently opened email
Once opened, cache the data so when the user wants to read that email again, it loads instantly rather using the network again to retrieve the same data. I don't need to keep this forever, because eventually the user will be finished with the email.
Files - An attachment downloaded from an email
This is an action by the user who is saying "I want to keep this data so I can pull it back up whenever I need it." Therefore, put it in files directory as I don't ever want to delete this file until the user wants it deleted.
When should I clear the cache directory?
From the Context.getCacheDir() javadocs:
Note: you should not rely on the system deleting these files for you;
you should always have a reasonable maximum, such as 1 MB, for the
amount of space you consume with cache files, and prune those files
when exceeding that space.
It uses the example of 1 MB, but that may or may not be reasonable for your app. Regardless, you need to set a hard maximum. The reason for this simply comes down to designing a responsible app. So when should you check? I would recommend checking every time you want to put something in the cache directory. Here's a very simple cache manager:
public class CacheManager {
private static final long MAX_SIZE = 5242880L; // 5MB
private CacheManager() {
}
public static void cacheData(Context context, byte[] data, String name) throws IOException {
File cacheDir = context.getCacheDir();
long size = getDirSize(cacheDir);
long newSize = data.length + size;
if (newSize > MAX_SIZE) {
cleanDir(cacheDir, newSize - MAX_SIZE);
}
File file = new File(cacheDir, name);
FileOutputStream os = new FileOutputStream(file);
try {
os.write(data);
}
finally {
os.flush();
os.close();
}
}
public static byte[] retrieveData(Context context, String name) throws IOException {
File cacheDir = context.getCacheDir();
File file = new File(cacheDir, name);
if (!file.exists()) {
// Data doesn't exist
return null;
}
byte[] data = new byte[(int) file.length()];
FileInputStream is = new FileInputStream(file);
try {
is.read(data);
}
finally {
is.close();
}
return data;
}
private static void cleanDir(File dir, long bytes) {
long bytesDeleted = 0;
File[] files = dir.listFiles();
for (File file : files) {
bytesDeleted += file.length();
file.delete();
if (bytesDeleted >= bytes) {
break;
}
}
}
private static long getDirSize(File dir) {
long size = 0;
File[] files = dir.listFiles();
for (File file : files) {
if (file.isFile()) {
size += file.length();
}
}
return size;
}
}
Of course, this could be an expensive operation, so you should plan on caching on a background thread.
Also, this could be as complicated as you need it to be. In my example, I'm assuming all cached files are placed at the root of the cache directory, so I don't check for potential sub-directories. The routine for deleting files can also become more sophisticated, such as deleting files by oldest access date.
One thing to keep in mind when deciding to cache data is that you need to always plan for the case that your cached data no longer exists. Always have a routine in place to retrieve data by external means when your cache doesn't have it in storage. Likewise, always check your cache before retrieve data externally. The purpose of the cache is to cut down on network activity, long processes, and provide a responsive UI in your app. So use it responsibly :)
i thing best way to clearing app cache when activity finish so that every time cache clear when new activity call.
put this code in onDestroy() for clear app cache
#Override
protected void onDestroy() {
super.onDestroy();
try {
trimCache(this);
// Toast.makeText(this,"onDestroy " ,Toast.LENGTH_LONG).show();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void trimCache(Context context) {
try {
File dir = context.getCacheDir();
if (dir != null && dir.isDirectory()) {
deleteDir(dir);
}
} catch (Exception e) {
// TODO: handle exception
}
}
public static boolean deleteDir(File dir) {
if (dir != null && dir.isDirectory()) {
String[] children = dir.list();
for (int i = 0; i < children.length; i++) {
boolean success = deleteDir(new File(dir, children[i]));
if (!success) {
return false;
}
}
}
// The directory is now empty so delete it
return dir.delete();
}
I think the idea behind the cache is to write anything you want on it and Android will manage its size if it gets too high.
You should keep in mind that you can write files to the cache, but always checks if the file is still saved when trying to access it. And let android manage th cache.
Depends on the type of application:
Some applications only use single sessions and don't need to remember any data, so you can clear the cache when you want (some apps even do this automatically in their onStop activity)
Most application keep your data because they remember your settings, the account you have used to log in,... In this case, it's best to only clear the cache when you don't use the application a lot.
Also:
So i took a look at Chrome app (Galaxy Nexus) and the cache size for the application is 110 MB. But what wired is that applications like Google current & Google maps cache the content in something called (USB Storage Data) :
AFAIK, Usb storage data has a different use from cache: the storage is to store program specific information (like maps for a GPS app), the cache is used to store user specific information (like logins)
In case of google maps: I assume they store map data in the usb storage, and keep your settings and search history in the cache ==> map data is application specific, settings and search history are user specific
According to the documentation the system will clear the cache when the device is low on internal storage. Since API8 you have getExternalCacheDir() method that i think useful since i read you can have around 150MB of data but the drawback of the external cache it's that you will have to clean your cache directory yourself if it's get too big.

Use Camera to take a pic, save it locally -- not to SD Card

As the title states, I'm trying to launch the camera and take a picture that I can use with the APP locally. This is for the Motorola Xoom and, by default, they do not come with SD cards. Or at least the ones my client is looking into purchasing do not have SD cards.
I've found a pretty nice tutorial on how to launch the camera and I can get it to launch and everything up to it physically saving the image and then being able to re-use that image.
A lot of the solutions or other tutorials I've found only show how to save to an SD card -- not to resident memory or local to the app. It looks like it is possible to do so but am stumped. :-/
You could perhaps load it to ContentProvider so that you could use the images later for anything:
// Add some parameters to the image that will be stored in the Image ContentProvider
int UNIQUE_BUCKET_ID = 1337;
ContentValues values = new ContentValues(7);
values.put(MediaStore.Images.Media.DISPLAY_NAME,"name of the picture");
values.put(MediaStore.Images.Media.TITLE,"Thats the title of the image");
values.put(MediaStore.Images.Media.DESCRIPTION, "Some description");
values.put(MediaStore.Images.Media.BUCKET_DISPLAY_NAME,"Album name");
values.put(MediaStore.Images.Media.BUCKET_ID,UNIQUE_BUCKET_ID);
values.put(MediaStore.Images.Media.DATE_TAKEN,System.currentTimeMillis());
values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
// Inserting the image meta data inside the content provider
Uri uri = getContentResolver().insert(MediaStore.Images.Media.INTERNAL_CONTENT_URI, values);
// Filling the real data returned by the picture callback function into the content provider
try {
OutputStream outStream = getContentResolver().openOutputStream(uri);
outStream.write(buffer); // buffer is the data stream returned by the picture callback
outStream.close();
}catch (Exception e) {
Log.e(TAG, "Exception while writing image", e);
}
Check out Activity.getFilesDir() and Activity.getCacheDir() they provide access to storage where the app is stored. Also check out Environment.getExternalStoragePublicDirectory (), external, here, means outside the application's private directories. There are several others so look around the API's similarly named methods to find the best fit for you.
So one thing you should note is that when you call Environment.getExternalStorageDirectory(), this does not necessarily correspond to an external memory space (i.e. and SD card) and would return a local memory space on devices without SD card slots.
Note: don't be confused by the word "external" here. This directory can better be thought as media/shared storage. It is a filesystem that can hold a relatively large amount of data and that is shared across all applications (does not enforce permissions). Traditionally this is an SD card, but it may also be implemented as built-in storage in a device that is distinct from the protected internal storage and can be mounted as a filesystem on a computer.
http://developer.android.com/reference/android/os/Environment.html#getExternalStorageDirectory()
The same applies for Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);

Categories

Resources