Confused on how to access files from a ZipResourceFile (APK extension file) - android

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;
}
}
};

Related

How to know if removed File monitored by File Observer is a Directory or a File

I'm using FileObserver to monitor changes in folders.
Events are triggered as expected but I have problem making a distinction between Files and Directories in the events DELETE and MOVED_FROM, since after the event is triggered, calling both File.isFile() and File.isDirectory() is false (which make sense).
Is there an efficient way to make this check before file is removed? I do have a workaround by listing all files in effected folder, however it's inefficient.
Fileobserver code:
mFileObserver = new FileObserver(DIRECTORY.getPath()) {
#Override
public void onEvent(int event, String path) {
event &= FileObserver.ALL_EVENTS;
switch (event) {
case (CREATE):
case (MOVED_TO):
Log.d(TAG, "Added to folder: " + DIRECTORY + " --> File name " + path);
addChild(path);
break;
case (DELETE):
case (MOVED_FROM):
Log.d(TAG, "Removed from folder " + DIRECTORY + " --> File name " + path);
removeChild(path);
break;
case (MOVE_SELF):
case (DELETE_SELF):
removeDirectory();
break;
}
}
};
EDIT:
This is how is File/Folder is evaluated in removeChild(String)
private void removeChild(String name) {
mFileObserver.stopWatching();
String filepath = this.getAbsolutePath() + separator + name;
File file = new File(filepath);
if (file.exists())
Log.d(TAG, "Exists");
else Log.d(TAG, " Does not Exists");
if (file.isDirectory())
Log.d(TAG, "is Directory");
else Log.d(TAG, " is NOT Directory");
if (file.isFile())
Log.d(TAG, "is File");
else Log.d(TAG, " is NOT File");
}
And the relevant logcat output is:
04-03 12:37:20.714 5819-6352: Removed from folder /storage/emulated/0/Pictures/GR --> File name ic_alarm_white_24dp.png
04-03 12:37:20.714 5819-6352: Does not Exists
04-03 12:37:20.714 5819-6352: is NOT Directory
04-03 12:37:20.714 5819-6352: is NOT File
Is there an efficient way to make this check before file is removed?
Unfortunately, not that I'm aware of. Which makes sense - filesystem events are things that have already happened.
FileObserver uses inotify to get events. A good description of inotify functionality can be found at https://www.ibm.com/developerworks/library/l-inotify/:
Monitor Linux file system events with inotify
...
Before inotify there was dnotify. Unfortunately, dnotify had
limitations that left users hoping for something better. Some of the
advantages of inotify are:
Inotify uses a single file descriptor, while dnotify requires opening one file descriptor for each directory that you intend to
watch for changes. This can be very costly when you are monitoring
several directories at once, and you may even reach a per-process file
descriptor limit.
The file descriptor used by inotify is obtained using a system call and does not have an associated device or file. With dnotify, the file
descriptor pins the directory, preventing the backing device to be
unmounted, a particular problem with removable media. With inotify, a
watched file or directory on a file system that is unmounted generates
an event, and the watch is automatically removed.
Inotify can watch files or directories. Dnotify monitors directories, and so programmers had to keep stat structures or an
equivalent data structure reflecting the files in the directories
being watched, then compare those with the current state after an
event occurred in order to know what happened to the entry in the
directory.
As noted above, inotify uses a file descriptor, allowing programmers to use standard select or poll functions to watch for
events. This allows for efficient multiplexed I/O or integration with
Glib's mainloop. In contrast, dnotify uses signals, which programmers
often find more difficult or less than elegant. Signal-drive I.O
notification was also added to inotify in kernel 2.6.25.
The API for inotify
...
Note that there are no mentions of "events about to happen", or anything like that.
You don't need to keep a list of all files - you just need a list of directories - a simple Set<String> should do just fine. If the deleted String path is in the set, it was a directory.
For a more robust approach, when you start your watch you can also put a FileObserver watch on all directories in the directory you're primarily watching (also add a watch to every directory created in your primary directory after you've created your watcher).
Then if you get a DELETE_SELF from one of the child FileObserver objects, you'll know it was a directory. If the event doesn't have an associated child FileObserver object that got a DELETE_SELF event, it wasn't a directory.
For an extremely large directory, this approach admittedly will have scalability problems...

Where should I choose to save text file in android?

I hope to export my data as a text file and save it to disk in Android, so I need to choose which folder I will save the file to.
I hope that a normal user can find the folder easily and the app does not need special permission to create the folder.
I have read some document, it seems that there are 3 ways: Context.getFilesDir().getAbsolutePath(), Environment.getExternalStorageDirectory().getAbsolutePath() and Context.getExternalFilesDir(null).
You know some android users don't install SD card, so it seems that Environment.getExternalStorageDirectory().getAbsolutePath() and Context.getExternalFilesDir(null) are be excluded.
Am I only to choose Context.getFilesDir().getAbsolutePath()? or is there a better way? Thanks!
BTW, From the document Android - Where to save text files to?
Save it in internal phone storage, here no users and applications can access these files(unless if phone is rooted). But these files will be deleted one's the user selectes clear data from Settings -> Apps -> .
It seems that normal users can't access the saved text files if I use Context.getFilesDir().getAbsolutePath(), is it right?
Use this if you want a path that the user can modify and can have access
getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getAbsolutePath();
More documentation here.
EDIT:
This is how use in case error in some devices:
File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
String fname = "TEXT.txt";
File file = new File(path, fname);
if (!path.exists()) {
//noinspection ResultOfMethodCallIgnored
path.mkdir();
}
// YOUR CODE FOR COPY OR CREATE THE FILE TXT in PATH WITH THE VARIABLE file ABOVE

Android DownloadManager remove() - how to determine when the remove() operation finishes?

Is there an easy mechanism to determine when a DownloadManager remove() has completed, as it appears to be partially asynchronous. The function returns almost instantaneously with a count of the entries in the Download table it's deleted, but the actual filesystem housekeeping appears to be pushed into some background thread.
Issue is I've written a little bit of code that looks for and deletes any existing DownloadManager entry for file X (and hopefully the filesystem object), before pulling a new copy. Unfortunately the new copy is making it to the directory before the filesystem housekeeping has kicked in, for the previous incarnation. So the housekeeping actually ends up deleting the new version at some point and leaving an orphan entry in the DownloadManager table.
Could do with some way to block till the filesystem delete is actioned.
Debug code:
DownloadManager.Query query = new DownloadManager.Query().setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
downloads = getAllDownloadIds(manager.query(query));
path = activity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS);
//If a file name was passed remove any existing entry / version of the file
if ( fileName != null && ! fileName.isEmpty() ){
if ( downloads.containsKey(fileName)){
ids = downloads.get(fileName);
for (Long id : ids) {
Uri path = manager.getUriForDownloadedFile(id);
File checkFile = new File(path.toString());
Log.e(TAG, "Removing existing file: " + path.toString() + ":" + id);
int entriesRemoved = manager.remove(id);
Log.e(TAG, "Existing files removed: " + entriesRemoved);
}
}
}
...
Log.v(TAG, "Attempting to create a file in the 'Download' directory on the external storage::" + path.toString() +"/"+ fileName);
file = new File(path, fileName);
Log.v(TAG, "Does the file already exist::" + file.exists());
Example output:
… V/Export﹕ Removing existing file: file:///storage/sdcard/Download/appData.csv:101
… V/Export﹕ Existing files removed: 1
… V/Export﹕ Attempting to create a file in the 'Download' directory on the external storage::/storage/sdcard/Download/appData.csv
… V/Export﹕ Does the file already exist::true
I had this same problem - when replacing small files in a fast network, the replacement files would sometimes arrive within a fraction of a second after calling DownloadManager.remove(...) In this case, the newly arrived files would be deleted.
The solution I'm using is, before I call DownloadManager.remove(...), I setup up a FileObserver to monitor the file. I then call remove(...), but then wait for the DELETE event to fire before initiating the replacement download.
This ended being a significant amount of code distributed among multiple classes. There are other complicating factors - for example I put a timeout mechanism in just in case the DownloadManager never deletes the file. (I can't imagine why it wouldn't, but it's not my component).
So in answer to the question "Is there an easy mechanism.....": There are mechanisms available to you, but unfortunately not particularly easy ones.
The way i'm solved this timing problem is just to delete the file before DownloadManager.remove function. The reference to the download will be removed by the "remove" function anyway.
int idFromCursor = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_ID));
String localPathOfFile =cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_FILENAME));
File fileToDelete= new File(localPathOfFile);
if(fileToDelete.exists()){
fileToDelete.delete();
}
downloadManager.remove(idFromCursor);
after that there was no timing problems anymore.

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.

How to prevent data on external storage getting deleted on reinstall/update?

I notice that my app's data on external storage (i.e. SD card) gets deleted during an "adb install -r". While this is normal for uninstall (and then afterwards install optionally to notice it), I do not understand why this is the case for reinstall (and thus for Market updates as well). I could have sworn this was not always the case.
Referring to http://developer.android.com/guide/topics/data/data-storage.html#filesExternal I am specifically using "Accessing files on external storage" on Android 2.2, but not "Saving files that should be shared" or "Saving cache files". So I am writing and reading data in "/sdcard/Android/data//files/somefolder/data". My preferences do stick.
#Commonsware: The problem is not so much with getExternalFilesDir() IMHO as I see my data is written where I expect it. It just does not stick. I am using a.o.:
public static File getDefaultDirectory(Context context, String packageName) {
File dir;
if(mActivity_getExternalFilesDir!=null){//API >=8
dir = invokeGetExternalFilesDir(context, "fortune");
}else if(mEnvironment_getExternalStorageDirectory!=null){//API <=7
dir = invokeGetExternalStorageDirectory();
dir = new File(dir.getPath() + "/Android/data/" + packageName + "/files/");
}else{
//should never occur
return null;
}
return dir;
}
IIRC, there is a bug in Android 2.2 that causes these symptoms. I advise against the use of getExternalFilesDir() until Gingerbread.

Categories

Resources