作业3:安卓系统文件管理观察者模式
一、观察者模式:
观察者模式(有时又被称为模型-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。
在阎宏博士的《JAVA与模式》一书中开头是这样描述的:观察者模式是对象的行为模式,又叫发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。观察者设计模式定义了对象间的一种一对多的组合关系,以便一个对象的状态发生变化时,所有依赖于它的对象都得到通知并自动刷新。
二、观察者模式结构图:
三、观察者模式所涉及的角色有:
1、抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象。
2、具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。
3、抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
4、具体观察者(ConcreteObserver)角色:存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。
四、具体事例FileObserver:Android系统的文件管理
GitHub源代码链接:https://github.com/doyee/FileObserver-android
1. 观察者接口
package custom.fileobserver; public interface FileListener { public void onFileCreated(String name); public void onFileDeleted(String name); public void onFileModified(String name); public void onFileRenamed(String oldName, String newName); }
2、具体观察者
/* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package custom.fileobserver; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.util.Log; import java.io.File; import java.io.FileFilter; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Iterator; /** * Monitors files (using <a href="http://en.wikipedia.org/wiki/Inotify">inotify</a>) * to fire an event after files are accessed or changed by by any process on * the device (including this one). FileObserver is an abstract class; * subclasses must implement the event handler {@link #onEvent(int, String)}. * * <p>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.</p> * * <p>An event mask is used to specify which changes or actions to report. * Event type constants are used to describe the possible changes in the * event mask as well as what actually happened in event callbacks.</p> * * <p class="caution"><b>Warning</b>: If a FileObserver is garbage collected, it * will stop sending events. To ensure you keep receiving events, you must * keep a reference to the FileObserver instance from some other live object.</p> */ /** * * @author Dai Dongsheng * Email: doyee@163.com * */ public abstract class FileObserver { /** Event type: Data was read from a file */ public static final int ACCESS = 0x00000001; /** Event type: Data was written to a file */ public static final int MODIFY = 0x00000002; /** Event type: Metadata (permissions, owner, timestamp) was changed explicitly */ public static final int ATTRIB = 0x00000004; /** Event type: Someone had a file or directory open for writing, and closed it */ public static final int CLOSE_WRITE = 0x00000008; /** Event type: Someone had a file or directory open read-only, and closed it */ public static final int CLOSE_NOWRITE = 0x00000010; /** Event type: A file or directory was opened */ public static final int OPEN = 0x00000020; /** Event type: A file or subdirectory was moved from the monitored directory */ public static final int MOVED_FROM = 0x00000040; /** Event type: A file or subdirectory was moved to the monitored directory */ public static final int MOVED_TO = 0x00000080; /** Event type: A new file or subdirectory was created under the monitored directory */ public static final int CREATE = 0x00000100; /** Event type: A file was deleted from the monitored directory */ public static final int DELETE = 0x00000200; /** Event type: The monitored file or directory was deleted; monitoring effectively stops */ public static final int DELETE_SELF = 0x00000400; /** Event type: The monitored file or directory was moved; monitoring continues */ public static final int MOVE_SELF = 0x00000800; public static final int UNMOUNT = 0x00002000; public static final int Q_OVERFLOW = 0x00004000; public static final int IGNORED = 0x00008000; public static final int CLOSE = (CLOSE_WRITE | CLOSE_NOWRITE); public static final int MOVE = (MOVED_FROM | MOVED_TO); public static final int ONLYDIR = 0x01000000; public static final int DONT_FOLLOW = 0x02000000; public static final int MASK_ADD = 0x20000000; public static final int ISDIR = 0x40000000 ; public static final int ONESHOT = 0x80000000; /** Event mask: All valid event types, combined */ public static final int ALL_EVENTS = ACCESS | MODIFY | ATTRIB | CLOSE_WRITE | CLOSE_NOWRITE | OPEN | MOVED_FROM | MOVED_TO | DELETE | CREATE | DELETE_SELF | MOVE_SELF; public static int FILE_CHANGED = CREATE | DELETE | MOVED_FROM | MOVED_TO | CLOSE_WRITE;/* MODIFY | ATTRIB*/; private static final String LOG_TAG = "FileObserver"; private static class FolderFilter implements FileFilter{ public boolean accept(File pathname) { return pathname.isDirectory(); } } private static class ObserverThread extends Thread { private HashMap<Integer, WeakReference<Object>> mObservers = new HashMap<Integer, WeakReference<Object>>(); private HashMap<Integer,String> mListPath = new HashMap<Integer,String>(); private FolderFilter mFilter = new FolderFilter(); private int m_fd; public ObserverThread() { super("FileObserver"); m_fd = init(); } public void run() { observe(m_fd); } public int startWatching(String observed, String path, int mask, FileObserver observer) { int wfd = startWatching(m_fd, path, mask); Integer i = new Integer(wfd); if (wfd <= 0) { return i; } synchronized (mObservers) { mObservers.put(i, new WeakReference<Object>(observer)); mListPath.put(i, path.replaceFirst(observed, "")); if(observer.mWatchSubDir){ File rootFolder = new File(path); File[] childFolders = rootFolder.listFiles(mFilter); if((childFolders != null)) { for(int index = 0; index < childFolders.length; index++) startWatching(observed, childFolders[index].getPath(), mask, observer); } } } return i; } public void stopWatching(int descriptor, FileObserver observer) { synchronized(mObservers) { stopWatching(m_fd, descriptor); mListPath.remove(descriptor); mObservers.remove(descriptor); Iterator <Integer> it = mListPath.keySet().iterator(); while(it.hasNext()) { Integer fd = it.next(); if(mObservers.get(fd).get() == observer) { stopWatching(m_fd, fd); it.remove(); mObservers.remove(fd); } } } } public void onEvent(int wfd, int mask, int cookie, String path) { // look up our observer, fixing up the map if necessary... FileObserver observer = null; synchronized (mObservers) { WeakReference<Object> weak = mObservers.get(wfd); if (weak != null) { // can happen with lots of events from a // dead wfd observer = (FileObserver) weak.get(); if (observer == null) { mObservers.remove(wfd); mListPath.remove(wfd); } } } // ...then call out to the observer without the sync lock held if (observer == null) { Log.i(LOG_TAG,"onEvent observer null ,return..."); return; } try { String observed = observer.mPath ; String newAbsPath = observed + mListPath.get(wfd); if (path != null) { if (newAbsPath.length() > 0) { newAbsPath += "/"; } newAbsPath += path; } if ((mask & (CREATE | ISDIR)) != 0) { //auto to watch new created subdirectory if(observer.mWatchSubDir){ startWatching(observed, newAbsPath, observer.mMask, observer); } } observer.onEvent(mask, cookie,newAbsPath); } catch (Throwable throwable) { Log.wtf(LOG_TAG, "Unhandled exception in FileObserver " + observer, throwable); } } private native int init(); private native void observe(int fd); private native int startWatching(int fd, String path, int mask); private native void stopWatching(int fd, int wfd); } private static ObserverThread s_observerThread; static { try{ System.loadLibrary("fileobserver_jni"); }catch (UnsatisfiedLinkError e) { e.printStackTrace(); } /*try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }*/ s_observerThread = new ObserverThread(); s_observerThread.start(); } // instance private String mPath; private Integer mDescriptor; private int mMask; private boolean mWatchSubDir; String mThreadName = FileObserver.class.getSimpleName(); HandlerThread mThread; Handler mThreadHandler; /** * Equivalent to FileObserver(path, FileObserver.ALL_EVENTS). */ public FileObserver(String path) { this(path, ALL_EVENTS); } public FileObserver(String path, int mask) { this(path,false,mask); } /** * Create a new file observer for a certain file or directory. * Monitoring does not start on creation! You must call * {@link #startWatching()} before you will receive events. * * @param path The file or directory to monitor * @param watchSubDir If the sub directory need monitor ,set to true,default false * @param mask The event or events (added together) to watch for */ public FileObserver(String path, boolean watchSubDir,int mask) { mPath = path; mMask = mask; mDescriptor = -1; mWatchSubDir = watchSubDir; } protected void finalize() { stopWatching(); } /** * Start watching for events. The monitored file or directory must exist at * this time, or else no events will be reported (even if it appears later). * If monitoring is already started, this call has no effect. */ public void startWatching() { mThreadName = FileWatcher.class.getSimpleName(); if (mThread == null || !mThread.isAlive()) { Log.i(LOG_TAG,"startFileWather new HandlerThread..."); mThread = new HandlerThread(mThreadName,Process.THREAD_PRIORITY_BACKGROUND); mThread.setDaemon(true); mThread.start(); mThreadHandler = new Handler(mThread.getLooper()); mThreadHandler.post(new Runnable() { @Override public void run() { Log.i(LOG_TAG,"startWatching mDescriptor:" + mDescriptor); if (mDescriptor < 0) { mDescriptor = s_observerThread.startWatching(mPath, mPath, mMask, FileObserver.this); Log.i(LOG_TAG,"startWatching finished mDescriptor: " + mDescriptor); } } }); } } /** * Stop watching for events. Some events may be in process, so events * may continue to be reported even after this method completes. If * monitoring is already stopped, this call has no effect. */ public void stopWatching() { if(null != mThreadHandler && null != mThread && mThread.isAlive()){ mThreadHandler.post(new Runnable() { @Override public void run() { Log.i(LOG_TAG,"stopWatching mDescriptor:" + mDescriptor); if (mDescriptor < 0) { Log.i(LOG_TAG,"stopWatching already stopped:" + mDescriptor); return; } s_observerThread.stopWatching(mDescriptor, FileObserver.this); mDescriptor = -1; Log.i(LOG_TAG,"stopWatching finished:" + mDescriptor); mThreadHandler = null; mThread.quit(); mThread = null; } }); } } /** * The event handler, which must be implemented by subclasses. * * <p class="note">This method is invoked on a special FileObserver thread. * It runs independently of any threads, so take care to use appropriate * synchronization! Consider using {@link Handler#post(Runnable)} to shift * event handling work to the main thread to avoid concurrency problems.</p> * * <p>Event handlers must not throw exceptions.</p> * * @param event The type of event which happened * @param path The path, relative to the main monitored file or directory, * of the file or directory which triggered the event */ public abstract void onEvent(int event, int cookie,String path); }
3、抽象主题类
package custom.fileobserver; import java.util.Hashtable; import android.util.Log; /** * * @author Dai Dongsheng * Email: doyee@163.com * FileWatcher support subdirectory(recursively) */ public class FileWatcher extends FileObserver { FileListener mFileListener; Hashtable<Integer, String> mRenameCookies = new Hashtable<Integer, String>(); public FileWatcher(String path) { this(path, ALL_EVENTS); } public FileWatcher(String path, int mask) { this(path, false,mask); } public FileWatcher(String path, boolean watchsubdir,int mask) { super(path, watchsubdir,mask); } public void setFileListener(FileListener fl){ mFileListener = fl; } @Override public void onEvent(int event,int cookie,String path) { switch (event) { case ACCESS: Log.i("FileWatcher", "ACCESS: " + path); break; case ATTRIB: Log.i("FileWatcher", "ATTRIB: " + path); if(null != mFileListener){ mFileListener.onFileModified(path); } break; case CLOSE_NOWRITE: Log.i("FileWatcher", "CLOSE_NOWRITE: " + path); break; case CLOSE_WRITE: Log.i("FileWatcher", "CLOSE_WRITE: " + path); if(null != mFileListener){ mFileListener.onFileModified(path); } break; case CREATE: Log.i("FileWatcher", "CREATE: " + path); if(null != mFileListener){ mFileListener.onFileCreated(path); } break; case DELETE: Log.i("FileWatcher", "DELETE: " + path); if(null != mFileListener){ mFileListener.onFileDeleted(path); } break; case DELETE_SELF: Log.i("FileWatcher", "DELETE_SELF: " + path); if(null != mFileListener){ mFileListener.onFileDeleted(path); } break; case MODIFY: Log.i("FileWatcher", "MODIFY: " + path); if(null != mFileListener){ mFileListener.onFileModified(path); } break; case MOVE_SELF: Log.i("FileWatcher", "MOVE_SELF: " + path); break; case MOVED_FROM: Log.i("FileWatcher", "MOVED_FROM: " + path); mRenameCookies.put(cookie, path); break; case MOVED_TO: Log.i("FileWatcher", "MOVED_TO: " + path); if(null != mFileListener){ String oldName = mRenameCookies.remove(cookie); mFileListener.onFileRenamed(oldName, path); } break; case OPEN: Log.i("FileWatcher", "OPEN: " + path); break; default: Log.i("FileWatcher", "DEFAULT(" + event + ") : " + path); switch(event - ISDIR){ case ACCESS: Log.i("FileWatcher", "ACCESS: " + path); break; case ATTRIB: Log.i("FileWatcher", "ATTRIB: " + path); if(null != mFileListener){ mFileListener.onFileModified(path); } break; case CLOSE_NOWRITE: Log.i("FileWatcher", "CLOSE_NOWRITE: " + path); break; case CLOSE_WRITE: Log.i("FileWatcher", "CLOSE_WRITE: " + path); if(null != mFileListener){ mFileListener.onFileModified(path); } break; case CREATE: Log.i("FileWatcher", "CREATE: " + path); if(null != mFileListener){ mFileListener.onFileCreated(path); } break; case DELETE: Log.i("FileWatcher", "DELETE: " + path); if(null != mFileListener){ mFileListener.onFileDeleted(path); } break; case DELETE_SELF: Log.i("FileWatcher", "DELETE_SELF: " + path); if(null != mFileListener){ mFileListener.onFileDeleted(path); } break; case MODIFY: Log.i("FileWatcher", "MODIFY: " + path); if(null != mFileListener){ mFileListener.onFileModified(path); } break; case MOVE_SELF: Log.i("FileWatcher", "MOVE_SELF: " + path); break; case MOVED_FROM: Log.i("FileWatcher", "MOVED_FROM: " + path); mRenameCookies.put(cookie, path); break; case MOVED_TO: Log.i("FileWatcher", "MOVED_TO: " + path); if(null != mFileListener){ String oldName = mRenameCookies.remove(cookie); mFileListener.onFileRenamed(oldName, path); } break; case OPEN: Log.i("FileWatcher", "OPEN: " + path); break; } break; } } }
4.具体主题类
package custom.fileobserver; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.util.Log; public class TestActivity extends Activity { FileWatcher mWatcher; final String TAG = TestActivity.class.getSimpleName(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); String sdcard = Environment.getExternalStorageDirectory().getAbsolutePath(); String dcim = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath(); //mWatcher = new FileWatcher(sdcard,true,FileWatcher.FILE_CHANGED); mWatcher = new FileWatcher(dcim,true,FileWatcher.FILE_CHANGED); mWatcher.setFileListener(mFileListener); mWatcher.startWatching(); } FileListener mFileListener = new FileListener(){ @Override public void onFileCreated(String name) { Log.i(TAG, "onFileCreated " + name); } @Override public void onFileDeleted(String name) { Log.i(TAG, "onFileDeleted " + name); } @Override public void onFileModified(String name) { Log.i(TAG, "onFileModified " + name); } @Override public void onFileRenamed(String oldName, String newName) { Log.i(TAG, "onFileRenamed from: " + oldName + " to: " + newName); } }; @Override protected void onResume() { super.onResume(); } @Override protected void onPause() { super.onPause(); } @Override protected void onUserLeaveHint() { super.onUserLeaveHint(); } @Override protected void onStop() { super.onStop(); } @Override protected void onDestroy() { mWatcher.stopWatching(); super.onDestroy(); } }
五、观察者模式的优缺点
优点:
1、分离了观察者和被观察者,使他们属于不同的抽象层次,从而降低了系统的耦合性,提高系统的健壮性和稳定性
2、由于分离了观察者与被观察者,当系统需要增加或删除新的模式的时候,不需要对原有的代码进行修改,而是只用添加或删除观察者,从而提高了系统的可扩展性。
3、观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知。
缺点:
1、如果一个被观察者对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
2、如果对观察者的通知是通过另外的线程进行异步投递的话,系统必须保证投递是以自恰的方式进行的。
3、虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。
4、如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察考模式时要特别注意这一点。