Android为 Camera,Audio,Video等媒体文件提供MediaProvider用于数据的保存,删除,检索等。MediaProvider实现了 ContentProvider类的几个操作SQL的重要方法如insert ,delete, updata, query。MediaProvider中DatabaseHelper类用于获取与创建Database。
private static final class DatabaseHelper extends SQLiteOpenHelper {
final Context mContext;
final boolean mInternal; // True if this is the internal database
// In memory caches of artist and album data.
HashMap< String, Long> mArtistCache = new HashMap< String, Long>();
HashMap< String, Long> mAlbumCache = new HashMap< String, Long>();
public DatabaseHelper(Context context, String name, boolean internal) {
super(context, name, null, DATABASE_VERSION);
mContext = context;
mInternal = internal;
}
/**
* Creates database the first time we try to open it.
*/
@Override //第一次打开数据库时创建Table
public void onCreate(final SQLiteDatabase db) {
updateDatabase(db, mInternal, 0, DATABASE_VERSION);
}
以Camera的image文件文件为例,数据库在第一次使用的时候被创建,对于image文件,SQL在updateDatabase为其创建一个table:
db.execSQL("CREATE TABLE IF NOT EXISTS images (" +
"_id INTEGER PRIMARY KEY," + //文件保存的序号
"_data TEXT," + //记录image 文件保存路径
"_size INTEGER," + //文件大小
"_display_name TEXT," + //图片名称
"mime_type TEXT," + //文件类型
"title TEXT," +
"date_added INTEGER," +
"date_modified INTEGER," +
"description TEXT," +
"picasa_id TEXT," +
"isprivate INTEGER," +
"latitude DOUBLE," +
"longitude DOUBLE," +
"datetaken INTEGER," +
"orientation INTEGER," +
"mini_thumb_magic INTEGER," +
"bucket_id TEXT," +
"bucket_display_name TEXT" +
");");
添加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中添加如下语句:
< provider android:name="MediaProvider" android:authorities="media"
android:multiprocess="false" />
Provider加载
MediaProvider安装在手机中,ContentResolver通过acquireProvider请求加载Provider程序。acquireProvider正是通过解析URI中的authority字段,在安装的package中查找与此authority相符的Provider,如果此Provider没有被加载ActivityMangerService将加载Provider程序,这个过程可以参考我以前写的Activity是如何加载的文章。 Provider到底是加载到调用Provider的应用程序还是将Provider程序加载到ActivityManagerService中作为 system Provider这个我不太确定是如何区分的,反正这两种方式都是可行的,总之按照sdk help 文档上的方式创建Provider程序,android会自动加载的。
记录的保存与获取
当Camera拍照完成或者是media play文件需要保存的时候,首先要构造保存的数据结构
ContentValues values = new ContentValues(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);
Uri uri = cr.insert(sStorageURI, values); //保存到数据库
sStorageURI == content://media/external/sdcard/media/
Authority是 media所以将调用MediaProvider::insert,在insert中生成文件保存的路径并放在key为_data项数据区。再通过 ContentResolver访问MediaProvider调用openFile打开文件并将Image或media数据写入。
Camera,Image gallery应用启动后不会扫描文件系统而是根据数据库的记录来进行列表并显示。这样做无疑效率比较高。
文件的删除
数据库中保存有文件的路径名,当调用delete从数据库中删除一个记录后,记录中_data保存的文件名(绝对路径)所指的文件也被删除了。而在应用程序中是找不到删除文件的代码,这个困扰了我两天,在程序中加log,分析源代码,最终屏蔽MediaProvider中的一段代码找到了一点线索:
public int delete(Uri uri, String userWhere, String[] whereArgs) {
int count;
int match = URI_MATCHER.match(uri);
……………………………..
if (match != VOLUMES_ID) {
DatabaseHelper database = getDatabaseForUri(uri);
if (database == null) {
throw new UnsupportedOperationException(
"Unknown URI: " + uri);
}
SQLiteDatabase db = database.getWritableDatabase();
synchronized (sGetTableAndWhereParam) {
getTableAndWhere(uri, match, userWhere, sGetTableAndWhereParam);
switch (match) {
case AUDIO_MEDIA:
case AUDIO_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的代码发现几个语句很奇怪,由于没有网络去搜索一下关于android
SQL的knowledge 只能猜测这个语句的意思了当然也怪偶没去学学SQL。db.execSQL("CREATE TRIGGER IF NOT EXISTS images_cleanup DELETE ON images " +
"BEGIN " +
"DELETE FROM thumbnails WHERE image_id = old._id;" +
"SELECT _DELETE_FILE(old._data);" +
"END");
创建table为 images的触发器,DELETE FROM thumbnails WHERE image_id = old._id正是删除thumbnails的意思,对于Camera创建的Images会生成一个小图片保存为thumbnail,这个记录也是自动清除的。 "SELECT _DELETE_FILE(old._data);" 应该是删除文件的意思。
在Sqlite3_android.cpp文件中还真找到delete_file这个函数,加上log,进入gallery选择一个文件删除,打出了删除文件名的全路径。
static void delete_file(sqlite3_context * context, int argc, sqlite3_value ** argv)
{
………………….
if (strncmp("/sdcard/", path, 8) != 0) { //只能删除sdcard/路径下的东西要删除其他路
sqlite3_result_null(context); //还需稍微改造一下
return;
}
……………………
}
如何将文件保存在手机上
MediaProvider中存在两个数据库一个是external对应文件系统为SD Card, 一个是内部数据库internal用于手机flash上的文件系统。遗憾的是虽然MediaProvider提供了操作内外两个数据库的功能,但在发布的应用中并没有使用这个内部文件系统。Android 默认状态下Image,audio等文件是保存在SD card上。External, internal 数据库都保存在手机文件系统上
Path: /data/data/com.android.providers.media/database/
如果要使用内部文件系统需要修改以下几个地方
1:MediaProvider generateFileName函数
private String generateFileName(boolean internal, String preferredExtension, String directoryName)
{
// create a random file
String name = String.valueOf(System.currentTimeMillis());
if (internal) {
throw new UnsupportedOperationException("Writing to internal storage is not supported.");
// return Environment.getDataDirectory()
// + "/" + directoryName + "/" + name + preferredExtension;
} else {
return Environment.getExternalStorageDirectory()
+ "/" + directoryName + "/" + name + preferredExtension;
}
}
恢复红色部分代码,将路径设置为手机内部文件系统文件夹。
2:保存文件使用的URI为content://media/internal/images/media/1
红色internal表名使用内部文件系统数据库
3:修改Sqlite3_android.cpp中delete_file函数
static void delete_file(sqlite3_context * context, int argc, sqlite3_value ** argv)
{
………………….
if (strncmp("/sdcard/", path, 8) != 0 && strncmp("/data/media/", path, 12)) {
sqlite3_result_null(context);
return;
}
……………………
}
添加红色部分,文件位于手机上/data/media路径时也进行删除。这样当我们从数据库里删除一条记录时,_data数据区存取的文件路径中的文件也将自动删除。