Android MediaProvider 分析
来源:互联网 发布:老男孩linux运维视频 编辑:程序博客网 时间:2024/05/29 07:07
MediaScanner分析
一 MediaScannerService
多媒体扫描是从MediaScannerService开始的。这是一个单独的package。位于
packages/providers/MediaProvider:含以下java文件
l MediaProvider.java
l MediaScannerReceiver.java
l MediaScannerService.java
l MediaThumbRequest.java
分析这个目录的Android.mk文件,发现它运行的进程名字就是android.process.media。
application android:process=android.process.media
1.1 MediaScannerReceiver
这个类从BroadcastReceiver中派生,用来接收任务的。
MediaScannerReceiver extends BroadcastReceiver在它重载的onRecieve函数内有以下几种走向:if (action.equals(Intent.ACTION_BOOT_COMPLETED)) { // 收到”启动完毕“广播后,扫描内部存储 scan(context, MediaProvider.INTERNAL_VOLUME); } else { ………. if (action.equals(Intent.ACTION_MEDIA_MOUNTED) && externalStoragePath.equals(path)) { /收到MOUNT信息后,扫描外部存储 scan(context, MediaProvider.EXTERNAL_VOLUME); } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) && path != null && path.startsWith(externalStoragePath + "/")) { //收到请求要求扫描某个文件,注意不会扫描内部存储上的文件 scanFile(context, path); ………………………….. }……下面是它调用的scan函数:scan(Context context, String volume)Bundle args = new Bundle(); args.putString("volume", volume);//直接启动MediaScannerService了, context.startService( new Intent(context, MediaScannerService.class).putExtras(args));
总结:
MediaScannerReceiver是用来接收任务的,它收到广播后,会启动MediaService进行扫描工作。
下面看看MediaScannerService.
1.2 MediaScannerService
MSS标准的从Service中派生下来,
MediaScannerService extends Service implements Runnable
//注意:是一个Runnable…,可能有线程之类的东西存在
下面从Service的生命周期的角度来看看它的工作。
1、 onCreate
public void onCreate() PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); //获得电源锁,防止在扫描过程中休眠 //单独搞一个线程去跑扫描工作,防止ANR Thread thr = new Thread(null, this, "MediaScannerService"); thr.start();
2、 onStartCommand
@Override public int onStartCommand(Intent intent, int flags, int startId) { //注意这个handler,是在另外一个线程中创建的,往这个handler里sendMessage //都会在那个线程里边处理 //不明白的可以去查看handler和Looper机制//这里就是同步机制,等待mServiceHandler在另外那个线程创建完毕while (mServiceHandler == null) { synchronized (this) { try { wait(100); } catch (InterruptedException e) { } } } if (intent == null) { Log.e(TAG, "Intent is null in onStartCommand: ", new NullPointerException()); return Service.START_NOT_STICKY; } Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent.getExtras();//把MediaScannerReceiver发出的消息传递到另外那个线程去处理。 mServiceHandler.sendMessage(msg);
基本上MSR(MediaScannerReceiver)发出的请求都会传到onStartCommand中处理。如果有多个存储的话,也只能一个一个扫描了。
下面看看那个线程的主函数
3、run
public void run() { // reduce priority below other background threads to avoid interfering // with other services at boot time. Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND + Process.THREAD_PRIORITY_LESS_FAVORABLE);//不明白的去看看Looper和handler的实现 Looper.prepare();//把这个looper对象设置到线程本地存储 mServiceLooper = Looper.myLooper(); mServiceHandler = new ServiceHandler();//创建handler,默认会把这个looper//的消息队列赋值给handler的消息队列,这样往handler中发送消息就是往这个线程的looper发 Looper.loop();//消息循环,内部会处理消息队列中的消息//也就是handleMessage函数}
上面handler中加入了一个扫描请求(假设是外部存储的),所以要分析handleMessage函数。
4、 handleMessage
private final class ServiceHandler extends Handler { @Override public void handleMessage(Message msg) { Bundle arguments = (Bundle) msg.obj; String filePath = arguments.getString("filepath"); try {……… 这里不讲了 } else { String volume = arguments.getString("volume"); String[] directories = null; if (MediaProvider.INTERNAL_VOLUME.equals(volume)) { //是扫描内部存储的请求? // scan internal media storage directories = new String[] { Environment.getRootDirectory() + "/media", }; } else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) { //是扫描外部存储的请求?获取外部存储的路径 directories = new String[] { Environment.getExternalStorageDirectory().getPath(), }; } if (directories != null) {//真正的扫描开始了,上面只不过是把存储路径取出来罢了. scan(directories, volume); …..//扫描完了,就把service停止了 stopSelf(msg.arg1); }};
5、 scan函数
private void scan(String[] directories, String volumeName) { mWakeLock.acquire();//下面这三句话很深奥…//从 getContentResolver获得一个ContentResover,然后直接插入//根据AIDL,这个ContentResover的另一端是MediaProvider。只要去看看它的//insert函数就可以了//反正这里知道获得了一个扫描URI即可。 ContentValues values = new ContentValues(); values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName); Uri scanUri = getContentResolver().insert(MediaStore.getMediaScannerUri(), values); Uri uri = Uri.parse("file://" + directories[0]);//发送广播,通知扫描开始了 sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri)); try { if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) { openDatabase(volumeName); }//创建真正的扫描器 MediaScanner scanner = createMediaScanner();//交给扫描器去扫描文件夹 scanDirectories scanner.scanDirectories(directories, volumeName); } catch (Exception e) { Log.e(TAG, "exception in MediaScanner.scan()", e); }//删除扫描路径 getContentResolver().delete(scanUri, null, null);//通知扫描完毕 sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri)); mWakeLock.release();}说说上面那个深奥的地方,在MediaProvider中重载了insert函数,insert函数会调用insertInternal函数。如下:private Uri insertInternal(Uri uri, ContentValues initialValues) { long rowId; int match = URI_MATCHER.match(uri); // handle MEDIA_SCANNER before calling getDatabaseForUri()//刚才那个insert只会走下面这个分支,其实就是获得一个地址….//太绕了!!!!! if (match == MEDIA_SCANNER) { mMediaScannerVolume = initialValues.getAsString(MediaStore.MEDIA_SCANNER_VOLUME); return MediaStore.getMediaScannerUri(); }……..再看看它创建了什么样的Scanner,这就是MSS中的createMediaScannerprivate MediaScanner createMediaScanner() {//下面这个MediaScanner在framework/base/中,待会再分析 MediaScanner scanner = new MediaScanner(this);//设置当前的区域,这个和字符编码有重大关系。 Locale locale = getResources().getConfiguration().locale; if (locale != null) { String language = locale.getLanguage(); String country = locale.getCountry(); String localeString = null; if (language != null) { if (country != null) {//给扫描器设置当前国家和语言。 scanner.setLocale(language + "_" + country); } else { scanner.setLocale(language); } } } return scanner;}
至此,MSS的任务完成了。接下来是MediaScanner的工作了。
6、 总结
MSS的工作流程如下:
l 1 单独启动一个带消息循环的工作线程。
l 2 主线程接收系统发来的任务,然后发送给工作线程去处理。
l 3 工作线程接收任务,创建一个MediaScanner去扫描。
l 4 MSS顺带广播一下扫描工作启动了,扫描工作完毕了。
二 MediaScanner
MediaScanner位置在
frameworks/base/media/下,包括jni和java文件。
先看看java实现。
这个类巨复杂,而且和MediaProvider交互频繁。在分析的时候要时刻回到MediaProvider去看看。
1、 初始化
public class MediaScanner{static {//libmedia_jni.so的加载是在MediaScanner类中完成的//这么重要的so为何放在如此不起眼的地方加载??? System.loadLibrary("media_jni"); native_init();}public MediaScanner(Context c) { native_setup();//调用jni层的初始化,暂时不用看了,无非就是一些//初始化工作,待会在再进去看看 }
刚才MSS中是调用scanDirectories函数,我们看看这个。
3、 scanDirectories
public void scanDirectories(String[] directories, String volumeName) { try { long start = System.currentTimeMillis(); initialize(volumeName);//初始化 prescan(null);//扫描前的预处理 long prescan = System.currentTimeMillis(); for (int i = 0; i < directories.length; i++) {//扫描文件夹,这里有一个很重要的参数 mClient// processDirectory是一个native函数 processDirectory(directories[i], MediaFile.sFileExtensions, mClient); } long scan = System.currentTimeMillis(); postscan(directories);//扫描后处理 long end = System.currentTimeMillis(); 打印时间,异常处理…没了…
下面简单讲讲initialize ,preScan和postScan都干嘛了。
private void initialize(String volumeName) {//打开MediaProvider,获得它的一个实例 mMediaProvider = mContext.getContentResolver().acquireProvider("media");//得到一些uri mAudioUri = Audio.Media.getContentUri(volumeName); mVideoUri = Video.Media.getContentUri(volumeName); mImagesUri = Images.Media.getContentUri(volumeName); mThumbsUri = Images.Thumbnails.getContentUri(volumeName);//外部存储的话,可以支持播放列表之类的东西,搞了一些个缓存池之类的//如mGenreCache等 if (!volumeName.equals("internal")) { // we only support playlists on external media mProcessPlaylists = true; mGenreCache = new HashMap<String, Uri>(); …
preScan,这个函数很复杂:
大概就是创建一个FileCache,用来缓存扫描文件的一些信息,例如last_modified等。这个FileCache是从MediaProvider中已有信息构建出来的,也就是历史信息。后面根据扫描得到的新信息来对应更新历史信息。
postScan,这个函数做一些清除工作,例如以前有video生成了一些缩略图,现在video文件被干掉了,则对应的缩略图也要被干掉。
另外还有一个mClient,这个是从MediaScannerClient派生下来的一个东西,里边保存了一个文件的一些信息。后续再分析。
刚才说到,具体扫描工作是在processDirectory函数中完成的。这个是一个native函数。
在frameworks/base/media/jni/android_media_MediaScanner.cpp中。
三 MediaScanner JNI层分析
MediaScanner JNI层内容比较多,单独搞一节分析吧。
先看看android_media_MediaScanner这个文件。
1、native_init函数,jni对应的函数如下
static voidandroid_media_MediaScanner_native_init(JNIEnv *env){ jclass clazz;clazz = env->FindClass("android/media/MediaScanner");//得都JAVA类中mNativeContext这个成员id fields.context = env->GetFieldID(clazz, "mNativeContext", "I"); //不熟悉JNI的自己去学习下吧}
2、 native_setup函数,jni对应函数如下:
android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz){//创建MediaScanner对象 MediaScanner *mp = createMediaScanner();//太变态了,自己不保存这个对象指针.//却把它设置到java对象的mNativeContext去保存 env->SetIntField(thiz, fields.context, (int)mp);}//创建MediaScanner函数static MediaScanner *createMediaScanner() {#if BUILD_WITH_FULL_STAGEFRIGHT ..//使用google自己的 return new StagefrightMediaScanner; #endif#ifndef NO_OPENCORE//使用opencore提供的…. return new PVMediaScanner();#endif
3、 processDirectories函数,jni对应如下:
android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client){ MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context);//每次都要回调到JAVA中去取这个Scanner!! ……… const char *pathStr = env->GetStringUTFChars(path, NULL); const char *extensionsStr = env->GetStringUTFChars(extensions, NULL);…….//又在C++这里搞一个client,然后把java的client放到C++Client中去保存//而且还是栈上的临时变量..MyMediaScannerClient myClient(env, client);//scanner扫描文件夹,用得是C++的client mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env); env->ReleaseStringUTFChars(path, pathStr); env->ReleaseStringUTFChars(extensions, extensionsStr);}
到这里似乎就没有了,那么扫描后的数据库是怎么更新的呢?为什么要传入一个client进去呢?看来必须得trace到scanner中去才知道了。
四 PVMediaScanner
这个在external/opencore/android/mediascanner.cpp中。
1、 processDirectory
status_t MediaScanner::processDirectory(const char *path, const char* extensions, MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv){ InitializeForThread(); int error = 0;status_t result = PVMFSuccess;….//调用client的设置区域函数 client.setLocale(mLocale);//扫描文件夹,咋还没开始?? result = doProcessDirectory(pathBuffer, pathRemaining, extensions, client, exceptionCheck, exceptionEnv);..
2、 doProcessDirectory
status_t MediaScanner::doProcessDirectory(char *path, int pathRemaining, const char* extensions, MediaScannerClient& client, ExceptionCheck exceptionCheck, void* exceptionEnv){…终于看到点希望了//打开这个文件夹,枚举其中的内容。//题外话,这个时候FileManager肯定删不掉这个文件夹!! DIR* dir = opendir(path); while ((entry = readdir(dir))) { const char* name = entry->d_name;//不处理.和..文件夹 if (isDirectory) {……..//不处理.开头的文件夹。如果是文件夹,递归调用doProcessDirectory//深度优先啊! int err = doProcessDirectory(path, pathRemaining - nameLength - 1, extensions, client, exceptionCheck, exceptionEnv); if (err) { LOGE("Error processing '%s' - skipping/n", path); continue; } } else if (fileMatchesExtension(path, extensions)) {//是一个可以处理的文件,交给client处理//彻底疯掉了….这是干嘛呢??? client.scanFile(path, statbuf.st_mtime, statbuf.st_size);
这里要解释下,刚才createMediaScanner中,明明创建的是PVMediaScanner,为何这里看得是MediaScanner代码呢?
l 因为PVMediaScanner从MediaScanner中派生下来的,而且没有重载processDirectory函数
l Eclaire没有PVMediaScanner这样的东西,估计是froyo又改了点啥吧。
FT,processDirctory无非是列举一个目录内容,然后又反回去调用client的scanFile处理了。为何搞这么复杂?只有回去看看C++的client干什么了。
3、 MediaScannerClient—JNI层
JNI中的这个类是这样的:
class MyMediaScannerClient : public MediaScannerClient//这是它的scanFile实现virtual bool scanFile(const char* path, long long lastModified, long long fileSize) {//再次崩溃了,C++的client调用了刚才传进去的java Client的//scanFile函数…不过这次还传进去了该文件的路径,最后修改时间和文件大小。 mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified, fileSize);…..想死,,, }
没办法了,只能再去看看MediaScanner.java传进去的那个client了。
4、 MediaScannerClient—-JAVA层
这个类在MediaScanner.java中实现。
private class MyMediaScannerClient implements MediaScannerClient:public void scanFile(String path, long lastModified, long fileSize) {//调用doScanFile..很烦.. doScanFile(path, null, lastModified, fileSize, false);…//下面是doScanFilepublic Uri doScanFile(String path, String mimeType, long lastModified, long fileSize, boolean scanAlways) {//预处理,看看之前创建的文件缓存中有没有这个文件 FileCacheEntry entry = beginFile(path, mimeType, lastModified, fileSize); // rescan for metadata if file was modified since last scan if (entry != null && (entry.mLastModifiedChanged || scanAlways)) {//如果事先有这个文件的信息,则需要修改一些信息,如长度,最后修改时间等 …..//真正的扫描文件 processFile(path, mimeType, this);//扫描完了,做最后处理 endFile(entry, ringtones, notifications, alarms, music, podcasts);
processFile又是jni层的。
对应android_media_MediaScanner_processFile函数
android_media_MediaScanner_processFile(JNIEnv *env, jobject thiz, jstring path, jstring mimeType, jobject client){ MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); //无语了,又搞一个 MyMediaScannerClientMyMediaScannerClient myClient(env, client); mp->processFile(pathStr, mimeTypeStr, myClient);}
第一次到PVMediaScanner中来了
status_t PVMediaScanner::processFile(const char *path, const char* mimeType, MediaScannerClient& client){ status_t result; InitializeForThread();//调用client的beginFile,估计是做一些啥预处理 client.setLocale(locale()); client.beginFile(); //LOGD("processFile %s mimeType: %s/n", path, mimeType); const char* extension = strrchr(path, '.'); if (extension && strcasecmp(extension, ".mp3") == 0) { result = parseMP3(path, client);//client又传进去了…根据后缀名去扫描….}client.endFile();
到parseMP3去看看。这个在
static PVMFStatus parseMP3(const char *filename, MediaScannerClient& client){//这个函数太专业了,和编解码有关,我们重点关注client在这里干什么了…//原来,解析器从文件中解析出一个信息,就调用client的addStringTagif (!client.addStringTag("duration", buffer))
addStringTag在JNI的client中处理。
这个MediaScannerClient是在opencore中的那个MediaScanner.cpp中实现的,而android_media_MediaScanner.cpp中的是MyMediaScannerClient,从MediaScannerClient派生下来的
bool MediaScannerClient::addStringTag(const char* name, const char* value){ if (mLocaleEncoding != kEncodingNone) { //字符串编码之类的转换。不详述了 bool nonAscii = false; const char* chp = value; char ch; while ((ch = *chp++)) { if (ch & 0x80) { nonAscii = true; break; } }//如果不是ASCII编码的话,内部先保存一下这些个tag信息//待会扫描完后再集中做一次字符串编码转换 if (nonAscii) { // save the strings for later so they can be used for native encoding detection mNames->push_back(name); mValues->push_back(value); return true; } // else fall through }//调用子类的handleStringTag return handleStringTag(name, value);}class MyMediaScannerClient : public MediaScannerClient{//调用到子类的handleStringTag了virtual bool handleStringTag(const char* name, const char* value){//又传递到JAVA层的handleStringTag来处理//麻木了.. mEnv->CallVoidMethod(mClient, mHandleStringTagMethodID, nameStr, valueStr); }JAVA层MediaScannerService中的MyMediaScannerClient类public void handleStringTag(String name, String value) {//下层扫描的文件tag信息,全部处理后赋值给java层这个MyScannerClient了例如MP3的title,专辑名等等。 …. int num = parseSubstring(value, 0, 0); mTrack = (num * 1000) + (mTrack % 1000); } else if (name.equalsIgnoreCase("duration")) { mDuration = parseSubstring(value, 0, 0); } else if (name.equalsIgnoreCase("writer") || name.startsWith("writer;")) { mWriter = value.trim();
到这里了,还没写到数据库呢?啥时候更新数据库?看来是在client.endFile()中了。
但是这个endClient并没有调用到JAVA层去。那在哪里结束呢?
还记得JAVA中的doScanFile函数吗,对了,这个endFile就是在那里直接由JAVA调用的。
private Uri endFile(FileCacheEntry entry, boolean ringtones, boolean notifications, boolean alarms, boolean music, boolean podcasts) throws RemoteException { // update database Uri tableUri; boolean isAudio = MediaFile.isAudioFileType(mFileType); boolean isVideo = MediaFile.isVideoFileType(mFileType); boolean isImage = MediaFile.isImageFileType(mFileType); ….//来了一个新文件,直接插入数据库 result = mMediaProvider.insert(tableUri, values);//或者更新数据库mMediaProvider.update(result, values, null, null);
这回真算是完了。
5.流程总结
l MediaScanner(MS)调用scanDirectories中的processDirectory,进入到JNI层
l JNI调用PVMediaScanner的processDirectory
l PVMediaScanner的processDirectory为目录下的文件调用MyMediaScannerClient的scanFile
l MyMediaScannerClient
- Android MediaProvider数据库分析
- Android MediaProvider源码分析
- Android MediaProvider 分析
- MediaProvider分析
- Android开发——MediaProvider源码分析
- Android开发——MediaProvider源码分析
- Android开发——MediaProvider源码分析 .
- Android media媒体库分析之:MediaProvider
- android mediaprovider
- Android开发——MediaProvider源码分析(1)
- Android开发——MediaProvider源码分析(2)
- Android开发——MediaProvider源码分析(一)
- Android开发——MediaProvider源码分析(二)
- Android开发——MediaProvider源码分析(1)
- Android开发——MediaProvider源码分析(2)
- Android开发——MediaProvider源码分析(1)
- Android开发——MediaProvider源码分析(2)
- Android开发——MediaProvider源码分析(1)
- gclub บนมือถือ 24 ชั่วโมง
- Factorization Machines 学习笔记(二)模型方程
- android-实现WebView只能滚动不能点击
- html中的tabIndex属性
- NodeJs服务器管理
- Android MediaProvider 分析
- JavaScript高级程序设计(第三版)学习笔记(1)
- 数据结构——表达式求值(二)
- java中静态代码块的用法 static用法详解
- Factorization Machines 学习笔记(三)回归和分类
- 高性能的敏感词过滤算法 可以忽略大小写、全半角、简繁体、特殊符号干扰 (一)
- 文章标题
- iOS多线程的初步研究(三)-- NSRunLoop
- 【Android应用开发技术:应用组件】:Activity基本原理