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.
Related
I have made an app for Android which saves results for skeet shooting. During a session, the user either presses hit or miss. When the session is over, the user press save and the new result is appended to the json-object. After that the result is appended, it is saved to the phone via
public static void saveData(Context context) {
File path = context.getFilesDir();
File file = new File(path, "jsonUsr.json");
if (file.exists()) {
try {
FileOutputStream stream = new FileOutputStream(file);
String objString = usrObject.toString();
stream.write(objString.getBytes());
stream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Now, my friend who has my app used it when we were at a competition today. During the session when my app was running and he had started to fill in his result, he receives an sms. He opens the message and reads it. Then instead of reopen my app from the current apps running, he goes to the meny and presses the icon. Suddenly he discovers that all data is gone! Not just the current session, but all results he has entered. I cannot understand that, because there does not even exists in the code a call which deletes the saved json string file.
I have tried to imitate what he did on my phone, but it works perfectly. He has had a lot of problems with the memory with his phone. For a couple of days ago, it complained about that there were not enough memory for upgrading, so he moved things to the SD-card? Is it possible that the data has either been removed due to lack of memory or that it is moved to the SD card?
It is not so much to work with, but I do not have more. Since I cannot recreate it myself, it is hard to know exactly what has happened.
It is a good idea to always write to a new file in the same directory. If that write succeeds, move the new file onto the config file by changing its name. (this change is atomic)
That way, you will always end up with a valid file, even if the call to write fails for some reason (out of disk, toString() fails, etc.).
I am implementing an image file cache in my application to reduce the number of times these files are downloaded directly from the web server. While I know the Android OS may decide to clear files from the cache directory of my application to free up internal storage space, I'm not quite sure if this "cleanup" process can happen even during its execution.
If I could assume the OS won't mess with the cached files during execution, I would be able to use "in memory" data structures to optimize my cache implementation. For example, I could use a map (in memory) of cached entries (files stored on the cache folder) containing time stamps of cache hits and other information that might be useful to prioritize the way cached entries are recycled. Otherwise, if I have to deal with the fact that the cache cleanup process may happen anytime, anywhere, even during app execution, use of in memory data structures would be useless because they would eventually become out of sync with the real contents of the folder in the event of the OS deciding to clean up files by itself. In which case my cache system would have to be less efficient because it would be forced to keep checking the contents of the cache folder directly in the file system everytime a new file is requested by the app.
In other words, I just want to know if I can assume the contents of the cache directory wont be changed by the OS concurrently with my app because that might considerably change my cache implementation.
just delete all files from the cache directory with normal method
public void clear() {
File[] directory = getCacheDir().listFiles();
if(directory != null){
for (File file : directory ){
file.delete();
}
}
}
While the application is running, you may allocate new objects and that will remain on the RAM and not been paged out, so you must release the memory from your app by releasing the object references making the memory available to the garbage collector, but if there is any file without modification, the system may paged it out of the RAM if it wants to use that memory elsewhere. So it'll be a better approach to handle memory management on your side and not reside on system. You can do the following:
Set<SoftReference<Bitmap>> mReusableBitmaps = Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
LruCacheString mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
#Override
protected void entryRemoved(boolean evicted, String key,
BitmapDrawable oldValue, BitmapDrawable newValue) {
if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
((RecyclingBitmapDrawable) oldValue).setIsCached(false);
} else {
if (Utils.hasHoneycomb()) {
mReusableBitmaps.add
(new SoftReference<Bitmap>(oldValue.getBitmap()));
}
}
}
If you are sure that now these bitmaps no longer used you can remove them as follows:
if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
synchronized (mReusableBitmaps) {
final Iterator<SoftReference<Bitmap>> iterator
= mReusableBitmaps.iterator();
Bitmap item;
while (iterator.hasNext()) {
item = iterator.next().get();
if (null != item && item.isMutable()) {
if (canUseForInBitmap(item, options)) {
bitmap = item;
iterator.remove();
break;
}
} else {
iterator.remove();
}
}
}
}
You can also go for the picasso, this will manage all your bitmap caching
My app is very image-heavy, so I need to supply expansion files with my APK. I've chosen to store all my images in a ZIP file and use the zip library to get access to my images.
My first intuition was to unzip all files when the app is first started and store them in the app's external directory. The guide gently nudges me to take a different approach:
Reading media files from a ZIP
If you're using your expansion files to store media files, a ZIP file
still allows you to use Android media playback calls that provide
offset and length controls (such as MediaPlayer.setDataSource() and
SoundPool.load()). In order for this to work, you must not perform
additional compression on the media files when creating the ZIP
packages.
So I guess I'll just get an input stream from the zip file when I have to, but I don't really know how long I should have that zip file open.
Suppose I have a gallery activity with a ViewPager that shows one image per page. Do I open my expansion zip file in onCreate and close it in onDestroy, or do I open and close the file for every new image loaded?
From API level 9, you could use the jobb tool to package your assets and use StorageManager to mount/dismount the OBB file. You can also use this tool to encrypt the assets, if need be.
OBBs are a good way of providing large amounts of binary assets without packaging them into APKs as they may be multiple gigabytes in size. However, due to their size, they're most likely stored in a shared storage pool accessible from all programs.
...
The OBB will remain mounted for as long as the StorageManager reference is held by the application. As soon as this reference is lost, the OBBs in use will be unmounted. The OnObbStateChangeListener registered with this call will receive the success or failure of this operation.
I guess a mounted OBB file can dismount at any unexpected time (for example, when the user turns on USB mass storage), so pay extra attention to your OnObbStateChangeListener.
Example from this question:
storage = (StorageManager) getSystemService( STORAGE_SERVICE );
storage.mountObb( obbFilepath, "optional_encryption_key", myListener );
You can use obbContentPath to read files just like they would be on disk.
private final OnObbStateChangeListener myListener = new OnObbStateChangeListener() {
#Override
public void onObbStateChange(String path, int state) {
super.onObbStateChange(path, state);
d(path + " changed to state " + state);
switch (state) {
case ERROR_ALREADY_MOUNTED:
case ERROR_COULD_NOT_MOUNT:
case ERROR_COULD_NOT_UNMOUNT:
case ERROR_INTERNAL:
case ERROR_NOT_MOUNTED:
case ERROR_PERMISSION_DENIED:
case UNMOUNTED:
//TODO
break;
case MOUNTED:
String assetsPath = mStorageManager.getMountedObbPath(mPathToObb);
if (assetsPath == null) throw new NullPointerException("Could not get path to mounted OBB path");
d("Checking if " + assetsPath + "/path/to/file exists");
File f = new File(assetsPath + "/path/to/file");
d("" + f.exists());
break;
default:
break;
}
}
};
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.
I have to make a dedicated image viewer app for Android 2.x.
There are too many jpeg image files: about 2000~ jpegs, over 100MB.
I want access the image files with their file names,
but I couldn't find such an example.
By the way, is it okay to put many image files in /res/drawable folder?
I heard that the android application cannot be installed on sdcard and
the program repository is very small so 100MB app cannot be installed generally.
I found some examples which download the large data files on sdcard online,
but I cannot run a web server to host the data files,
and I must upload the fully packaged program on Android Market. (Should I build one apk file?)
What are the best practices for managing too many resource images (or something) in Android?
I think you are going to have a hard time convincing users to install a program that is 100 MB into the internal memory of their phones. It would be much better to sideload the images onto the SD card. There are a number of fairly cheap file hosting services available such as Amazon S3.
Also, you should consider allowing the users to download the images in small groups instead of in one large chunk.
The G1 has 256MB of internal storage for applications. Even on the Nexus One there's only 512MB so I think it's unlikely that anyone would want a single application taking up such a high proportion of this storage, so creating a 100MB+ .apk file isn't going to be practical.
You are right that stock android phones cannot run applications from the SD Card. (There are custom firmwares that allow this, but this isn't going to help you as only a small minority of users run these.)
You say that you cannot run a webserver, but unfortunately, I think that's your only real option here. You could dowload the images as needed and cache them on the SD Card if you had them on a webserver somewhere. Configuring a webserver to serve a whole of images is pretty straightforward, although you may need to do some work to stop people looking at the images using a web browser rather than your app if you're charging for it.
ImageView iv = new ImageView(context);
iv.setImageResource(R.drawable.icon);
To access a large array of images sitting in a directory on the SD card:
ImageView iv= ...
int imageIndex=0; //can access all image files by imageIndex, 0=first entry
List<String> ImageList=FindImages();
if(ImageList!=null && ImageList.size()>=currentIndex){
iv.setImageDrawable(Drawable.createFromPath(ImageList.get(imageIndex)));
}
//put your image files on SD in DIRECTORY
private List<String> FindImages() {
final List<String> tFileList = new ArrayList<String>();
Resources resources = getResources();
// may use array of valid image file extensions
//String[] imageTypes = ...
FilenameFilter[] filter = new FilenameFilter[imageTypes.length];
int i = 0;
/* can use string array of image types:
for (final String type : imageTypes) {
filter[i] = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith("." + type);
}
};
i++;
}*/
filter[i] = new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.endsWith(".png");
}
};
FileUtils fileUtils = new FileUtils();
File[] allMatchingFiles = fileUtils.listFilesAsArray(
new File(DIRECTORY), filter, -1);
for (File f : allMatchingFiles) {
tFileList.add(f.getAbsolutePath());
}
return tFileList;
}
Haven't tried it myself, but looks like this could work:
public BitmapDrawable(Resources res, String filepath);
https://developer.android.com/reference/android/graphics/drawable/BitmapDrawable.html#BitmapDrawable%28android.content.res.Resources,%20java.lang.String%29