android fileobserver

项目要求扫描出SD卡中所有的视频文件,之前是开一个线程全盘扫描,但扫描时间太长。调用系统 MediaScannerService 进行扫描也是非常慢。万恶的产品经理对比了暴风魔镜,暴风魔镜拷入SD卡中一个视频刷新一下 1 秒钟就扫描了出来。

人家都能做到,咱们怎么做不到
哎我等屌丝只能再研究了发现了 fileobserver

android fileobserver 可以对文件系统的文件夹进行监听,但不能监听子目录。
要监听子目录必须递归遍历所有的文件夹对其添加监听。
fileobserver 在 Android 4.0 和 5.0 中是好用的,但在 Android 6.0 中有 bug 监听不能工作。
如果文件太多,递归监听非常耗内存,导致频繁 GC。
参考网上代码稍微改了一点,仅供参考

/**
 * @author ddai FileWatcher support subdirectory(recursively)
 */
public class FileWatcher extends FileObserver {
    static final String TAG = "FileWatcher";
    ArrayList<FileObserver> mObservers;
    String mPath;
    int mMask;

    String mThreadName = FileWatcher.class.getSimpleName();
    HandlerThread mThread;
    Handler mThreadHandler;

    private Context mContext;

    public FileWatcher(Context context, String path) {
        this(path, ALL_EVENTS);
        mContext = context;
    }

    public FileWatcher(String path) {
        this(path, ALL_EVENTS);
    }

    public FileWatcher(String path, int mask) {
        super(path, mask);
        mPath = path;
        mMask = mask;
    }

    @Override
    public void startWatching() {
        mThreadName = FileWatcher.class.getSimpleName();
        if (mThread == null || !mThread.isAlive()) {
            mThread = new HandlerThread(mThreadName, Process.THREAD_PRIORITY_BACKGROUND);
            mThread.start();

            mThreadHandler = new Handler(mThread.getLooper());
            mThreadHandler.post(new startRunnable());
        }
    }

    @Override
    public void stopWatching() {
        if (null != mThreadHandler && null != mThread && mThread.isAlive()) {
            mThreadHandler.post(new stopRunnable());
        }
        mThreadHandler = null;
        mThread.quit();
        mThread = null;
    }

    @Override
    public void onEvent(int event, String path) {
        event = event & FileObserver.ALL_EVENTS;
        final String tmpPath = path;
        switch (event) {
        case FileObserver.ACCESS:
            // Log.i("FileWatcher", "ACCESS: " + path);
            break;
        case FileObserver.ATTRIB:
            // Log.i("FileWatcher", "ATTRIB: " + path);
            break;
        case FileObserver.CLOSE_NOWRITE:
            // Log.i("FileWatcher", "CLOSE_NOWRITE: " + path);
            break;
        case FileObserver.CLOSE_WRITE:
            Log.i("FileWatcher", "CLOSE_WRITE: " + path);
            // 文件写入完毕后会回调,可以在这对新写入的文件做操作
            mThreadHandler.post(new Runnable() {

                @Override
                public void run() {
                     //
                }
            });
            break;
        case FileObserver.CREATE:
            Log.i(TAG, "CREATE: " + path);
            mThreadHandler.post(new Runnable() {
                @Override
                public void run() {
                    doCreate(tmpPath);
                }
            });
            break;
        case FileObserver.DELETE:
            Log.i(TAG, "DELETE: " + path);
            mThreadHandler.post(new Runnable() {
                @Override
                public void run() {
                    doDelete(tmpPath);
                }
            });
            break;
        case FileObserver.DELETE_SELF:
            // Log.i("FileWatcher", "DELETE_SELF: " + path);
            break;
        case FileObserver.MODIFY:
            // Log.i("FileWatcher", "MODIFY: " + path);
            break;
        case FileObserver.MOVE_SELF:
            // Log.i("FileWatcher", "MOVE_SELF: " + path);
            break;
        case FileObserver.MOVED_FROM:
            // Log.i("FileWatcher", "MOVED_FROM: " + path);
            break;
        case FileObserver.MOVED_TO:
            // Log.i("FileWatcher", "MOVED_TO: " + path);
            break;
        case FileObserver.OPEN:
            Log.i("FileWatcher", "OPEN: " + path);
            break;
        default:
            Log.i(TAG, "DEFAULT(" + event + ";) : " + path);
            break;
        }
    }

    private void doCreate(String path) {
        synchronized (FileWatcher.this) {
            File file = new File(path);
            if (!file.exists()) {
                return;
            }

            if (file.isDirectory()) {
                // 新建文件夹,对该文件夹及子目录添加监听
                Stack<String> stack = new Stack<String>();
                stack.push(path);

                while (!stack.isEmpty()) {
                    String parent = stack.pop();
                    SingleFileObserver observer = new SingleFileObserver(parent, mMask);
                    observer.startWatching();

                    mObservers.add(observer);
                    Log.d(TAG, "add observer " + parent);
                    File parentFile = new File(parent);
                    File[] files = parentFile.listFiles();
                    if (null == files) {
                        continue;
                    }

                    for (File f : files) {
                        if (f.isDirectory() && !f.getName().equals(".") && !f.getName().equals("..")) {
                            stack.push(f.getPath());
                        }
                    }
                }

                stack.clear();
                stack = null;
            } else {
                // 新建文件
            }
        }
    }

    private void doDelete(String path) {
        synchronized (FileWatcher.this) {
            Iterator<FileObserver> it = mObservers.iterator();
            while (it.hasNext()) {
                SingleFileObserver sfo = (SingleFileObserver) it.next();
                // 如果删除的是文件夹移除对该文件夹及子目录的监听
                if (sfo.mPath != null && (sfo.mPath.equals(path) || sfo.mPath.startsWith(path + "/"))) {
                    Log.d(TAG, "stop observer " + sfo.mPath);
                    sfo.stopWatching();
                    it.remove();
                    sfo = null;
                }
            }
        }
    }

    /**
     * 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) {
            if (path == null) {
                return;
            }
            String newPath = mPath + "/" + path;
            FileWatcher.this.onEvent(event, newPath);
        }
    }

    class startRunnable implements Runnable {
        @Override
        public void run() {
            synchronized (FileWatcher.this) {
                if (mObservers != null) {
                    return;
                }

                mObservers = new ArrayList<FileObserver>();
                Stack<String> stack = new Stack<String>();
                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());
                        }
                    }
                }

                Iterator<FileObserver> it = mObservers.iterator();
                while (it.hasNext()) {
                    SingleFileObserver sfo = (SingleFileObserver) it.next();
                    sfo.startWatching();
                }
            }
        }
    }

    class stopRunnable implements Runnable {
        @Override
        public void run() {
            synchronized (FileWatcher.this) {
                if (mObservers == null) {
                    return;
                }

                Iterator<FileObserver> it = mObservers.iterator();
                while (it.hasNext()) {
                    FileObserver sfo = it.next();
                    sfo.stopWatching();
                }
                mObservers.clear();
                mObservers = null;
            }
        }
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容