[API] 안드로이드 디렉토리와 파일 모니터링(감시) 를 위한 FileObserver.
FileObserver
안드로이드에서 제공하는 파일 모니터링을 위한 API 인 FileObserver 에 대하여 알아보겠다.
FileObserver 는 안드로이드 내부에 접근할 수 있는 각각의 파일과 디렉토리의 접근이나 변경에 대한 이벤트를 비동기적으로 받아올 수 있다.
이 클래스는 리눅스 커널에서 제공하는 파일 감시 모듈인 inotify (클릭시 위키로 이동) 의 네이티브 API 들을 JNI 를 통해서 호출한다. FileObserver 를 사용하면 내부적으로 쓰레드 하나가 (static 으로)생성된다.
우선 기본 사용법에 대하여 알아보자.
(참고 페이지 - 안드로이드 레퍼런스 : http://developer.android.com/reference/android/os/FileObserver.html)
기본 사용법:
/** * FileObserver 는 추상 클래스로 선언되어 있으므로 반드시 상속받아 사용해야 한다. */ FileObserver fileObserver = new FileObserver(/* 감시할 디렉토리 또는 파일 경로, 예 :
Environment.getExternalStorageDirectory().getAbsolutePath()*/) { @Override public void onEvent(int event, String path) { // event 값. 아래서 다루겠습니다. // path : 이 fileObserver 에 등록된 Path 와 관계있는 파일 또는 폴더의 path 값. // 예를 들어 특정 디렉토리를 감시하는 경우에서
// 그 내부의 파일 또는 디렉토리가 변경되었을때 path 값이 온다. // 이 FileObserver에 등록된 파일에 대한 이벤트가 발생하였을 때, null 을 반환한다.
// 즉, 이 곳에 등록된 path 에대한 이벤트가 발생했을 때 path 값이 null 로 반환. } }; /** * 이 메소드를 호출하면 파일 또는 디렉토리 감시를 시작하며 이벤트를 받는다. * 만약 이미 감시중이거나 파일 또는 디렉토리가 존재하지 않을경우 이 메소드 호출은 무효가 된다. */ fileObserver.startWatching(); /** * 파일 또는 디렉토리의 감시를 종료하고 이벤트를 받고 싶지 않을 때, 다음 메소드를 호출한다. * fileObserver.stopWatching(); */
FileObserver 를 사용하면서 주의해야 할 점은, FileObserver 가 GC 가 된다면 모든 파일 감시 이벤트를 보내는 것을 종료한다. 이벤트 수신을 보장받게 하려면 반드시 FileObserver 를 항상 살아있도록 만들어야 한다.
FileObserver 로 받을 수 있는 이벤트 타입들은 다음과 같다. - 이 값들은 onEvent() 메소드의 인자값으로 받는다 -
Constants | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
int | ACCESS | 파일로 부터 데이터를 읽었을 때 발생하는 이벤트 타입. | |||||||||
int | ALL_EVENTS | 모든 이벤트 타입이 포함되어 있다. 모든 이벤트 타입은 각각 2진수의 각 자리수 하나씩을 차지하고 있고, ALL_EVENTS 는 마스크 역할을 한다. 예를들어 FileObserver 를 생성할 때, 두 번째 인자값으로 이벤트 타입 값을 넣어서 원하는 이벤트만 받을 수 있도록 필터링을 할 수 있는데, 이때 ATTRIB 이벤트를 제외한 모든 이벤트를 받고 싶다면
new FileObserver("path", ALL_EVENTS ^ ATTRIB) 와 같이 사용할 수 있다. | |||||||||
int | ATTRIB | 메타 데이터(permissions, owner, timestamp)가 변경되었을 때 발생하는 이벤트 타입. | |||||||||
int | CLOSE_NOWRITE | 읽기 전용으로 열려있는 특정 파일 또는 디렉트로리를 닫았을 때 발생하는 이벤트 타입. | |||||||||
int | CLOSE_WRITE | 쓰기 모드로 열려있는 특정 파일 또는 디렉토리를 닫았을 때 발생하는 이벤트 타입. | |||||||||
int | CREATE | FileObserver 으로 디렉토리를 감시할 때, 디렉토리 내부 파일 또는 서브디렉토리가 생성되었을때 발생하는 이벤트 타입. | |||||||||
int | DELETE | 감시되고 있는 디렉토리 내부에서 파일 또는 디렉토리가 삭제 되었을때 발생하는 이벤트 타입. | |||||||||
int | DELETE_SELF | 감시되고 있는 디렉토리 또는 파일이 삭제 되었을때 발생하는 이벤트 타입. 이 이벤트가 발생하면 대상 파일이나 디렉토리에 대한 감시는 종료된다. | |||||||||
int | MODIFY | 파일이 감시되고 있을경우, 파일에 데이터가 쓰여질때 발생하는 이벤트 타입. | |||||||||
int | MOVED_FROM | 감시되고 있는 디렉토리 내부로부터 파일 또는 디렉토리가 이동했을때 발생. (물론 이름이 변경되어도 발생한다.) | |||||||||
int | MOVED_TO | 감시되고 있는 디렉토리 내부로 파일 또는 디렉토리가 이동했을때 발생. (마찬가지로 이름이 변경되어도 발생한다.) | |||||||||
int | MOVE_SELF | 감시되고 있는 디렉토리 또는 파일이 이동했을때 발생하는 이벤트 타입. 이 이벤트가 발생하여도 대상 파일에 대한 감시는 종료되지 않는다. | |||||||||
int | OPEN | 파일 또는 디렉토리가 열렸을때 발생한다. |
만약 특정 디렉토리를 감시하고 있는 상태에서는 그 내부 자식들의 파일 또는 디렉토리가 변경 되었을때만 이벤트가 발생한다. 즉, 감시 대상이 되는 디렉토리 내부의 어떤 디렉토리의 자식 디렉토리 상태가 변경되었을때는 이벤트가 발생하지 않는다.
이렇게 구구절절 설명하는 것 보다 한 번 돌려보고 눈으로 직접 확인해 보는 것이 최고다. 그래서 아래와 같은 예제 코드를 마련해 놓았다.
이 코드는 접근 가능한 영역의 모든 파일 또는 폴더를 대상으로 FileObserver 를 생성하여 감시할 수 있도록 하였으며 이벤트를 로그로 출력하도록 하였다.
MainActivity.java:
public class MainActivity extends Activity { public static final ArrayList<TestFileObserver> sListFileObserver = new ArrayList<TestFileObserver>(); static class TestFileObserver extends FileObserver { private String mPath; int[] eventValue = new int[] {FileObserver.ACCESS, FileObserver.ALL_EVENTS, FileObserver.ATTRIB, FileObserver.CLOSE_NOWRITE,FileObserver.CLOSE_WRITE, FileObserver.CREATE, FileObserver.DELETE, FileObserver.DELETE_SELF,FileObserver.MODIFY,FileObserver.MOVED_FROM,FileObserver.MOVED_TO, FileObserver.MOVE_SELF,FileObserver.OPEN}; String[] eventName = new String[] {"ACCESS", "ALL_EVENTS", "ATTRIB", "CLOSE_NOWRITE", "CLOSE_WRITE", "CREATE", "DELETE", "DELETE_SELF" , "MODIFY" , "MOVED_FROM" ,"MOVED_TO", "MOVE_SELF","OPEN"}; public TestFileObserver(String path) { super(path); mPath = path; sListFileObserver.add(this); } public TestFileObserver(String path, int mask) { super(path, mask); mPath = path; sListFileObserver.add(this); } @Override public void onEvent(int event, String path) { StringBuilder strEvents = new StringBuilder(); strEvents.append("Event : ").append('(').append(event).append(')'); for(int i = 0; i < eventValue.length; ++i) { if((eventValue[i] & event) == eventValue[i]) { strEvents.append(eventName[i]); strEvents.append(','); } } if((event & FileObserver.DELETE_SELF) == FileObserver.DELETE_SELF) { sListFileObserver.remove(this); } strEvents.append("\tPath : ").append(path).append('(').append(mPath).append(')'); Log.i("TestFileObserverTestFileObserver",strEvents.toString()); } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); monitorAllFiles(Environment.getExternalStorageDirectory()); } private void monitorAllFiles(File root) { File[] files = root.listFiles(); for(File file : files) { TestFileObserver fileObserver = new TestFileObserver(file.getAbsolutePath()); fileObserver.startWatching(); if(file.isDirectory()) monitorAllFiles(file); } } }
이 코드를 실행하기 위하여 AndroidManifest.xml 파일 상단에 다음과 같은 권한을 추가해야 한다.
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>