手记

通过观察者模式监听媒体库的变化实现APP本地数据自动更新

前言

  当我在使用音乐播放器和各种小说APP的过程中,感觉非常不好的一个体验就是你需要通过手动点击全盘检索后,新下载的数据、从磁盘拷贝的数据才会更新显示在列表上,这对于我们来说看上去没有什么不对,但从用户的角度出发这是一个非常不好的体验,因为多数人是根本不知道全盘检索这个概念的,手动更新APP的本地数据无形之中增加了用户使用APP的学习成本。

  我们之前做的小系统APP也是通过手动检索这种方式来刷新程序中的本地数据列表的,在接触到Android的媒体库后,发现这个问题能够通过观察者模式监听Android媒体数据库变化的方式来实现APP本地数据的自动更新,在成功实现这个功能后,现在将其总结下来方便后面查看。

Android媒体库

·         媒体库是什么?:在Android系统中,为了提高应用检索数据的效率,Android会将存储在文件系统中的文件信息保存在一个数据库文件中,这样在应用中就可以通过读取该数据库来快速查找满足APP需求的文件列表,比如一个电子书阅读APP,通过如下方法就可以获取到媒体库中存在的电子书文件列表,保存哪些格式的文件是可以通过修改Android原来来调整的,不过对于多媒体文件来说,Android原生系统默认就保存在媒体库中了:

[代码]java代码:

?

01

02

03

04

05

06

07

08

09

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

/**

  * 从媒体库中获取指定后缀的文件列表

  *

  * @param searchFileSuffix 文件后缀列表,eg: new   String[]{"epub","mobi","pdf","txt"};

  * @return 指定后缀的文件列表

  * */

 public static ArrayList<string>   getSupportFileList(Context context, String[] searchFileSuffix) {

     ArrayList<string   deep="9"> searchFileList = null;

     if (null == context ||   null == searchFileSuffix

             ||   searchFileSuffix.length == 0) {

         return   null;

     }

 

     String searchPath =   "";

     int length =   searchFileSuffix.length;

     for (int index = 0; index   < length; index++) {

         searchPath   += (MediaStore.Files.FileColumns.DATA + " LIKE '%" +   searchFileSuffix[index] + "' ");

         if   ((index + 1) < length) {

             searchPath   += "or ";

         }

     }

     searchFileList = new ArrayList<string>();

     Uri uri =   MediaStore.Files.getContentUri("external");

     Cursor cursor =   context.getContentResolver().query(

             uri,new   String[] { MediaStore.Files.FileColumns.DATA,MediaStore.Files.FileColumns.SIZE   }, searchPath, null,null);

 

     if (cursor == null) {

         System.out.println("Cursor   获取失败!");

     } else {

         if   (cursor.moveToFirst()) {

             do   {

                 String   filepath = cursor.getString(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA));

                 if   (isFileExist(filepath)) {

                     try   {

                         searchFileList.add(new   String(filepath.getBytes("UTF-8")));

                     }   catch (UnsupportedEncodingException e) {

                         e.printStackTrace();

                     }

                 }

 

             }   while (cursor.moveToNext());

         }

 

         if   (!cursor.isClosed()) {

             cursor.close();

         }

     }

 

     return searchFileList;

 }

    

 /**

  * 判断SD卡上的文件夹是否存在

  *

  * @param fileName 文件名

  * @return true 文件存在,false 文件不存在

  */

 private static boolean isFileExist(String   filePath) {

     File file = null;

     boolean isExist = false;

 

     if (null != filePath) {

         file   = new File(filePath);

         isExist   = (null != file && file.isFile()) ? file.exists() : false;

         if   (isExist && null != file && 0 == file.length()) {

             isExist   = false;

         }

     }

 

     return isExist;

 }</string></string></string>

 

·         媒体库更新时机:Android系统会在系统开机、USB插拔、TF卡插拔的时候自动更新媒体库(将新增的文件添加到媒体库中,移除不存在的文件数据记录),除了Android系统会自动更新媒体库文件外,开发者也可以在程序中手动更新媒体库,这样能够在文件系统中有新的文件或者通过程序删掉某些文件时能够将动态及时更新到媒体库,保证媒体库中的文件信息是实时的,更新的具体方式如下:

context.sendBroadcast( new Intent( Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse( “file://” + filePath ) ) );

注:上面介绍是的更新单个文件的方式,Android没有提供直接更新整个文件夹的方式,如果是整个文件夹,可以先得到文件夹下的所有文件路径列表,然后挨个更新,对于这个如果有更先进的方法欢迎提出。

APP本地数据自动更新的具体实现

  类似于上述的音视频播放器、小说阅读APP,如果我们需要实现本地数据APP自动更新的功能,只要保持APP支持文件列表的数据库和媒体库中的对应格式的文件同步就可以了,所以我们需要做的是:监听媒体库中文件列表的变化,然后将变化告知APP即可,原理是:

·         通过广播监听USB插拔、TF卡插拔,如果检查到在APP运行过程中有这些操作,直接通过APP全盘检索;

·         通过观察者模式监听媒体库中的文件变化,如果有变化,每隔五秒钟将APP现存列表和媒体库中检索到对应格式的文件列表做比较,如果列表有变化,则将变化的列表更新给APP;

·         在APP进入、退出时注册/反注册广播、打开/关闭计时器。

  整个代码非常简单,一个类搞定,具体代码如下:

[代码]java代码:

?

001

002

003

004

005

006

007

008

009

010

011

012

013

014

015

016

017

018

019

020

021

022

023

024

025

026

027

028

029

030

031

032

033

034

035

036

037

038

039

040

041

042

043

044

045

046

047

048

049

050

051

052

053

054

055

056

057

058

059

060

061

062

063

064

065

066

067

068

069

070

071

072

073

074

075

076

077

078

079

080

081

082

083

084

085

086

087

088

089

090

091

092

093

094

095

096

097

098

099

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

import java.io.UnsupportedEncodingException;

import java.util.ArrayList;

import java.util.Timer;

 

import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

import android.content.IntentFilter;

import android.database.ContentObserver;

import android.database.Cursor;

import android.net.Uri;

import android.os.AsyncTask;

import android.os.Handler;

import android.provider.MediaStore;

 

/**

 * 自动更新书架

 *

 * */

public class AutoRefreshBookShelf {

    public AutoRefreshBookShelf(   Context context, AutoRefreshListener autoRefreshListener, String[]   supportSuffix ) throws NullPointerException{

        if(   null == context || null == autoRefreshListener || null == supportSuffix ){

            throw   new NullPointerException( "传非空的参数进来!" );

        }

         

        mContext   = context;

        mAutoRefreshListener   = autoRefreshListener;

        mSupportSuffix   = supportSuffix;

         

        initAutoRefreshBookShelf(   );

    }

     

    // 不在本界面停止后台检索

    public void onPause( ){

        stopCheckFileTimer(   );

    }

     

    // 返回界面恢复后台检索

    public void onResume( ){

        startCheckFileTimer(   );

    }

     

    /**

     * 注销广播

     *

     * */

    public void unregisterAutoRefreshBookShelf(   ) throws NullPointerException{

        if(   null == mBroadcastReceiver || null == mMediaStoreChangeObserver || null ==   mContext ){

            throw   new NullPointerException( "没有初始化" );

        }

        mContext.unregisterReceiver(   mBroadcastReceiver );

        mContext.getContentResolver(   ).unregisterContentObserver( mMediaStoreChangeObserver );

        stopCheckFileTimer(   );

    }

     

    /**

     * 得到变化的文件列表

     *

     * */

    public void getChangedFileList(   ){

        System.out.println(   "toast ================= getChangedFileList " );

        startCheckFileTimer(   );

    }

     

    private void initAutoRefreshBookShelf(   ){

        startMediaFileListener(   );

        observerMediaStoreChange(   );

    }

     

    private void observerMediaStoreChange(   ){

        if(   null == mMediaStoreChangeObserver ){

            mMediaStoreChangeObserver   = new MediaStoreChangeObserver( );

        }

        mContext.getContentResolver(   ).registerContentObserver(   MediaStore.Files.getContentUri("external"), false, mMediaStoreChangeObserver   );

    }

     

    /**

     * 监听USB的状态,更新书架书本信息

     *

     * */

    private void startMediaFileListener(   ){

        if(   null != mBroadcastReceiver ){

            return;

        }

         

        IntentFilter   intentFilter = new IntentFilter( );

        intentFilter.addAction(   Intent.ACTION_MEDIA_SCANNER_FINISHED );

        intentFilter.addAction(   Intent.ACTION_MEDIA_MOUNTED );

        intentFilter.addAction(   Intent.ACTION_MEDIA_EJECT );

        intentFilter.addDataScheme(   "file" );

           

        mBroadcastReceiver   = new BroadcastReceiver(){

            @Override

            public   void onReceive(Context context,Intent intent){

                String   action = intent.getAction( );

                if(   Intent.ACTION_MEDIA_SCANNER_FINISHED.equals( action ) ){

                    System.out.println(   "toast ================= ACTION_MEDIA_SCANNER_FINISHED " );

                    mTimerWorking   = false;

                    startCheckFileTimer(   );

                }else   if( action.equals( Intent.ACTION_MEDIA_MOUNTED ) ){

                    System.out.println(   "toast ================= ACTION_MEDIA_MOUNTED or ACTION_MEDIA_EJECT   " );

                    mTimerWorking   = true;

                    mAutoRefreshListener.onBookScan(   );

                }else   if( action.equals( Intent.ACTION_MEDIA_EJECT ) ){

                    mAutoRefreshListener.onBookScan(   );

                }

            }

        };

        mContext.registerReceiver(   mBroadcastReceiver, intentFilter );//注册监听函数

    }

     

    /**

     * 媒体数据库变更观察类

     *

     * */

    class MediaStoreChangeObserver   extends ContentObserver{

        public MediaStoreChangeObserver(   ) {

            super(   new Handler( ) );

        }

 

        @Override

        public void   onChange(boolean selfChange) {

            super.onChange(selfChange);

            startCheckFileTimer(   );

        }

    }

     

    private void startCheckFileTimer(   ){

        if(   mTimerWorking ){

            return;

        }

         

        mCheckFileTimer   = new Timer( );

        mCheckFileTimer.schedule(   new CheckFileChangeTimerTask( ), 5000 );

        mTimerWorking   = true;

    }

     

    private void stopCheckFileTimer(   ){

        if(   null != mCheckFileTimer ){

            mCheckFileTimer.cancel(   );

            mCheckFileTimer   = null;

             

            mTimerWorking   = false;

        }

    }

     

    /**

     * 得到新增的文件列表

     *

     * */

    public ArrayList<string> getChangedFileList(   Context context, String[] searchFileSuffix, ArrayList<string>   existFileList ){

        ArrayList<string>   changedFileList = null;

        if(   null == context || null == searchFileSuffix ){

            return   changedFileList;

        }

         

        ArrayList<string>   supportFileList = getSupportFileList( context, searchFileSuffix );

        changedFileList   = getDifferentFileList( supportFileList, existFileList );

        if(   null == changedFileList || changedFileList.size( ) == 0 ){

            changedFileList   = null;

        }

         

        return changedFileList;

    }

     

    /**

     * 获取新增的文件列表

     *

     * */

    private ArrayList<string>   getDifferentFileList( ArrayList<string> newFileList,   ArrayList<string> existFileList ){

        ArrayList<string>   differentFileList = null;

        if(   null == newFileList || newFileList.size( ) == 0 ){

            return   differentFileList;

        }

         

        differentFileList   = new ArrayList<string>( );

        boolean   isExist = false;

        if(   null == existFileList ){

            //   如果已存在文件为空,那肯定是全部加进来啦。

            for(   String newFilePath : newFileList ){

                differentFileList.add(   newFilePath );

            }

        }else{

            for(   String newFilePath : newFileList ){

                isExist   = false;

                for(   String existFilePath : existFileList ){

                    if(   existFilePath.equals( newFilePath ) ){

                        isExist   = true;

                        break;

                    }

                }

                 

                if(   !isExist ){

                    differentFileList.add(   newFilePath );

                }

            }

        }

         

        return differentFileList;

    }

     

    /**

     * 从媒体库中获取指定后缀的文件列表

     *

     * */

    public ArrayList<string>   getSupportFileList( Context context, String[] searchFileSuffix ) {

        ArrayList<string>   searchFileList = null;

        if(   null == context || null == searchFileSuffix || searchFileSuffix.length == 0 ){

            return   null;

        }

         

        String   searchPath = "";

        int length   = searchFileSuffix.length;

        for(   int index = 0; index < length; index++ ){

            searchPath   += ( MediaStore.Files.FileColumns.DATA + " LIKE '%" +   searchFileSuffix[ index ] + "' " );

            if(   ( index + 1 ) < length ){

                searchPath   += "or ";

            }

        }

         

        searchFileList   = new ArrayList<string>();

        Uri uri   = MediaStore.Files.getContentUri("external");

        Cursor   cursor = context.getContentResolver().query(

                uri,   new String[] { MediaStore.Files.FileColumns.DATA,   MediaStore.Files.FileColumns.SIZE, MediaStore.Files.FileColumns._ID },

                searchPath,   null, null);

         

        String   filepath = null;

        if (cursor   == null) {

            System.out.println("Cursor   获取失败!");

        } else {

            if   (cursor.moveToFirst()) {

                do   {

                    filepath   = cursor.getString(cursor.getColumnIndex(MediaStore.Files.FileColumns.DATA));

                    try   {

                        searchFileList.add(new   String(filepath.getBytes("UTF-8")));

                    }   catch (UnsupportedEncodingException e) {

                        e.printStackTrace();

                    }

 

                }   while (cursor.moveToNext());

            }

 

            if   (!cursor.isClosed()) {

                cursor.close();

            }

        }

 

        return searchFileList;

    }

     

    /**

     * 得到媒体库更新的文件

     *

     * */

    class GetMediaStoreDataTask   extends AsyncTask< Void , Void , Void>{

        @Override

        protected   Void doInBackground(Void... arg0) {

            ArrayList<string>   changedFileList = getChangedFileList( mContext, mSupportSuffix,   mAutoRefreshListener.onGetBookPathList( ) );

            if(   null != changedFileList && changedFileList.size( ) > 0 ){

                mAutoRefreshListener.onBookRefresh(   changedFileList );

            }

            mTimerWorking   = false;

             

            return   null;

        }

    }

     

    class CheckFileChangeTimerTask   extends java.util.TimerTask{

        @Override

        public void   run() {

            new   GetMediaStoreDataTask( ).execute( );

        }

    }

     

    /**

     * 书架自动刷新接口

     *

     * */

    public interface AutoRefreshListener{

        public ArrayList<string>   onGetBookPathList( ); // 得到书架书本列表

        public void   onBookRefresh( ArrayList<string> bookInfoList );// 刷新书架

        public void   onBookScan( );//全盘扫描书架

    }

     

    private boolean mTimerWorking =   false;

    private Context mContext =   null;

    private String[] mSupportSuffix   = null;

    private BroadcastReceiver   mBroadcastReceiver = null;

    private MediaStoreChangeObserver   mMediaStoreChangeObserver = null;

    private AutoRefreshListener   mAutoRefreshListener = null;

    private Timer mCheckFileTimer =   null;

}</string></string></string></string></string></string></string></string></string></string></string></string></string></string></string>

 

注意:建议该功能只在APP运行时开启,因为现实的文件列表只有你真正在使用APP时才会去查看,所以没有必要通过这种方式在后台操作。

原文链接:http://www.apkbus.com/blog-705730-61104.html

0人推荐
随时随地看视频
慕课网APP