Android documentations says:
Each FileObserver instance monitors a single file or directory. If a directory is monitored, events will be triggered for all files and subdirectories (recursively) inside the monitored directory.
But it does not work this way.
Anyway, then I looked in more and found
here it's been reported to Google and they have fixed it in 2.3 or 3.0 it seems. I tried to integrate the same code into my code and it failed because it's using Native functions.
Then I found this code on here with few compile errors, then i fixed it and thought of posting it here, because it might help someone else time.
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
import android.os.FileObserver;
import android.util.Log;
public class RecursiveFileObserver extends FileObserver
{
/** Only modification events */
public static int CHANGES_ONLY = CREATE | DELETE | CLOSE_WRITE | MOVE_SELF
| MOVED_FROM | MOVED_TO;
List
String mPath;
int mMask;
public RecursiveFileObserver(String path)
{
this(path, ALL_EVENTS);
}
public RecursiveFileObserver(String path, int mask)
{
super(path, mask);
mPath = path;
mMask = mask;
}
@Override
public void startWatching()
{
if (mObservers != null)
return ;
mObservers = new ArrayList();
Stack stack = new Stack();
stack.push(mPath);
while (!stack.isEmpty())
{
String parent = String.valueOf(stack.pop());
mObservers.add(new SingleFileObserver(parent, mMask));
File path = new File(parent);
File[]files = path.listFiles();
if (null == files)
continue;
for (File f: files)
{
if (f.isDirectory() && !f.getName().equals(".") && !f.getName()
.equals(".."))
{
stack.push(f.getPath());
}
}
}
for (SingleFileObserver sfo: mObservers)
{
sfo.startWatching();
}
}
@Override
public void stopWatching()
{
if (mObservers == null)
return ;
for (SingleFileObserver sfo: mObservers)
{
sfo.stopWatching();
}
mObservers.clear();
mObservers = null;
}
@Override
public void onEvent(int event, String path)
{
switch (event)
{
case FileObserver.ACCESS:
Log.i("RecursiveFileObserver", "ACCESS: " + path);
break;
case FileObserver.ATTRIB:
Log.i("RecursiveFileObserver", "ATTRIB: " + path);
break;
case FileObserver.CLOSE_NOWRITE:
Log.i("RecursiveFileObserver", "CLOSE_NOWRITE: " + path);
break;
case FileObserver.CLOSE_WRITE:
Log.i("RecursiveFileObserver", "CLOSE_WRITE: " + path);
break;
case FileObserver.CREATE:
Log.i("RecursiveFileObserver", "CREATE: " + path);
break;
case FileObserver.DELETE:
Log.i("RecursiveFileObserver", "DELETE: " + path);
break;
case FileObserver.DELETE_SELF:
Log.i("RecursiveFileObserver", "DELETE_SELF: " + path);
break;
case FileObserver.MODIFY:
Log.i("RecursiveFileObserver", "MODIFY: " + path);
break;
case FileObserver.MOVE_SELF:
Log.i("RecursiveFileObserver", "MOVE_SELF: " + path);
break;
case FileObserver.MOVED_FROM:
Log.i("RecursiveFileObserver", "MOVED_FROM: " + path);
break;
case FileObserver.MOVED_TO:
Log.i("RecursiveFileObserver", "MOVED_TO: " + path);
break;
case FileObserver.OPEN:
Log.i("RecursiveFileObserver", "OPEN: " + path);
break;
default:
Log.i("RecursiveFileObserver", "DEFAULT(" + event + ";) : " +
path);
break;
}
}
/**
* Monitor single directory and dispatch all events to its parent, with full path.
*/
class SingleFileObserver extends FileObserver
{
String mPath;
public SingleFileObserver(String path)
{
this(path, ALL_EVENTS);
mPath = path;
}
public SingleFileObserver(String path, int mask)
{
super(path, mask);
mPath = path;
}
@Override public void onEvent(int event, String path)
{
String newPath = mPath + "/" + path;
RecursiveFileObserver.this.onEvent(event, newPath);
}
}
}
How to use
-----------
RecursiveFileObserver mObserver;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
File DIRECTORY_DCIM = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM);
mObserver = new RecursiveFileObserver(DIRECTORY_PICTURES.getAbsolutePath(), RecursiveFileObserver.CREATE);
mObserver.startWatching();
Toast.makeText(getApplicationContext(), "Watching " + DIRECTORY_PICTURES.getAbsolutePath(), Toast.LENGTH_LONG).show();
}
Android 0, Me 1 :)
I'm glad they fixed it - I recently worked on an anti-virus project and ended up having to add a FileObserver to every single directory on the device.
ReplyDeleteBarry
Looks like they are not going to accept this change because of overheads.
ReplyDeletetyvm for posting!
ReplyDeleteThere are some mistakes in your code, List and Stack are without class
ReplyDeleteThanks this helps. Too bad Google doesn't care too much.
ReplyDeleteDid you get the "Type mismatch..." error when doing this:
ReplyDeletefor (SingleFileObserver sfo: mObservers)
{
sfo.startWatching();
}
I had tried this example on monitoring the change of /proc/net/tcp.. But it seems it doesn't work. It's there any suggestion about it?
ReplyDeleteOne issue with your code is that it should itself react to changes to the directory. If a directory gets created it should add an observer to the directory. Likewise if a directory is removed the file observer should be removed.
ReplyDeleteWould also suggest using the listFiles call that takes a FileFilter parameter as it excludes the . and .. for you. If you have the filter only accept directories then the array returned will be just the subdirectories.
ReplyDeleteAlso Stack class is usually a wrong choice as it is synchronized because it is a subclass of Vector (it really should be deprecated). ArrayDeque or LinkedList would probably be a better choice.
Your code does not compile without changes, even though some commenters already pointed those problems out.
ReplyDeleteThen, the switch block does not work either, because the event is not coming in a clean form, something like "if ((event & CREATE) != 0)" is needed instead.
Thanks for the basic idea, it would be great if it worked out of the box!
Can we keep observer running all the time in background using a service class
ReplyDelete