天天看點

使用Vitamio打造自己的Android萬能播放器(11)—— 本地掃描背景服務

一、注意事項

注意3.0整合的适合建議使用Library方式整合Vitamio,否則需要反射或者使用OPlayer的方法,覆寫R(參見代碼)。 

二、主要代碼:

/** 媒體掃描 */

public class MediaScannerService extends Service implements Runnable {

    private static final String SERVICE_NAME = "com.nmbb.oplayer.service.MediaScannerService";

    /** 掃描檔案夾 */

    public static final String EXTRA_DIRECTORY = "scan_directory";

    /** 掃描檔案 */

    public static final String EXTRA_FILE_PATH = "scan_file";

    public static final String EXTRA_MIME_TYPE = "mimetype";

    public static final int SCAN_STATUS_NORMAL = -1;

    /** 開始掃描 */

    public static final int SCAN_STATUS_START = 0;

    /** 正在掃描 掃描到一個視訊檔案 */

    public static final int SCAN_STATUS_RUNNING = 1;

    /** 掃描完成 */

    public static final int SCAN_STATUS_END = 2;

    /**  */

    private ArrayList<IMediaScannerObserver> observers = new ArrayList<IMediaScannerObserver>();

    private ConcurrentHashMap<String, String> mScanMap = new ConcurrentHashMap<String, String>();

    /** 目前狀态 */

    private volatile int mServiceStatus = SCAN_STATUS_NORMAL;

    private DbHelper<POMedia> mDbHelper;

    private Map<String, Object> mDbWhere = new HashMap<String, Object>(2);

    @Override

    public void onCreate() {

        super.onCreate();

        mDbHelper = new DbHelper<POMedia>();

    }

    /** 是否正在運作 */

    public static boolean isRunning() {

        ActivityManager manager = (ActivityManager) OPlayerApplication.getContext().getSystemService(Context.ACTIVITY_SERVICE);

        for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {

            if (SERVICE_NAME.equals(service.service.getClassName()))

                return true;

        }

        return false;

    public int onStartCommand(Intent intent, int flags, int startId) {

        if (intent != null)

            parseIntent(intent);

        return super.onStartCommand(intent, flags, startId);

    /** 解析Intent */

    private void parseIntent(final Intent intent) {

        final Bundle arguments = intent.getExtras();

        if (arguments != null) {

            if (arguments.containsKey(EXTRA_DIRECTORY)) {

                String directory = arguments.getString(EXTRA_DIRECTORY);

                Logger.i("onStartCommand:" + directory);

                //掃描檔案夾

                if (!mScanMap.containsKey(directory))

                    mScanMap.put(directory, "");

            } else if (arguments.containsKey(EXTRA_FILE_PATH)) {

                //單檔案

                String filePath = arguments.getString(EXTRA_FILE_PATH);

                Logger.i("onStartCommand:" + filePath);

                if (!StringUtils.isEmpty(filePath)) {

                    if (!mScanMap.containsKey(filePath))

                        mScanMap.put(filePath, arguments.getString(EXTRA_MIME_TYPE));

                    //                    scanFile(filePath, arguments.getString(EXTRA_MIME_TYPE));

                }

            }

        if (mServiceStatus == SCAN_STATUS_NORMAL || mServiceStatus == SCAN_STATUS_END) {

            new Thread(this).start();

            //scan();

    public void run() {

        scan();

    /** 掃描 */

    private void scan() {

        //開始掃描

        notifyObservers(SCAN_STATUS_START, null);

        while (mScanMap.keySet().size() > 0) {

            String path = "";

            for (String key : mScanMap.keySet()) {

                path = key;

                break;

            if (mScanMap.containsKey(path)) {

                String mimeType = mScanMap.get(path);

                if ("".equals(mimeType)) {

                    scanDirectory(path);

                } else {

                    scanFile(path, mimeType);

                //掃描完成一個

                mScanMap.remove(path);

            //任務之間歇息一秒

            try {

                Thread.sleep(1000);

            } catch (InterruptedException e) {

                Logger.e(e);

        //全部掃描完成

        notifyObservers(SCAN_STATUS_END, null);

        //第一次掃描

        OPreference pref = new OPreference(this);

        if (pref.getBoolean(OPlayerApplication.PREF_KEY_FIRST, true))

            pref.putBooleanAndCommit(OPlayerApplication.PREF_KEY_FIRST, false);

        //停止服務

        stopSelf();

    private Handler mHandler = new Handler() {

        @Override

        public void handleMessage(Message msg) {

            super.handleMessage(msg);

            for (IMediaScannerObserver s : observers) {

                if (s != null) {

                    s.update(msg.what, (POMedia) msg.obj);

    };

    private void scanFile(String path, String mimeType) {

        save(new POMedia(path, mimeType));

    private void scanDirectory(String path) {

        eachAllMedias(new File(path));

    /** 遞歸查找視訊 */

    private void eachAllMedias(File f) {

        if (f != null && f.exists() && f.isDirectory()) {

            File[] files = f.listFiles();

            if (files != null) {

                for (File file : f.listFiles()) {

                    //                    Logger.i(f.getAbsolutePath());

                    if (file.isDirectory()) {

                        //忽略.開頭的檔案夾

                        if (!file.getAbsolutePath().startsWith("."))

                            eachAllMedias(file);

                    } else if (file.exists() && file.canRead() && FileUtils.isVideo(file)) {

                        save(new POMedia(file));

                    }

    /**

     * 儲存入庫

     * 

     * @throws FileNotFoundException

     */

    private void save(POMedia media) {

        mDbWhere.put("path", media.path);

        mDbWhere.put("last_modify_time", media.last_modify_time);

        //檢測

        if (!mDbHelper.exists(media, mDbWhere)) {

                if (media.title != null && media.title.length() > 0)

                    media.title_key = PinyinUtils.chineneToSpell(media.title.charAt(0) + "");

            } catch (Exception ex) {

                Logger.e(ex);

            media.last_access_time = System.currentTimeMillis();

            //提取縮略圖

            //            extractThumbnail(media);

            media.mime_type = FileUtils.getMimeType(media.path);

            //入庫

            mDbHelper.create(media);

            //掃描到一個

            notifyObservers(SCAN_STATUS_RUNNING, media);

    /** 提取生成縮略圖 */

    private void extractThumbnail(POMedia media) {

        final Context ctx = OPlayerApplication.getContext();

        //        ThumbnailUtils.

        Bitmap bitmap = ThumbnailUtils.createVideoThumbnail(ctx, media.path, ThumbnailUtils.MINI_KIND);

        try {

            if (bitmap == null) {

                //縮略圖建立失敗

                bitmap = Bitmap.createBitmap(ThumbnailUtils.TARGET_SIZE_MINI_THUMBNAIL_WIDTH, ThumbnailUtils.TARGET_SIZE_MINI_THUMBNAIL_HEIGHT, Bitmap.Config.RGB_565);

            media.width = bitmap.getWidth();

            media.height = bitmap.getHeight();

            //縮略圖

            bitmap = ThumbnailUtils.extractThumbnail(bitmap, ConvertUtils.dipToPX(ctx, ThumbnailUtils.TARGET_SIZE_MICRO_THUMBNAIL_WIDTH), ConvertUtils.dipToPX(ctx, ThumbnailUtils.TARGET_SIZE_MICRO_THUMBNAIL_HEIGHT), ThumbnailUtils.OPTIONS_RECYCLE_INPUT);

            if (bitmap != null) {

                //将縮略圖存到視訊目前路徑

                File thum = new File(OPlayerApplication.OPLAYER_VIDEO_THUMB, UUID.randomUUID().toString());

                media.thumb_path = thum.getAbsolutePath();

                //thum.createNewFile();

                FileOutputStream iStream = new FileOutputStream(thum);

                bitmap.compress(Bitmap.CompressFormat.JPEG, 85, iStream);

                iStream.close();

        } catch (Exception ex) {

            Logger.e(ex);

        } finally {

            if (bitmap != null)

                bitmap.recycle();

    // ~~~ 狀态改變 

    /** 通知狀态改變 */

    private void notifyObservers(int flag, POMedia media) {

        mHandler.sendMessage(mHandler.obtainMessage(flag, media));

    /** 增加觀察者 */

    public void addObserver(IMediaScannerObserver s) {

        synchronized (this) {

            if (!observers.contains(s)) {

                observers.add(s);

    /** 删除觀察者 */

    public synchronized void deleteObserver(IMediaScannerObserver s) {

        observers.remove(s);

    /** 删除所有觀察者 */

    public synchronized void deleteObservers() {

        observers.clear();

    public interface IMediaScannerObserver {

        /**

         * 

         * @param flag 0 開始掃描 1 正在掃描 2 掃描完成

         * @param file 掃描到的視訊檔案

         */

        public void update(int flag, POMedia media);

    // ~~~ Binder 

    private final MediaScannerServiceBinder mBinder = new MediaScannerServiceBinder();

    public class MediaScannerServiceBinder extends Binder {

        public MediaScannerService getService() {

            return MediaScannerService.this;

    public IBinder onBind(Intent intent) {

        return mBinder;

}

代碼說明:

1、預設Service是在主線程中運作的,掃描比較耗時,這裡啟用了一個新的線程。

2、使用了觀察者模式來通知資料變化,沒有用廣播。

3、暫時去掉了截圖,截圖報錯,還在修改。

三、OPlayer

3.1 下載下傳

請移步#Taocode(SVN):(沒有賬戶的請注冊一個即可)

3.2 更新

a). 資料庫層使用了ormlite架構。

b). 将掃描視訊檔案修改為背景服務,單獨起了一個線程。

c). 整合Vitamio 3.0,修改反射為覆寫io.voo.vitamio.R。 

四、Vitamio

已建立Vitamio開發者聯盟官方QQ群!群号為:246969281

注意:目前僅接受已經開發基于Vitamio産品的開發者申請加入,申請理由請填寫産品的名稱和連結!

五、八卦

想在這裡澄清一下OPlayer與VPlayer、Vitamio的關系:

1、OPlayer這個項目是跟公司沒有關系,純屬于個人業餘愛好作品。

2、OPlayer使用的是Vitamio,但不會比大家優先擷取Vitamio的新版本。

3、OPlayer非官方示例。