首先这个需求的来源是在一个特殊的环境的下单机使用设备,不可连接网络,蓝牙,nfc等通信的情况下需要在两台设备间共享文件,其实这个系统本身就有这个功能,我们想让用户更加简介,一键操作,不需要进行更多的选择,就打算把这个功能提取出来。
首先看看系统的功能
通过otg传输文件的模式可以获取到另一台设备的目录,并且可以进行操作。基于这一功能开始研究,首先通过日志打印发现系统在插入usb设备时候
I/ActivityManager: START u0 {act=android.hardware.usb.action.USB_DEVICE_ATTACHED flg=0x11000000 cmp=com.android.mtp/.ReceiverActivity (has extras)} from uid 1000
I/ActivityManager: START u0 {act=android.intent.action.VIEW cat=[android.intent.category.DEFAULT] dat=content://com.android.mtp.documents/root/44 typ=vnd.android.document/root cmp=com.android.documentsui/.files.FilesActivity} from uid 10017
去源码里面搜了一下这个类,这是在MtpDocumentsProvider\MtpDocumentsProvider\src\com\android\mtp\ReceiverActivity.java
看一下源码,这里很简单
public class ReceiverActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(getIntent().getAction())) {
final UsbDevice device = getIntent().getParcelableExtra(UsbManager.EXTRA_DEVICE);
try {
final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance();
provider.openDevice(device.getDeviceId());
final String deviceRootId = provider.getDeviceDocumentId(device.getDeviceId());
final Uri uri = DocumentsContract.buildRootUri(
MtpDocumentsProvider.AUTHORITY, deviceRootId);
final Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, DocumentsContract.Root.MIME_TYPE_ITEM);
intent.addCategory(Intent.CATEGORY_DEFAULT);
this.startActivity(intent);
} catch (IOException exception) {
Log.e(MtpDocumentsProvider.TAG, "Failed to open device", exception);
}
}
finish();
}
}
就是监听usb的广播后直接打开了一个activity,这个Activity正好就是FilesActivity,这个MtpDocumentsProvider是系统mtp对外提供的一个provider.
FilesActivity这个类是在系统的DocumentUi下面的一个类,通过查看代码发现是通过contentprovider来获取的数据,
这时候在去GitHub上浏览了一番发现了一个AndroidOtgUSBMtpSample,参考发现这个注意是对U盘这里的有作用,另外还有一个项目libaums可直接读取U盘进行操作,在两份开源项目的参考下自己再进行一番打磨成功实现改效果
首先通过一个广播接收器接收到
IntentFilter intentFilter = new IntentFilter();
//注册监听自定义广播
intentFilter.addAction(READ_USB_DEVICE_PERMISSION);
//设备插入
intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
//设备拔出
intentFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
registerReceiver(usbmtpReceiver, intentFilter);
监听广播
override fun onReceive(context: Context, intent: Intent) {
when (intent.action) {
//Usb设备已连接
UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
mUSBDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
if (mUSBDevice != null) {
checkConnectedDevice()
}
}
//设备已断开
UsbManager.ACTION_USB_DEVICE_DETACHED -> {
Log.e(TAG, "设备已断开")
}
//USB权限检测成功
USBReceiverConstant.READ_USB_DEVICE_PERMISSION -> {
mUSBDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
//检查连接的设备
checkConnectedDevice()
}
}
}
@RequiresApi(api = Build.VERSION_CODES.N)
private fun checkConnectedDevice() {
SmartLogUtils.showDebug("开始检查USB连接设备", true)
//获取Android 设备管理器
val usbManager =
MyApplication.getContext().getSystemService(Context.USB_SERVICE) as UsbManager
//获取所有已连接上的USB列表
val allConnectedUSBDeviceList = usbManager.deviceList
if (allConnectedUSBDeviceList.size <= 0) {
Log.e(TAG, "未检测到任何USB连接设备")
return
} else {
Log.e(TAG, "当前连接设备个数:" + allConnectedUSBDeviceList.size)
}
for (hasConnectedDevice in allConnectedUSBDeviceList.values) {
mUSBDevice = hasConnectedDevice
//遍历连接的设备接口
for (i in 0 until mUSBDevice!!.interfaceCount) {
//获取连接设备接口
val usbInterface = mUSBDevice!!.getInterface(i)
//检查设备连接接口 USB class for mass storage devices.
if (usbInterface.interfaceClass == UsbConstants.USB_CLASS_VENDOR_SPEC) {
if (usbManager.hasPermission(mUSBDevice)) {
Log.e(TAG,"设备已获取权限")
if (mUSBDevice == null) {
Log.e(TAG,"设备为空")
return
}
//打开USB设备连接通道
val usbDeviceConnection = usbManager.openDevice(mUSBDevice)
//获取MTP设备
val mtpDevice = MtpDevice(mUSBDevice)
if (!mtpDevice.open(usbDeviceConnection)) {
Log.e(TAG, "设备打开失败")
return
}
val storageIds = mtpDevice.storageIds
for (storageId in storageIds) {
//读取MTP 设备上的数据
readAllFileFromMTPDevice(mtpDevice, storageId)
}
usbDeviceConnection?.close()
mtpDevice.close()
} else {
Log.e(TAG,"数码相机未获取权限")
该代码执行后,系统弹出一个对话框
val pendingIntent = PendingIntent.getBroadcast(MyApplication.getContext(),
0,
Intent(USBReceiverConstant.READ_USB_DEVICE_PERMISSION),
0)
usbManager.requestPermission(mUSBDevice, pendingIntent)
}
} //end switch
}
}
}
private fun readAllFileFromMTPDevice(mtpDevice: MtpDevice?, storageId: Int) {
Log.e(TAG,"readAllFileFromMTPDevice start")
Log.e(TAG,"storageId=$storageId")
//返回给定存储单元上所有对象的对象句柄列表,具有给定格式和父级。
val objectHandles = mtpDevice!!.getObjectHandles(storageId, 0, -1)
for (itemObjectHandles in objectHandles) {
//每一个mtpObjectInfo 是一个图片对象 检索MtpObjectInfo对象
val mtpObjectInfo = mtpDevice.getObjectInfo(itemObjectHandles)
Log.e(TAG, "mHandle:${mtpObjectInfo.objectHandle}," +
"storageId:${mtpObjectInfo.storageId}," +
"mFormat:${mtpObjectInfo.format}," +
"mCompressedSize:${mtpObjectInfo.compressedSize}," +
"mParent:${mtpObjectInfo.parent}," +
"mAssociationType:${mtpObjectInfo.associationType}," +
"mAssociationDesc:${mtpObjectInfo.associationDesc}," +
"mName:${mtpObjectInfo.name}")
}
Log.e(TAG,"readAllFileFromMTPDevice end")
}
1.首先通过UsbManager获取所以已连接的设备,这里我们可以将所以设备信息显示到界面上去选择,我目前只做了一个demo所以就直接打印获取内容了(我测试只连接了一台设备,其实可以连接多个设备),
2.再获取设备中的所以的接口(比如有的手机有内部存储和存储卡之类的),遍历获取每个接口的读写权限,
usbInterface.interfaceClass == UsbConstants.USB_CLASS_VENDOR_SPEC
这个是设备间识别到的接口类型,如果是U盘或者其他设备可能获取到的接口类型不同
/**
* USB class indicating that the class is determined on a per-interface basis.
*/
public static final int USB_CLASS_PER_INTERFACE = 0;
/**
* USB class for audio devices.
*/
public static final int USB_CLASS_AUDIO = 1;
/**
* USB class for communication devices.
*/
public static final int USB_CLASS_COMM = 2;
/**
* USB class for human interface devices (for example, mice and keyboards).
*/
public static final int USB_CLASS_HID = 3;
/**
* USB class for physical devices.
*/
public static final int USB_CLASS_PHYSICA = 5;
/**
* USB class for still image devices (digital cameras).
*/
public static final int USB_CLASS_STILL_IMAGE = 6;
/**
* USB class for printers.
*/
public static final int USB_CLASS_PRINTER = 7;
/**
* USB class for mass storage devices.
*/
public static final int USB_CLASS_MASS_STORAGE = 8;
/**
* USB class for USB hubs.
*/
public static final int USB_CLASS_HUB = 9;
/**
* USB class for CDC devices (communications device class).
*/
public static final int USB_CLASS_CDC_DATA = 0x0a;
/**
* USB class for content smart card devices.
*/
public static final int USB_CLASS_CSCID = 0x0b;
/**
* USB class for content security devices.
*/
public static final int USB_CLASS_CONTENT_SEC = 0x0d;
/**
* USB class for video devices.
*/
public static final int USB_CLASS_VIDEO = 0x0e;
/**
* USB class for wireless controller devices.
*/
public static final int USB_CLASS_WIRELESS_CONTROLLER = 0xe0;
/**
* USB class for wireless miscellaneous devices.
*/
public static final int USB_CLASS_MISC = 0xef;
/**
* Application specific USB class.
*/
public static final int USB_CLASS_APP_SPEC = 0xfe;
/**
* Vendor specific USB class.
*/
public static final int USB_CLASS_VENDOR_SPEC = 0xff;
3.通过UsbManager打开设备
val usbDeviceConnection = usbManager.openDevice(mUSBDevice)
再创建通过mUsbDevice创建MtpDevice对象,使用mtpDevice打开usb设备
val mtpDevice = MtpDevice(mUSBDevice) if (!mtpDevice.open(usbDeviceConnection)) { Log.e(TAG, "设备打开失败") return }
getObjectHandlers获取改接口下的文件,第二参数类型,0代表所有,还可以根据需要设置,最后一个参数为parentId,-1代表根目录下的搜索,0代表全部,具体的还可以根据自己获取的父目录id来获取
val objectHandles = mtpDevice!!.getObjectHandles(storageId, 0, -1)
这里的数据来源注意来自Mtp数据库