개발 관련/Android

[API] 안드로이드 디렉토리와 파일 모니터링(감시) 를 위한 FileObserver.

snoworca 2014. 10. 29. 19:21

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
intACCESS

파일로 부터 데이터를 읽었을 때 발생하는 이벤트 타입.

intALL_EVENTS

모든 이벤트 타입이 포함되어 있다.

모든 이벤트 타입은 각각 2진수의 각 자리수 하나씩을 차지하고 있고, ALL_EVENTS 는 마스크 역할을 한다.


  예를들어 FileObserver 를 생성할 때, 두 번째 인자값으로 이벤트 타입 값을 넣어서 원하는 이벤트만 받을 수 있도록 필터링을 할 수 있는데, 이때 ATTRIB 이벤트를 제외한 모든 이벤트를 받고 싶다면 

  

    new FileObserver("path", ALL_EVENTS ^ ATTRIB)


   와 같이 사용할 수 있다.


intATTRIB

메타 데이터(permissions, owner, timestamp)가 변경되었을 때 발생하는 이벤트 타입. 

intCLOSE_NOWRITE

읽기 전용으로 열려있는 특정 파일 또는 디렉트로리를 닫았을 때 발생하는 이벤트 타입.

intCLOSE_WRITE

쓰기 모드로 열려있는 특정 파일 또는 디렉토리를 닫았을 때 발생하는 이벤트 타입.

intCREATE

FileObserver 으로 디렉토리를 감시할 때, 디렉토리 내부 파일 또는 서브디렉토리가 생성되었을때 발생하는 이벤트 타입.

intDELETE

감시되고 있는 디렉토리 내부에서 파일 또는 디렉토리가 삭제 되었을때 발생하는 이벤트 타입.

intDELETE_SELF

감시되고 있는 디렉토리 또는 파일이 삭제 되었을때 발생하는 이벤트 타입. 이 이벤트가 발생하면 대상 파일이나 디렉토리에 대한 감시는 종료된다. 

intMODIFY

파일이 감시되고 있을경우,  파일에 데이터가 쓰여질때 발생하는 이벤트 타입.

intMOVED_FROM

감시되고 있는 디렉토리 내부로부터 파일 또는 디렉토리가 이동했을때 발생. 

(물론 이름이 변경되어도 발생한다.)  

intMOVED_TO

감시되고 있는 디렉토리 내부로 파일 또는 디렉토리가 이동했을때 발생. 

(마찬가지로 이름이 변경되어도 발생한다.)  

intMOVE_SELF

감시되고 있는 디렉토리 또는 파일이 이동했을때 발생하는 이벤트 타입. 이 이벤트가 발생하여도 대상 파일에 대한 감시는 종료되지 않는다.

intOPEN

파일 또는 디렉토리가 열렸을때 발생한다.


  만약 특정 디렉토리를 감시하고 있는 상태에서는 그 내부 자식들의 파일 또는 디렉토리가 변경 되었을때만 이벤트가 발생한다. 즉, 감시 대상이 되는 디렉토리 내부의 어떤 디렉토리의 자식 디렉토리 상태가 변경되었을때는 이벤트가 발생하지 않는다. 


   이렇게 구구절절 설명하는 것 보다 한 번 돌려보고 눈으로 직접 확인해 보는 것이 최고다. 그래서 아래와 같은 예제 코드를 마련해 놓았다.

   이 코드는 접근 가능한 영역의 모든 파일 또는 폴더를 대상으로 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"/>