- 深入解析Android 虚拟机
- 钟世礼
- 1749字
- 2020-06-28 05:36:03
4.2 分析Java层
在MediaScanner系统中,JNI的调用关系为:
MediaScanner -------------libmedia_jni.so -------------libmedia.so
在Android系统中,MediaScanner的功能是扫描媒体文件,得到诸如歌曲时长、歌曲作者等信息,并将这些信息存放到媒体数据库中,以供其他应用程序使用。
在JNI应用中,Java层MediaScanner的实现文件为:
/frameworks/base/media/java/android/media/MediaScanner.java
在本节的内容中,将详细讲解MediaScanner系统中Java层的具体实现过程。
4.2.1 加载JNl库
在文件MediaScanner.java中,首先定义类MediaScanner并加载JNI库,然后定义JNI的Native(本地)函数。主要代码如下所示:
public class MediaScanner { static { System.loadLibrary("media_jni"); native_init(); } private final static String TAG = "MediaScanner"; private static final String[] FILES_PRESCAN_PROJECTION = new String[] { Files.FileColumns._ID,//0 Files.FileColumns.DATA,//1 Files.FileColumns.FORMAT,//2 Files.FileColumns.DATE_MODIFIED,//3 }; private static final String[] ID_PROJECTION = new String[] { Files.FileColumns._ID, }; private static final int FILES_PRESCAN_ID_COLUMN_INDEX = 0; private static final int FILES_PRESCAN_PATH_COLUMN_INDEX = 1; private static final int FILES_PRESCAN_FORMAT_COLUMN_INDEX = 2; private static final int FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX = 3; private static final String[] PLAYLIST_MEMBERS_PROJECTION = new String[] { Audio.Playlists.Members.PLAYLIST_ID,//0 }; private static final int ID_PLAYLISTS_COLUMN_INDEX = 0; private static final int PATH_PLAYLISTS_COLUMN_INDEX = 1; private static final int DATE_MODIFIED_PLAYLISTS_COLUMN_INDEX = 2; private static final String RINGTONES_DIR = "/ringtones/"; private static final String NOTIFICATIONS_DIR = "/notifications/"; private static final String ALARMS_DIR = "/alarms/"; private static final String MUSIC_DIR = "/music/"; private static final String PODCAST_DIR = "/podcasts/"; …… private static native final void native_init(); //声明一个native函数,native为关键字 private native final void native_setup(); …… }
函数native_init位于包android.media中,其完整路径名为:
android.media.MediaScanner.nantive_init
根据规则其对应的JNI层函数名称为:
android_media_MediaScanner_native_init
在调用函数native之前需要先加载JNI库,一般在类的static中加载调用函数System.loadLibrary()。在加载了相应的JNI库之后,如果要使用相应的native函数,只需使用native声明需要被调用的函数即可:
private native void processDirectory(String path, String extensions, MediaScannerClient client); private native void processFile(String path, String mimeType, MediaScannerClient client); public native void setLocale(String locale);
4.2.2 实现扫描工作
在文件MediaScanner.java中,通过函数scanDirectories实现扫描工作,具体实现代码如下所示:
public void scanDirectories(String[] directories, String volumeName) { try { long start = System.currentTimeMillis(); initialize(volumeName); //初始化 prescan(null, true); //扫描前的预处理 long prescan = System.currentTimeMillis(); if (ENABLE_BULK_INSERTS) { //create MediaInserter for bulk inserts mMediaInserter = new MediaInserter(mMediaProvider, 500); } //函数processDirectory是一个Native函数,功能是对目标文件夹进行扫描 for (int i = 0; i < directories.length; i++) { processDirectory(directories[i], mClient); } if (ENABLE_BULK_INSERTS) { //flush remaining inserts mMediaInserter.flushAll(); mMediaInserter = null; } long scan = System.currentTimeMillis(); postscan(directories); //扫描后的处理 long end = System.currentTimeMillis(); if (false) { Log.d(TAG, " prescan time: " + (prescan - start) + "ms\n"); Log.d(TAG, " scan time: " + (scan - prescan) + "ms\n"); Log.d(TAG, "postscan time: " + (end - scan) + "ms\n"); Log.d(TAG, " total time: " + (end - start) + "ms\n"); } } catch (SQLException e) { //this might happen if the SD card is removed while the media scanner is running Log.e(TAG, "SQLException in MediaScanner.scan()", e); } catch (UnsupportedOperationException e) { //this might happen if the SD card is removed while the media scanner is running Log.e(TAG, "UnsupportedOperationException in MediaScanner.scan()", e); } catch (RemoteException e) { Log.e(TAG, "RemoteException in MediaScanner.scan()", e); } }
在上述代码中用到了函数initialize,此函数的功能是实现初始化操作,具体实现代码如下所示:
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); mFilesUri = Files.getContentUri(volumeName); //如果需要外部存储的话,则可以支持播放列表,用缓存池实现,例如mGenreCache等 if (! volumeName.equals("internal")) { //we only support playlists on external media mProcessPlaylists = true; mProcessGenres = true; mPlaylistsUri = Playlists.getContentUri(volumeName); mCaseInsensitivePaths = true; } }
4.2.3 读取并保存信息
在文件MediaScanner.java中,函数prescan的功能是读取之前扫描的数据库中和文件相关的信息并保存起来。此函数创建了一个FileCache,用来缓存扫描文件的一些信息,例如last_modified等。这个FileCache是从MediaProvider中已有信息构建出来的,也就是历史信息。后面根据扫描得到的新信息来对应更新历史信息。函数prescan的具体实现代码如下所示:
private void prescan(String filePath, boolean prescanFiles) throws RemoteException { Cursor c = null; String where = null; String[] selectionArgs = null; //mPlayLists保存从数据库中获取的信息 if (mPlayLists == null) { mPlayLists = new ArrayList<FileEntry>(); } else { mPlayLists.clear(); } if (filePath ! = null) { //只有一个文件查询 where = MediaStore.Files.FileColumns._ID + ">? " + " AND " + Files.FileColumns.DATA + "=? "; selectionArgs = new String[] { "", filePath }; } else { where = MediaStore.Files.FileColumns._ID + ">? "; selectionArgs = new String[] { "" }; } //告诉提供者不删除文件. //如果不需要删除文件,则需要避免意外删除这个文件的机制 //这可能在系统未被安装和未安装在扫描仪之前发生 Uri.Builder builder = mFilesUri.buildUpon(); builder.appendQueryParameter(MediaStore.PARAM_DELETE_DATA, "false"); MediaBulkDeleter deleter = new MediaBulkDeleter(mMediaProvider, builder.build()); //根据内容提供者建立文件列表 try { if (prescanFiles) { //首先从文件表读到现有文件 //因为可能存在删除不存在文件的情况,所以要小批量的实现数据库查询以避免这个问题 long lastId = Long.MIN_VALUE; Uri limitUri = mFilesUri.buildUpon().appendQueryParameter("limit", "1000").build(); mWasEmptyPriorToScan = true; while (true) { selectionArgs[0] = "" + lastId; if (c ! = null) { c.close(); c = null; } c = mMediaProvider.query(limitUri, FILES_PRESCAN_PROJECTION, where, selectionArgs, MediaStore.Files.FileColumns._ID, null); if (c == null) { break; } int num = c.getCount(); if (num == 0) { break; } mWasEmptyPriorToScan = false; while (c.moveToNext()) { long rowId = c.getLong(FILES_PRESCAN_ID_COLUMN_INDEX); String path = c.getString(FILES_PRESCAN_PATH_COLUMN_INDEX); int format = c.getInt(FILES_PRESCAN_FORMAT_COLUMN_INDEX); long lastModified = c.getLong(FILES_PRESCAN_DATE_MODIFIED_COLUMN_INDEX); lastId = rowId; //Only consider entries with absolute path names. //This allows storing URIs in the database without the //media scanner removing them. if (path ! = null && path.startsWith("/")) { boolean exists = false; try { exists = Libcore.os.access(path, libcore.io.OsConstants.F_OK); } catch (ErrnoException e1) { } if (! exists && ! MtpConstants.isAbstractObject(format)) { //do not delete missing playlists, since they may have been //modified by the user. //The user can delete them in the media player instead. //instead, clear the path and lastModified fields in the row MediaFile.MediaFileType mediaFileType = MediaFile.getFileType(path); int fileType = (mediaFileType == null ? 0 : mediaFileType.fileType); if (! MediaFile.isPlayListFileType(fileType)) { deleter.delete(rowId); if (path.toLowerCase(Locale.US).endsWith("/.nomedia")) { deleter.flush(); String parent = new File(path).getParent(); mMediaProvider.call(MediaStore.UNHIDE_CALL, parent, null); } } } } } } } } finally { if (c ! = null) { c.close(); } deleter.flush(); } //计算图像的原始尺寸 mOriginalCount = 0; c = mMediaProvider.query(mImagesUri, ID_PROJECTION, null, null, null, null); if (c ! = null) { mOriginalCount = c.getCount(); c.close(); } }
4.2.4 删除SD卡外的信息
在文件MediaScanner.java中,函数postscan的功能是删除不存在于SD卡中的文件信息。函数postscan的具体实现代码如下所示:
private void postscan(String[] directories) throws RemoteException { //触发播放列表后能够知道对应存储的媒体文件 if (mProcessPlaylists) { processPlayLists(); } if (mOriginalCount == 0 && mImagesUri.equals(Images.Media.getContentUri("external"))) pruneDeadThumbnailFiles(); //允许GC清理 mPlayLists = null; mMediaProvider = null; }
4.2.5 直接转向JNl
在文件MediaScanner.java中,processDirectory是一个本地方法,能够直接转向JNI。具体实现代码如下所示:
static void android_media_MediaScanner_processDirectory(JNIEnv *env, jobject thiz, jstring path, jstring extensions, jobject client) { //获取MediaScanner MediaScanner *mp = (MediaScanner *)env->GetIntField(thiz, fields.context); //参数判断,并抛出异常 if (path == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return; } if (extensions == NULL) { jniThrowException(env, "java/lang/IllegalArgumentException", NULL); return; } const char *pathStr = env->GetStringUTFChars(path, NULL); if (pathStr == NULL) { //Out of memory jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); return; } const char *extensionsStr = env->GetStringUTFChars(extensions, NULL); if (extensionsStr == NULL) { //Out of memory env->ReleaseStringUTFChars(path, pathStr); jniThrowException(env, "java/lang/RuntimeException", "Out of memory"); return; } //初始化client实例 MyMediaScannerClient myClient(env, client); //mp调用processDirectory mp->processDirectory(pathStr, extensionsStr, myClient, ExceptionCheck, env); //gc env->ReleaseStringUTFChars(path, pathStr); env->ReleaseStringUTFChars(extensions, extensionsStr); }
4.2.6 扫描函数scanFile
在此讲解Java层的函数scanFile,功能是调用函数doScanFile对指定的文件进行扫描,具体实现代码如下所示:
public void scanFile(String path, long lastModified, long fileSize, boolean isDirectory, boolean noMedia) { //这是来自本地代码的回调函数 //Log.v(TAG, "scanFile: "+path); doScanFile(path, null, lastModified, fileSize, isDirectory, false, noMedia); }
4.2.7 JNl中的异常处理
为了处理Java实现的方法中或者C/C++实现的方法中抛出的Java异常,JNI也提供了一套异常处理机制函数集,专门用于检查、分析和处理异常情况。例如在文件jni.h中,定义了主要的异常函数,具体代码如下所示:
//抛出异常 jint (*Throw)(JNIEnv*, jthrowable); //抛出新的异常 jint (*ThrowNew)(JNIEnv *, jclass, const char *); //异常产生 jthrowable (*ExceptionOccurred)(JNIEnv*); void (*ExceptionDescribe)(JNIEnv*); //清除异常 void (*ExceptionClear)(JNIEnv*); void (*FatalError)(JNIEnv*, const char*);
例如在Camera模块中也用到了异常处理,在文件android_hardware_Camera.cpp中,也涉及到了异常操作,具体代码例如下所示:
void JNICameraContext::copyAndPost(JNIEnv* env, const sp<IMemory>& dataPtr, int msgType) { …… if (obj == NULL) { LOGE("Couldn't allocate byte array for JPEG data"); env->ExceptionClear(); } else { env->SetByteArrayRegion(obj, 0, size, data); } } else { LOGE("image heap is NULL"); } } …… }
在文件android_hardware_Camera.cpp中,函数android_hardware_Camera_startPreview()也同样用到了异常处理机制,具体代码如下所示:
static void android_hardware_Camera_startPreview(JNIEnv *env, jobject thiz) { LOGV("startPreview"); sp<Camera> camera = get_native_camera(env, thiz, NULL); if (camera == 0) return; if (camera->startPreview() ! = NO_ERROR) { jniThrowRuntimeException(env, "startPreview failed"); return; } }
在上述代码中,android_hardware_Camera_startPreview()如果发现startPreview()函数返回错误,则会抛出异常并返回。这里的异常与Java中的异常机制很相似,读者可以对比分析它们的原理。