Android如何通过contentprovider构建媒体文件数据库Android为Camera,Audio,Video等媒体文件提供MediaProvider用于数据的保存,删除,检索等。MediaProvider实现了ContentProvider类的几个操作SQL的重要方法如insert,delete,updata,query。MediaProvider中DatabaseHelper类用于获取与创建Database。privatestaticfinalclassDatabaseHelperextendsSQLiteOpenHelper{finalContextmContext;finalbooleanmInternal;//Trueifthisistheinternaldatabase//Inmemorycachesofartistandalbumdata.HashMapString,LongmArtistCache=newHashMapString,Long();HashMapString,LongmAlbumCache=newHashMapString,Long();publicDatabaseHelper(Contextcontext,Stringname,booleaninternal){super(context,name,null,DATABASE_VERSION);mContext=context;mInternal=internal;}/***Createsdatabasethefirsttimewetrytoopenit.*/@Override//第一次打开数据库时创建TablepublicvoidonCreate(finalSQLiteDatabasedb){updateDatabase(db,mInternal,0,DATABASE_VERSION);}以Camera的image文件文件为例,数据库在第一次使用的时候被创建,对于image文件,SQL在updateDatabase为其创建一个table:db.execSQL(CREATETABLEIFNOTEXISTSimages(+_idINTEGERPRIMARYKEY,+//文件保存的序号_dataTEXT,+//记录image文件保存路径_sizeINTEGER,+//文件大小_display_nameTEXT,+//图片名称mime_typeTEXT,+//文件类型titleTEXT,+date_addedINTEGER,+date_modifiedINTEGER,+descriptionTEXT,+picasa_idTEXT,+isprivateINTEGER,+latitudeDOUBLE,+longitudeDOUBLE,+datetakenINTEGER,+orientationINTEGER,+mini_thumb_magicINTEGER,+bucket_idTEXT,+bucket_display_nameTEXT+););添加URI到match这个也很重要,因为不同类型的文件会保存在不同的table中,如果我们得到一个URI为:content://media/external/images/media/1通过UriMatcher.match(uri)将返回注册的IMAGES_MEDIA_ID值。URI_MATCHER.addURI(media,*/images/media,IMAGES_MEDIA);URI_MATCHER.addURI(media,*/images/media/#,IMAGES_MEDIA_ID);URI格式解析如下:URI格式中最重要的字段是authority,authority确定了操作数据库的Provider是由谁提供的。MediaProvider在AndroidManifest.xml中添加如下语句:providerandroid:name=MediaProviderandroid:authorities=mediaandroid:multiprocess=false/Provider加载MediaProvider安装在手机中,ContentResolver通过acquireProvider请求加载Provider程序。acquireProvider正是通过解析URI中的authority字段,在安装的package中查找与此authority相符的Provider,如果此Provider没有被加载ActivityMangerService将加载Provider程序,这个过程可以参考我以前写的Activity是如何加载的文章。Provider到底是加载到调用Provider的应用程序还是将Provider程序加载到ActivityManagerService中作为systemProvider这个我不太确定是如何区分的,反正这两种方式都是可行的,总之按照sdkhelp文档上的方式创建Provider程序,android会自动加载的。记录的保存与获取当Camera拍照完成或者是mediaplay文件需要保存的时候,首先要构造保存的数据结构ContentValuesvalues=newContentValues(7);values.put(Images.Media.TITLE,imageName);values.put(Images.Media.DISPLAY_NAME,imageName);values.put(Images.Media.DESCRIPTION,description);values.put(Images.Media.DATE_TAKEN,dateTaken);values.put(Images.Media.MIME_TYPE,image/jpeg);values.put(Images.Media.ORIENTATION,orientation);Uriuri=cr.insert(sStorageURI,values);//保存到数据库sStorageURI==content://media/external/sdcard/media/Authority是media所以将调用MediaProvider::insert,在insert中生成文件保存的路径并放在key为_data项数据区。再通过ContentResolver访问MediaProvider调用openFile打开文件并将Image或media数据写入。Camera,Imagegallery应用启动后不会扫描文件系统而是根据数据库的记录来进行列表并显示。这样做无疑效率比较高。文件的删除数据库中保存有文件的路径名,当调用delete从数据库中删除一个记录后,记录中_data保存的文件名(绝对路径)所指的文件也被删除了。而在应用程序中是找不到删除文件的代码,这个困扰了我两天,在程序中加log,分析源代码,最终屏蔽MediaProvider中的一段代码找到了一点线索:publicintdelete(Uriuri,StringuserWhere,String[]whereArgs){intcount;intmatch=URI_MATCHER.match(uri);……………………………..if(match!=VOLUMES_ID){DatabaseHelperdatabase=getDatabaseForUri(uri);if(database==null){thrownewUnsupportedOperationException(UnknownURI:+uri);}SQLiteDatabasedb=database.getWritableDatabase();synchronized(sGetTableAndWhereParam){getTableAndWhere(uri,match,userWhere,sGetTableAndWhereParam);switch(match){caseAUDIO_MEDIA:caseAUDIO_MEDIA_ID:count=db.delete(audio_meta,sGetTableAndWhereParam.where,whereArgs);break;default://count=db.delete(sGetTableAndWhereParam.table,//sGetTableAndWhereParam.where,whereArgs);break;}getContext().getContentResolver().notifyChange(uri,null);}}……………………..}将上面红色代码注掉以后发现文件没有被删除,而红色代码只是对数据库进行操作,因而文件应该是在清除数据库的记录时候被删除的。仔细查看创建table的代码发现几个语句很奇怪,由于没有网络去搜索一下关于androidSQL的knowledge只能猜测这个语句的意思了当然也怪偶没去学学SQL。db.execSQL(CREATETRIGGERIFNOTEXISTSimages_cleanupDELETEONimages+BEGIN+DELETEFROMthumbnailsWHEREimage_id=old._id;+SELECT_DELETE_FILE(old._data);+END);创建table为images的触发器,DELETEFROMthumbnailsWHEREimage_id=old._id正是删除thumbnails的意思,对于Camera创建的Images会生成一个小图片保存为thumbnail,这个记录也是自动清除的。SELECT_DELETE_FILE(old._data);应该是删除文件的意思。在Sqlite3_android.cpp文件中还真找到delete_file这个函数,加上log,进入gallery选择一个文件删除,打出了删除文件名的全路径。staticvoiddelete_file(sqlite3_context*context,intargc,sqlite3_value**argv){………………….if(strncmp(/sdcard/,path,8)!=0){//只能删除sdcard/路径下的东西要删除其他路sqlite3_result_null(context);//还需稍微改造一下return;}……………………}如何将文件保存在手机上MediaProvider中存在两个数据库一个是external对应文件系统为SDCard,一个是内部数据库internal用于手机flash上的文件系统。遗憾的是虽然MediaProvider提供了操作内外两个数据库的功能,但在发布的应用中并没有使用这个内部文件系统。Android默认状态下Image,audio等文件是保存在SDcard上。External,internal数据库都保存在手机文件系统上Path:/data/data/com.android.providers.media/database/如果要使用内部文件系统需要修改以下几个地方1:MediaProvidergenerateFileName函数privateStringgenerateFileName(booleaninternal,StringpreferredExtension,StringdirectoryName){//createarandomfileStringname=String.valueOf(System.currentTimeMillis());if(internal){thrownewUnsupportedOperationException(Writingtointernalstorageisnotsupported.);//returnEnvironment.getDataDirectory()//+/