转载请注明文章出错及作者
作者:Xandy
出处:http://blog.csdn.net/xl19862005
一、mtp概述
android在3.0以后的版本加入了mtp的支持,相对于mass storage模式,由于mtp优越性,现在几乎所有的手机连接PC后都是以mtp的方式进行文件访问。
这里简单讲述一下mtp的优点:
1、Initiator和Responder可同时对文件进行存储。相对于mass storage的模式,这种优点是显而易见的。
PC连接上responder设备之后,不是直接对设备中的存储分区进行访问,而是通过vfs的方式间接访问存储分区中的文件,这个作为Initiator端的PC设备来说,就不再需要关心要访问的responder存储分区是什么文件系统了,通过公用的vfs就可以对不同文件系统的存储设备进行读写了。
2、mtp模式下Initiator可以知道Responder所支持的媒体文件格式有哪些
3、文件访问权限可控。这点是笔者根据android下mtp的架构自加的,也正是本文所需要说的重点。
二、android mtp启动流程
这里要提到一点的是:android设备启动之后,当在MediaScannerReceiver(android_src/providers/MediaProvider/src/com/android/providers/media/MediaScannerReceiver.java)中监听到开机完成广播(android.intent.action.BOOT_COMPLETED)时,会启动MeidaScannerService,对整个设备内部的存储设备进行扫描,并将扫描到的文件存入数据库!
这里要提一个原生android系统的bug:在开机完成之后,在android设备上拍照或者截图后,将设备连接上PC,是无法找到刚拍的照片或截图图片的!这是因为MediaScannerService的启动只在BOOT_COMPLETED时scan一次,此后新增加的文件都还没有更新到数据库,需要重启系统后在PC上才能发现新增加的文件。
为了解决此bug,我在MediaScannerReceiver中增加了对USB_STATE状态广播的监听,所以每次插拔USB时都会scan一次。
而从上图可知,MtpService启动后也是需要去数据库(MtpDatabase)里拿文件的,所以可以通过修改数据库的查找规则来达到连接PC后只显示指定文件/文件夹的功能。
三、文件过滤代码修改
首先来看看MtpDatabase(android_src/frameworks/base/media/java/android/mtp/MtpDatabase.java)里关于创建数据库查询的方法:
private Cursor createObjectQuery(int storageID, int format, int parent) throws RemoteException {
String where;
String[] whereArgs;
if (storageID == 0xFFFFFFFF) {
// query all stores
if (format == 0) {
// query all formats
if (parent == 0) {
// query all objects
where = null;
whereArgs = null;
} else {
if (parent == 0xFFFFFFFF) {
// all objects in root of store
parent = 0;
}
where = PARENT_WHERE;
whereArgs = new String[] { Integer.toString(parent) };
}
} else {
// query specific format
if (parent == 0) {
// query all objects
where = FORMAT_WHERE;
whereArgs = new String[] { Integer.toString(format) };
} else {
if (parent == 0xFFFFFFFF) {
// all objects in root of store
parent = 0;
}
where = FORMAT_PARENT_WHERE;
whereArgs = new String[] { Integer.toString(format),
Integer.toString(parent) };
}
}
} else {
// query specific store
if (format == 0) {
// query all formats
if (parent == 0) {
// query all objects
where = STORAGE_WHERE;
whereArgs = new String[] { Integer.toString(storageID) };
} else {
if (parent == 0xFFFFFFFF) {
// all objects in root of store
parent = 0;
where = STORAGE_PARENT_WHERE;
whereArgs = new String[] { Integer.toString(storageID),
Integer.toString(parent) };
}
} else {
// query specific format
if (parent == 0) {
// query all objects
where = STORAGE_FORMAT_WHERE;
whereArgs = new String[] { Integer.toString(storageID),
Integer.toString(format) };
} else {
if (parent == 0xFFFFFFFF) {
// all objects in root of store
parent = 0;
}
where = STORAGE_FORMAT_PARENT_WHERE;
whereArgs = new String[] { Integer.toString(storageID),
Integer.toString(format),
Integer.toString(parent) };
}
}
}
// if we are restricting queries to mSubDirectories, we need to add the restriction
// onto our "where" arguments
if (mSubDirectoriesWhere != null) {
if (where == null) {
where = mSubDirectoriesWhere;
whereArgs = mSubDirectoriesWhereArgs;
} else {
where = where + " AND " + mSubDirectoriesWhere;
// create new array to hold whereArgs and mSubDirectoriesWhereArgs
String[] newWhereArgs =
new String[whereArgs.length + mSubDirectoriesWhereArgs.length];
int i, j;
for (i = 0; i < whereArgs.length; i++) {
newWhereArgs[i] = whereArgs[i];
}
for (j = 0; j < mSubDirectoriesWhereArgs.length; i++, j++) {
newWhereArgs[i] = mSubDirectoriesWhereArgs[j];
}
whereArgs = newWhereArgs;
}
}
return mMediaProvider.query(mPackageName, mObjectsUri, ID_PROJECTION, where,
whereArgs, null, null);
}
而方法query的定义如下:
public Cursor query(String callingPkg, Uri url, String[] projection, String selection, String[] selectionArgs, String sortOrder, IcancellationSignal cancellationSignal)throws RemoteExceptioin;
不难理解,方法createObjectQuery中获得的where 字串是用于到数据库里查询与where匹配的文件的,这里还要说明一点的是mSubDirectoriersWhere,可以看到当这个subDirectoriersWhere不为空时,作了如下合并到where字串
where = where + " AND " + mSubDirectoriesWhere;
那么可以通过增加对这个mSubDirectoriesWhere的赋值(赋以指定需要到数据库里查询的文件夹名),就可以达到想要的文件过滤功能!
而mSubDirectoriesWhere只在MtpDatabase的构造方法里进行赋值:
public MtpDatabase(Context context, Stirng volumeName, String storagePath, String[] subDirectories){
……
if(subDirectories != null){
StringBuilder builder = new StringBuilder();
int count = subDirectories.lenght;
for(int i=0;i
那么只要在创建 MtpDatabase的时候传入这个subDirectories String数组就可以达到想要的功能了!
在MtpService(android_src/packages/providers/MediaProvider/src/com/android/providers/media/MtpService.java)中增加如下代码:
private static final String[] MTP_DIRECTORIES_PRIVATE = new String[]{
Environment.DIRECTORY_DCIM,
Environment.DIRECTORY_PICTURES,
Environment.DIRECTORY_MOVIES,
Environment.XXXXXX,
……
};
……
@Override
public int onStartCommand(Intent intent, int flags, int startId){
……
if(!mPtpMode){
int num = MTP_DIRECTORIES_PRIVATE.length;
subdirs = new String[num];
for(int j=0;j//最后在这里创建MtpDatabase,并传入subdirs到前面提到的where
mDatabase = new MtpDatabase(this, MediaProvider.EXTERNAL_VOLUME, primary.getPath(), subdirs);
……
}