热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

多媒体文件管理,资源的扫描MediaScanner(二)

多媒体文件的扫描MediaScanner主要由两部分组成,一是MediaScannerReceiver,一是MediaScannerService&#

多媒体文件的扫描MediaScanner

主要由两部分组成,一是MediaScannerReceiver,一是MediaScannerService,扫描的执行由广播触发。MediaScannerReceiver接收4中类型的广播:

AndroidManifest.xml


看下执行扫描的具体代码:

Packages/providers/mediaprovider/…/MediaScannerReceiver.java
public class MediaScannerReceiver extends BroadcastReceiver {public void onReceive(Context context, Intent intent) {final String action = intent.getAction();final Uri uri = intent.getData();if (Intent.ACTION_BOOT_COMPLETED.equals(action)) {
//收到开机广播,同时扫描内外存储设备。scan(context, MediaProvider.INTERNAL_VOLUME);scan(context, MediaProvider.EXTERNAL_VOLUME);
}else{if (uri.getScheme().equals("file")) {
//获取外部存储设备的路径。String path = uri.getPath();String externalStoragePath =
Environment.getExternalStorageDirectory().getPath();String legacyPath = Environment.getLegacyExternalStorageDirectory().getPath();path = new File(path).getCanonicalPath();if (path.startsWith(legacyPath)) {path = externalStoragePath + path.substring(legacyPath.length());}
}
//外部存储设备挂载的广播,if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {scan(context, MediaProvider.EXTERNAL_VOLUME);
}else if(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE.equals(action) &&
path != null && path.startsWith(externalStoragePath + "/")) {
//扫描特定的文件路径。scanFile(context, path);
}
}
}


不管是调用scan还是scanfile方法,都是调用MediaScannerService来接着处理。

private void scan(Context context, String volume) {Bundle args = new Bundle();args.putString("volume", volume);context.startService(new Intent(context, MediaScannerService.class).putExtras(args));} private void scanFile(Context context, String path) {Bundle args = new Bundle();args.putString("filepath", path);context.startService(new Intent(context, MediaScannerService.class).putExtras(args));
}


接着看MediaScannerService的代码:

MediaScannerService.java

public class MediaScannerService extends Service implements Runnable {
//首先创建一个唤醒锁,级别是PARTIAL_WAKE_LOCK,确保cpu一直在运行,屏幕和键盘背光都可以被关闭,在扫描开始会通过mWakeLock.acquire();获取唤醒锁,这样扫描的过程中不会sleep,如果用户按了power键,屏幕会关掉,但是cpu会保持运行,知道调用mWakeLock.release();PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE);mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
//这里新启了一个线程,因为service默认是运行在主线程的,所以要在单独的线程处理扫描,不然会block主线程。Thread thr = new Thread(null, this, "MediaScannerService");thr.start();
}


顺带看下他的run()方法,这里展示启动一个独立线程的示例:

public void run() {Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND +Process.THREAD_PRIORITY_LESS_FAVORABLE);
//prepare()这个方法是一定要调用的,因为这个方法会去创建属于这个新线程的Looper,同时创建属于这个线程的消息队列,也即是Looper实例中的MessageQueue变量。Looper.prepare();
//获取属于这个新线程的Looper,也即是上一步prepare中创建的。mServiceLooper = Looper.myLooper();mServiceHandler = new ServiceHandler();
//调用loop()方法,让线程循环起来。Looper.loop();
}

我们知道service的onCreate方法之后,会调用其onStartCommand,在onStartCommand方法中会通过mServiceHandler发送消息,同时携带Intent中的扫描信息,直接看其handleMessage方法实现:

private final class ServiceHandler extends Handler {public void handleMessage(Message msg) {Bundle arguments = (Bundle) msg.obj;String filePath = arguments.getString("filepath");if (filePath != null) {
//这里处理扫描指定文件路径的请求,ACTION_MEDIA_SCANNER_SCAN_FILE。IBinder binder = arguments.getIBinder("listener");IMediaScannerListener listener =(binder == null ? null : IMediaScannerListener.Stub.asInterface(binder));uri = scanFile(filePath, arguments.getString("mimetype"));listener.scanCompleted(filePath, uri);
}else {
//下面是扫描存储设备,String volume = arguments.getString("volume");String[] directories = null;if (MediaProvider.INTERNAL_VOLUME.equals(volume)) {
//这里是扫描内部存储设备,只考虑了media目录,directories = new String[] {Environment.getRootDirectory() + "/media",Environment.getOemDirectory() + "/media",};} else if (MediaProvider.EXTERNAL_VOLUME.equals(volume)) {
//这里扫描外部存储设备,考虑了是不是多用户。if (getSystemService(UserManager.class).isDemoUser()) {directories = ArrayUtils.appendElement(String.class,mExternalStoragePaths,Environment.getDataPreloadsMediaDirectory().getAbsolutePath());
}else{directories = mExternalStoragePaths;
}
scan(directories, volume);
}
}
}
}


接着看下他的scan()函数:

private void scan(String[] directories, String volumeName) {if (volumeName.equals(MediaProvider.EXTERNAL_VOLUME)) {openDatabase(volumeName);}
//通过MediaScanner对象,进一步执行扫描操作,携带的参数是要扫描的目录。try (MediaScanner scanner = new MediaScanner(this, volumeName)) {scanner.scanDirectories(directories);
}
}


转到frameworks/base/media/java/…/MediaScanner.java。

private final MyMediaScannerClient mClient= new MyMediaScannerClient();

mClient对象,会作为processDirectory的参数传到native层,后续会在native层调用java层方法扫描单个文件目录,并在扫描结束时做出入数据库的处理。也就是说目录数组的扫描工作,最终还是拆分为单个文件目录,做扫描,插入数据库。

public void scanDirectories(String[] directories) @ MediaScanner.java {
// mMediaInserter对象处理数据批量插入数据库的操作,mMediaInserter = new MediaInserter(mMediaProvider, 500);for (int i = 0; i //这是个jni方法,循环处理directories的目录。这里的mClient是MyMediaScannerClient类型的对象,它是MediaScanner.java的内部类。processDirectory(directories[i], mClient);
}
//刷新剩余的插入记录。
mMediaInserter.flushAll();
mMediaInserter = null;
}

还要继续看扫描的处理,转到jni层,

android_media_MediaScanner_processDirectory( )
@android_media_MediaScanner.cpp{MediaScanner *mp = getNativeScanner_l(env, thiz);MyMediaScannerClient myClient(env, client);MediaScanResult result = mp->processDirectory(pathStr, myClient);
}

上面的函数中要搞清楚两个问题,mp具体是什么对象?myClient怎么调用的java层的client实例的方法。
先看mp具体是什么对象?


//这个native_init方法,是有MediaScanner.java的static block调用的。

static void android_media_MediaScanner_native_init(JNIEnv *env){//显示找到java层"android/media/MediaScanner";这个类。jclass clazz = env->FindClass(kClassMediaScanner);//然后取出java层的变量mNativeContext,转成native层的变量fields.context,用来存取值使用。fields.context = env->GetFieldID(clazz,
"mNativeContext", "J");}


//这个native_setup是在MediaScanner.java的构造函数中调用的。

static void android_media_MediaScanner_native_setup(JNIEnv *env, jobject thiz){
//把StagefrightMediaScanner实例存入到了fields.context中。MediaScanner *mp = new StagefrightMediaScanner;env->SetLongField(thiz, fields.context, (jlong)mp);
}


所以上面的processDirectory(…)方法就转到了StagefrightMediaScanner.cpp中,实质上调用的是其父类MediaScanner.cpp中的processDirectory方法。

MediaScanner.cpp

MediaScanResult MediaScanner::doProcessDirectory(char *path, int pathRemaining, MediaScannerClient &client, bool noMedia) {
//先做一些处理,是不是要跳过某个目录,找出所有非多媒体.nomedia,打开目录,跳过一些不存在的目录,调用doProcessDirectoryEntry。DIR* dir = opendir(path);while ((entry = readdir(dir))) {if (doProcessDirectoryEntry(path, pathRemaining, client, noMedia, entry, fileSpot)== MEDIA_SCAN_RESULT_ERROR){result = MEDIA_SCAN_RESULT_ERROR;break;
}
}
closedir(dir);
}
MediaScanResult MediaScanner::doProcessDirectoryEntry(char *path, int pathRemaining, MediaScannerClient &client, bool noMedia,struct dirent* entry, char* fileSpot) {const char* name = entry->d_name;
//忽略“.”和“..”if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {…}
//获取文件类型。int type = entry->d_type;
//接下来如果是目录,继续调用doProcessDirectory,类似一个递归。如果是文件,调用client.scanFile方法。if (type == DT_DIR) {MediaScanResult result = doProcessDirectory(path, pathRemaining - nameLength - 1,client, childNoMedia);
} else if (type == DT_REG) {status_t status = client.scanFile(path, statbuf.st_mtime, statbuf.st_size,false /*isDirectory*/, noMedia);
}
}

还记得上面提到的client,是android_media_MediaScanner_processDirector()@android_media_MediaScanner.cpp中传过来的,实质是MyMediaScannerClient类型的对象。MyMediaScannerClient是android_media_MediaScanner.cp的子类,继承自MediaScannerClient.cpp

接着看下他的scanFile()方法:

class MyMediaScannerClient : public MediaScannerClient
virtual status_t scanFile(const char* path, long long lastModified,long long fileSize, bool isDirectory, bool noMedia){
//这个函数是调用java层的类MyMediaScannerClient(MediaScanner.java的内部类)中的方法scanFile方法。mEnv->CallVoidMethod(mClient, mScanFileMethodID, pathStr, lastModified,fileSize, isDirectory, noMedia);
}


以上面的代码为例,插播native层如何调用java层代码的:

mEnv指针变量的类型是JNIEnv,而JNIEnv又是JNINativeInterface结构类型的,代表了jni的本地接口,里面包含了很多的函数。

jobject类型,代表了调用native方法的,那个java类实例,当从java层调用native方法时,前两个参数(JNIEnv *env, jobject thiz)都是自动带入的,后面的才是函数的真正入参。

解释下mEnv->CallVoidMethod()中参数:

mClient是jobject类型的对象,代表了java层的类MyMediaScannerClient的类实例,具体是在MyMediaScannerClient(cpp)的构造函数中的初始化列表中赋值的:mClient(env->NewGlobalRef(client))。

mScanFileMethodID是jmethodID类型的变量,它的赋值语句:

mScanFileMethodID = env->GetMethodID(

mediaScannerClientInterface,"scanFile","(Ljava/lang/String;JJZZ)V");

在这条赋值语句中mediaScannerClientInterface是jclass 类型的java层类(MediaScannerClient.java)实例(注意不是对象实例):

static const char* constkClassMediaScannerClient ="android/media/MediaScannerClient";

jclass mediaScannerClientInterface = env->FindClass(kClassMediaScannerClient);

"scanFile"是java层类MediaScannerClient.java中方法名。

"(Ljava/lang/String;JJZZ)V"是scanFile这个方法的类型签名,括号中是参数,最后是返回值。

接着说mEnv->CallVoidMethod()这个函数中的参数,除了前两个mClient, mScanFileMethodID,后面的值是实际传给调用函数的参数值,是真正的入参。



JNI对所有的数据类型做了类型定义:

JNI基础类型对照表

Java类型

本地类型

占位说明

boolean

jboolean

Unsigned 8 bits

byte

jbyte

Signed 8 bits

char

jchar

Unsigned 16 bits

short

jshort

Signed 16 bits

int

jint

Signed 32 bits

long

jlong

Signed 64 bits

float

jfloat

32bits

double

jdouble

64bits

void

void

void

JNI引用数据类型对照表

Java类型

本地类型

Java类型

本地类型

All objects

jobject

Char[]

jcharArray

Java.lang.Class  instances

jclass

Short[]

jshortArray

Java.lang.String  instances

jstring

Int[]

jintArray

arrays

jarray

Long[]

jlongArray

Object[]

jobjectArray

Float[]

jfloatArray

Boolean []

JbooleanArray

Double[]

jdoubleArray

Byte[]

jbyteArray

Java.lang.Throwable  objects    

jthrowable

JNI中类型签名,注意boolean,long

Java type

Type Sinature

Java type

Type signature

boolean

Z

float

F

byte

B

Double

D

char

C

Full-qualified-class

(如:java/lang/String)

L full-qualified-class(全限定类

如:Ljava/lang/String)

short

S

Type[] (数组,如:int[])

[type   (如:[I)

int

I

Method type (方法类型)

(arg-types)ret-type

(参数类型)返回值类型

long

J

 

 






接着前面扫描流程分析:

scanFile又直接调用了doScanFile方法:

MediaScanner.java

public Uri doScanFile(String path, String mimeType, long lastModified, long fileSize,boolean isDirectory, boolean scanAlways, boolean noMedia)@ MyMediaScannerClient {
//获取文件类型,生成FileEntry对象。FileEntry entry = beginFile(path, mimeType, lastModified,fileSize, isDirectory, noMedia);
//接下来区分多媒体文件,非多媒体文件。if (noMedia) {
//非多媒体文件,直接调用endFile。result = endFile(entry, false, false, false, false, false);
}else{
//根据beginFile中获取的文件类型mFileType,判断是音频,视频,图片。boolean isaudio = MediaFile.isAudioFileType(mFileType);boolean isvideo = MediaFile.isVideoFileType(mFileType);boolean isimage = MediaFile.isImageFileType(mFileType);
//做路径转化,这里的模拟外部存储设备,应该是指手机内存中除PRIVATE外的部分?if (isaudio || isvideo || isimage) {path = Environment.maybeTranslateEmulatedPathToInternal(new File(path)).getAbsolutePath();
}
//如果是音视频文件,只提取元数据,ID3信息,比如:宽高,日期,专辑名,艺术家等。
if (isaudio || isvideo) {processFile(path, mimeType, this);
}
//如果是图片,执行解码。
if (isimage) {processImageFile(path);
}
// endFile,做的一个主要工作就是往数据库插入数据。
result = endFile(entry, ringtones, notifications, alarms, music, podcasts);
}
}

private Uri endFile(FileEntry entry, boolean ringtones, boolean notifications,boolean alarms, boolean music, boolean podcasts){
//生成一个存入数据库的数据对象ContentValues values = toValues();
//对jpg的对象,处理下其ExifInterface信息,主要角度调整。if((mFileType == MediaFile.FILE_TYPE_JPEG|| MediaFile.isRawImageFileType(mFileType)) && !mNoMedia){ExifInterface exif = new ExifInterface(entry.mPath);int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1);if (orientation != -1) {int degree;switch(orientation) {case ExifInterface.ORIENTATION_ROTATE_90:degree = 90;
}
values.put(Images.Media.ORIENTATION, degree);
}
}
//处理数据插入的对象,
MediaInserter inserter = mMediaInserter;
//根据不同的类型,设置相应的uri,这些uri来自MediaStore.java。
if (MediaFile.isVideoFileType(mFileType)) {tableUri = mVideoUri;
} else if (MediaFile.isImageFileType(mFileType)) {tableUri = mImagesUri;
}
//有了uri,有了要插入的数据values,调用MediaInserter插入数据库。一个新的文件插入时,要先插入其目录,然后在插入文件,记得调用flushAll,或者flush清空缓冲区。
inserter.insert(tableUri, values);
}





推荐阅读
  • 本文介绍了如何使用C#制作Java+Mysql+Tomcat环境安装程序,实现一键式安装。通过将JDK、Mysql、Tomcat三者制作成一个安装包,解决了客户在安装软件时的复杂配置和繁琐问题,便于管理软件版本和系统集成。具体步骤包括配置JDK环境变量和安装Mysql服务,其中使用了MySQL Server 5.5社区版和my.ini文件。安装方法为通过命令行将目录转到mysql的bin目录下,执行mysqld --install MySQL5命令。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • PHP中的单例模式与静态变量的区别及使用方法
    本文介绍了PHP中的单例模式与静态变量的区别及使用方法。在PHP中,静态变量的存活周期仅仅是每次PHP的会话周期,与Java、C++不同。静态变量在PHP中的作用域仅限于当前文件内,在函数或类中可以传递变量。本文还通过示例代码解释了静态变量在函数和类中的使用方法,并说明了静态变量的生命周期与结构体的生命周期相关联。同时,本文还介绍了静态变量在类中的使用方法,并通过示例代码展示了如何在类中使用静态变量。 ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
author-avatar
挖墙找红杏000
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有