当你的Android主机是处于USB主模式下,它充当USB主机,开启设备并列举出所有已连接的USB设备。这种模式在Android 3.1及以上的版本支持。

API预览

在开始开发USB Host之前,需要了解android.hardware.usb包下USB host 的API:

Class Description
UsbManager 可以列举出USB设备,并和设备交互
UsbDevice 表示已连接的USB设备,包含了访问设备标识信息的方法、接口和挂载点
UsbInterface 表示设备接口,定义了一些列的功能函数。一个设备可以包含一个或多个接口
UsbEndPoint 表示一个挂载点接口,定义了一个交互通道。一个接口可以包含一个或多个挂载点。通常都包含用于和USB设备交互输入输出的双向端点
UsbDeviceConnection 表示到USB设备的一个基于端点通信的接口,这个类支持同步和异步数据的来回通讯
UsbRequest 表示一个异步的,基于UsbDeviceConnection通讯的异步请求
UsbConstants 定义了一些USB常量,与linux内核中的 Linux/usb/ch9.h中定义的一致

在USB设备交互的情况下,你需要用到所有的这些类(除了交互的时候,需要用到UsbRequest)。首先需要获取UsbManager对象来查询目标设备UsbDevice。当获得设备UsbDevice之后,你需要找到适当的UsbInterface以及该接口对应的UsbEndPoint,从而进行交互。一但拿到正确的端点,打开连接UsbDeviceConnection,以此和Usb设备进行交互。

Manifest文件的必要节点

下面的列表,列出了在使用Usb host的API之前,你所需要添加到Manifest文件中的东西:

由于不能确保所有的Android设备都支持USB host 的API , 我们可以在Manifest文件中加入 节点,来声明我们的应用需要android.hardware.usb.host 支持.

设置应用程序的最低支持的SDK版本为12或者更高. USB 主机模式的 API 在12以前的版本上不适用.

如果希望你的应用能够接收到USB设备挂在的通知,则需要在你的主Activity的android.hardware.usb.action.USB_DEVICE_ATTACHED过滤器中指定 和 节点对. 节点对应着一个外部XML 资源文件,这个资源文件定义了你想检测的设备的一些唯一标识信息.

在XML资源文件中,通过声明节点 来过滤你想要过滤的设备. 以下列表列出了 的 属性. 总的来说, 使用 vendor-id 和 product-id 来过滤指定的USB设备,使用 class, subclass 和 protocol 来过滤一组设备, 比如说大容量存储设备和数码相机. 你可以指定所有的属性或者一个都不指定.如果你什么属性都不设置,就将匹配所有的USB设备,当程序需要的时候,你可以这么指定:

vendor-id
product-id
class
subclass
protocol (device or interface)

该资源文件保存在 res/xml/ 目录下. 它的文件名(不包含.xml后缀名) 必须和 节点指定的名字一样. 文件的格式样式如下example:
Manifest文件 和资源文件
下面是一个简单的manifest文件及其对应的资源文件:

<manifest ...><uses-feature android:name="android.hardware.usb.host" /> <uses-sdk android:minSdkVersion="12" />...<application><activity ...>...<intent-filter><action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /></intent-filter><meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" /></activity></application>
</manifest>

在上面的manifest文件下,对应的资源文件必须保存为 res/xml/device_filter.xml,并且使用特定的属性来过滤任何USB设备:

<?xml version="1.0" encoding="utf-8"?>
<resources><usb-device product-id="4100" vendor-id="1204" /><usb-device product-id="4356" vendor-id="1204" /><usb-device product-id="22288" vendor-id="1155" /><usb-device product-id="0xAA97" vendor-id="0xAAAA" />
</resources>

代码实例

public class UsbHostTool {private final int USB_CONNECTED = 0;private final int USB_DISCONNECT = 1;private Context mContext = null;private static final String WK_ACTION_USB_PERMISSION = "com.android.example.USB_PERMISSION";private UsbDevice mUsbDevice = null;private UsbDeviceConnection mDeviceConnection;private UsbEndpoint endPointForSendData;private UsbEndpoint endPointForUsb;//private UsbEndpoint endPointForHdmi;private UsbEndpoint endPointForGetData;//private UsbEndpoint endPointForMData;private int usbState = USB_DISCONNECT;private FiFoUsbStream fiFoUsbStream = null;private ExecutorService executorService = null;private ScheduledThreadPoolExecutor mScheduledThreadPoolExecutor = null;private IUsbHostDataListner iUsbHostDataListner;private int interfaceCount = 0;private UsbInterface[] mInterfaceArray = new UsbInterface[5];private byte[] revUsbData = new byte[8 * 1024];private final int TIMEOUT = 200; //300public UsbHostTool(Context context, IUsbHostDataListner iUsbHostDataListner) {this.mContext = context;this.iUsbHostDataListner = iUsbHostDataListner;executorService = Executors.newScheduledThreadPool(5);mScheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(5);usbDevicesInit(context);}/*** onCreate/onDestroy;onResume/onPause组合中注册和解绑*/public void registerMyReceivers(Context mContext) {IntentFilter filter = new IntentFilter();filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);filter.addAction(WK_ACTION_USB_PERMISSION);mContext.registerReceiver(mUsbReceiver, filter);}/*** onCreate/onDestroy;onResume/onPause组合中注册和解绑*/public void unregisterMyReceivers(Context mContext) {mContext.unregisterReceiver(mUsbReceiver);}private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {String action = intent.getAction();WkLogTool.showLog("action=" + action);switch (action) {case WK_ACTION_USB_PERMISSION:synchronized (this) {if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {WkLogTool.showLog("授权成功 ,再次请求链接usb");findEndPoints(mUsbDevice); // 授权成功} else {WkLogTool.showLog("用户拒绝授权");}}break;case UsbManager.ACTION_USB_DEVICE_DETACHED:Observable.just(action).compose(RxjavaTool::toSimpleObservable).subscribe(value -> {showMsg(mContext, "USB断开连接");((Activity) mContext).finish();});break;case UsbManager.ACTION_USB_DEVICE_ATTACHED:Observable.just(action).compose(RxjavaTool::toSimpleObservable).subscribe(value -> {showMsg(mContext, "USB已经连接");});break;default:break;}}};private void usbDevicesInit(Context mContext) {WkLogTool.showLog("开始初始化 usb 设备----------------");if (usbState == USB_CONNECTED) {WkLogTool.showLog("usb 已经初始化了");return;}HashMap<String, UsbDevice> deviceList = getUsbManager(mContext).getDeviceList();Iterator<UsbDevice> deviceIterator = deviceList.values().iterator();while (deviceIterator.hasNext()) {UsbDevice device = deviceIterator.next();int vid = device.getVendorId();if (vid == 0x0000|| vid == 0x04b4|| vid == 0x0951|| vid == 0x483|| vid == 0xAAAA) {mUsbDevice = device;WkLogTool.showLog("发现usb设备, vid=" + vid);}}boolean hasPermission = checkUsbPermission(mContext, mUsbDevice);if (mUsbDevice == null) {WkLogTool.showLog("mUsbDevice==null    lllllllll");return;}if (hasPermission) {WkLogTool.showLog("有权限,无需授权 ,直接去打开usb 连接");findEndPoints(mUsbDevice); // 初始化进去有权限 就直接打开} else {WkLogTool.showLog("usb没有权限,开始申请权限");PendingIntent permissionIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(WK_ACTION_USB_PERMISSION), 0);getUsbManager(mContext).requestPermission(mUsbDevice, permissionIntent);}}private UsbManager getUsbManager(Context mContext) {UsbManager manager = (UsbManager) mContext.getSystemService(Context.USB_SERVICE);if (manager == null) {WkLogTool.showLogError("UsbManager 获取失败!!!");}return manager;}private boolean checkUsbPermission(Context mContext, UsbDevice mUsbDevice) {if (mUsbDevice == null) {WkLogTool.showLog("mUsbDevicew为null------------------------------");return false;}return getUsbManager(mContext).hasPermission(mUsbDevice);}private void findEndPoints(UsbDevice mUsbDevice) {WkLogTool.showLog("开始 寻找usb节点----------------");int faceCount = mUsbDevice.getInterfaceCount();interfaceCount = faceCount;int pid = mUsbDevice.getProductId();WkLogTool.showLog("pid=" + pid + "    vid=" + mUsbDevice.getVendorId() + " 设备发现接口数量=" + faceCount);if (faceCount < 1) {WkLogTool.showLog("接口数量异常!!!!--------------------- " + faceCount);return;}for (int i = 0; i < faceCount; i++) {mInterfaceArray[i] = mUsbDevice.getInterface(i);}if (mInterfaceArray[0] == null) {WkLogTool.showLogError("usb 设备接口获取失败");} else {UsbDeviceConnection connection = getUsbManager(mContext).openDevice(mUsbDevice);mDeviceConnection = connection;if (connection == null) {WkLogTool.showLogError("usb connection 获取失败");return;}for (int i = 0; i < faceCount; i++) {boolean claimInterface = connection.claimInterface(mInterfaceArray[i], true);if (claimInterface) {int endpointCount = mInterfaceArray[i].getEndpointCount();for (int index = 0; index < endpointCount; index++) {UsbEndpoint usbEndpoint = mInterfaceArray[i].getEndpoint(index);int direction = usbEndpoint.getDirection();int endPointNumber = usbEndpoint.getEndpointNumber();if (direction == UsbConstants.USB_DIR_IN) {switch (endPointNumber) {case 0x05://hdmi码流(瞄准镜)//endPointForHdmi = usbEndpoint;break;case 0x08:case 0x04:endPointForGetData = usbEndpoint;break;case 0x06://usb码流(主镜头)endPointForUsb = usbEndpoint;break;case 0x03://endPointForMData = usbEndpoint;break;default:break;}} else if (direction == UsbConstants.USB_DIR_OUT) {if (endPointNumber == 0x01) {endPointForSendData = usbEndpoint;}}}} else {WkLogTool.showLogError("usb 连接打开接口失败 。 节点=" + i);}}// end for// usb链接成功建立了onConnectUsbSuccess();}}private void onConnectUsbSuccess() {usbState = USB_CONNECTED;fiFoUsbStream = new FiFoUsbStream();//接收 usb 数据new Thread(this::captureUsbStream).start();mScheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> {if (isSerialPortDataTimeOut()) { // 暂停 主动读取飞控数据(要读取)WkLogTool.showLog("灯变红 ,暂停主动接收 飞控信息 --------");} else {initCmdToGetFcsData(); // 为了获取飞控数据}}, 500, 300, TimeUnit.MILLISECONDS);mScheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> {if (isSerialPortDataTimeOut()) { // 暂停 主动读取飞控数据(要读取)WkLogTool.showLog("灯变红 ,暂停主动接收  图传质量等数据--------");} else {toGetDeviceInfo(); // 为了 当前频段 、图传带宽、自适应码流是否开启等}}, 500, 500, TimeUnit.MILLISECONDS);mScheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> {if (isSerialPortDataTimeOut()) { // 暂停 主动读取飞控数据(要读取)WkLogTool.showLog("灯变红 ,暂停主动接收  图传质量等数据--------");} else {toGetSignalQuality();// 为了读取链路是否断开等情况 、图传状态 、信号强度 、信号质量等}}, 0, 600, TimeUnit.MILLISECONDS);mScheduledThreadPoolExecutor.scheduleWithFixedDelay(() -> {if (isSerialPortDataTimeOut()) {WkLogTool.showLog("灯变红 ,暂停主动接收  图传质量等数据--------");} else {queryAR8020Status();}}, 500, 200, TimeUnit.MILLISECONDS);// 开始接收 飞控信息startRevDataThread();// 开始寻找 usb 视频帧executorService.execute(() -> {try {WkLogTool.showLog("开始接收 寻找 usb 视频帧 " + isRunning);while (isRunning) {if (fiFoUsbStream.buffer.length > 0) {iUsbHostDataListner.toSearchUsbVideoFrame(fiFoUsbStream);}Thread.sleep(1);}} catch (Exception e) {WkLogTool.showLog("主码流发生异常=" + e.getMessage());e.printStackTrace();}});}private boolean isRunning = true;public void releaseUsbDeviceConnections() {isRunning = false;usbState = USB_DISCONNECT;WkLogTool.showLog("开始释放所有usb,status= " + usbState);for (int i = 0; i < interfaceCount; i++) {WkLogTool.showLog("释放usb接口" + i);if (mDeviceConnection != null) {mDeviceConnection.releaseInterface(mInterfaceArray[i]);}}for (int i = 0; i < interfaceCount; i++) {mInterfaceArray[i] = null;}if (mDeviceConnection != null) {mDeviceConnection.close();mDeviceConnection = null;}if (mScheduledThreadPoolExecutor != null) {mScheduledThreadPoolExecutor.shutdown();}mUsbDevice = null;executorService.shutdown();stopStatusQuery();WkLogTool.showLog("释放完成");}/**** 图传状态查询*/private void queryAR8020Status() {int len = -1;byte[] data = new byte[8];Arrays.fill(data, (byte) 0x00);data[0] = 0x01;try {if (USB_CONNECTED == usbState) {byte[] protocolPacket = AirLinkProtocol.getUsbHostCmdPackToSend(0x11, (byte) 1, (byte) 0, data, 1);len = mDeviceConnection.bulkTransfer(endPointForSendData, protocolPacket, protocolPacket.length, 1000);}} catch (Exception e) {e.printStackTrace();}}/*** 图传心跳。 飞控数据一直会到图传的。 如果不发,就不会下发到app 。*/private int initCmdToGetFcsData() {int len = -1;try {if (USB_CONNECTED == usbState) {if (mDeviceConnection != null) {byte[] protocolPacket = AirLinkProtocol.getUsbHostCmdPackToSend(UsbHostConfig.KEY_0X85, (byte) 1, (byte) 0, null, 0);len = mDeviceConnection.bulkTransfer(endPointForSendData, protocolPacket, protocolPacket.length, 1000);}}} catch (Exception e) {e.printStackTrace();}return 0;}/**** 切换 2.4G 和 5.8G*/public void switch24and58(boolean is24Way) {try {if (USB_CONNECTED == usbState) {if (mDeviceConnection != null) {int msgID = UsbHostConfig.KEY_0X22;byte sumNum = 1; //一条数据分成几个包发送了byte curNum = 0; // 当前包的顺序byte[] payload = new byte[1]; // 数据段payload[0] = is24Way ? (byte) 0x00 : (byte) 0x01;int valueLen = payload.length; // payload的长度byte[] protocolPacket = AirLinkProtocol.getUsbHostCmdPackToSend(msgID, sumNum, curNum, payload, valuelen);WkLogTool.showLog("频道切换=" + MyByteBitTools.byte2HexStr(protocolPacket));mDeviceConnection.bulkTransfer(endPointForSendData, protocolPacket, protocolPacket.length, 500);}}} catch (Exception e) {e.printStackTrace();}}/*** to get Device Info*/private void toGetDeviceInfo() {int len = -1;try {if (USB_CONNECTED == usbState) {if (mDeviceConnection != null) {int msgID = UsbHostConfig.KEY_0X19;byte sumNum = 1; //一条数据分成几个包发送了byte curNum = 0; // 当前包的顺序byte[] payload = null; // 数据段int valuelen = payload == null ? 0 : payload.length; // payload的长度byte[] protocolPacket = AirLinkProtocol.getUsbHostCmdPackToSend(msgID, sumNum, curNum, payload, valuelen);WkLogTool.showLog("为了读取0x19信息----------->" + MyByteBitTools.byte2HexStr(protocolPacket));len = mDeviceConnection.bulkTransfer(endPointForSendData, protocolPacket, protocolPacket.length, 1000);}}} catch (Exception e) {e.printStackTrace();}}/*** to get 模组地面端状态信息*/private void toGetSignalQuality() {int len = -1;try {if (USB_CONNECTED == usbState) {if (mDeviceConnection != null) {int msgID = UsbHostConfig.KEY_0X82;byte sumNum = 1; //一条数据分成几个包发送了byte curNum = 0; // 当前包的顺序byte[] payload = null; // 数据段int valuelen = payload == null ? 0 : payload.length; // payload的长度byte[] protocolPacket = AirLinkProtocol.getUsbHostCmdPackToSend(msgID, sumNum, curNum, payload, valuelen);//WkLogTool.showLog("为了读取0x82信息----------->"+MyByteBitTools.byte2HexStr(protocolPacket));len = mDeviceConnection.bulkTransfer(endPointForSendData, protocolPacket, protocolPacket.length, 1000);}}} catch (Exception e) {e.printStackTrace();}}private boolean isSerialPortDataTimeOut() {return false;}/*** 循环获取来自USB的数据*/private void captureUsbStream() {WkLogTool.showLog("开始接收 usb 数据--------" + usbState);int dataLen = 0;while (USB_CONNECTED == usbState) {try {if (isSerialPortDataTimeOut()) {  // 暂停 接收usb视频// 串口数据接收超时,板子变红了。Thread.sleep(100);WkLogTool.showLog("灯变红 ,暂停接收 usb 数据--------");} else {dataLen = mDeviceConnection.bulkTransfer(endPointForUsb, revUsbData, revUsbData.length, TIMEOUT);boolean isError = dataLen < 200;if (isError) {byte[] testData = dataLen == -1 ? null : WkByteBitTools.subBytes(revUsbData, 0, 30);WkLogTool.showLog("testData= " + MyByteBitTools.byte2HexStr(testData));}if (dataLen > 0) {WkLogTool.showLog("usb数据接收  " + "   " + revUsbData.length);if (fiFoUsbStream != null) {fiFoUsbStream.FiFoWrite(revUsbData, dataLen);}} else {WkLogTool.showLog("usb数据接收为(异常)  " + dataLen + "   " + revUsbData.length);}}} catch (Exception e) {WkLogTool.showLogError("接收USB数据异常," + e.getMessage());}}}/*** 接收信息字节*/private byte[] mReceiveBytes = new byte[512];public void startRevDataThread() {executorService.execute(() -> {try {int len = -1;WkLogTool.showLog("开始接收 飞控数据 " + isRunning);while (isRunning) {if (USB_CONNECTED == usbState && endPointForGetData != null) {if (isSerialPortDataTimeOut()) { // 暂停接收飞控数据WkLogTool.showLog("灯变红 ,暂停被动接收 飞控 数据--------");Thread.sleep(100);} else {len = mDeviceConnection.bulkTransfer(endPointForGetData, mReceiveBytes, mReceiveBytes.length, TIMEOUT);if (len >= 10) {// msgID 占用两个byte , [2] 、[3]int msgId = ((mReceiveBytes[3] & 0x0FF) << 8) + (mReceiveBytes[2] & 0x0FF);switch (msgId) {case UsbHostConfig.KEY_0X85:if (len > 10) {//  0XFF 0X5A 是固定头部if (mReceiveBytes[0] == (byte) 0xFF && mReceiveBytes[1] == (byte) 0x5A) {// lengh 占用两个byte  , [6]  、[7]int userDataLen = ((mReceiveBytes[7] & 0x0FF) << 8) + (mReceiveBytes[6] & 0x0FF);if (userDataLen > 0 && userDataLen < len) {byte[] userDataArray = new byte[userDataLen];// 头部格式 占用了10个 byte ,数据段是从第11个byte 开始读取System.arraycopy(mReceiveBytes, 10, userDataArray, 0, userDataLen);if (isRunning) {iUsbHostDataListner.onDataRev(userDataArray);}}}}break;case UsbHostConfig.KEY_0X19:if (isRunning) {processOx19(mReceiveBytes, len);}break;case UsbHostConfig.KEY_0X82:if (isRunning) {process0x82(mReceiveBytes, len);}break;default:break;}} else {WkLogTool.showLog("飞控 数据接收为(异常) len= " + len);}}} else {WkLogTool.showLog("飞控 节点异常 ,或者数据通道还未未连接  ");Thread.sleep(100);}}  // end while} catch (Exception e) {WkLogTool.showLogError("接收 飞控 数据异常," + e.getMessage());e.printStackTrace();}});}private void processOx19(byte[] mReceiveBytes, int len) {if (mReceiveBytes[0] == (byte) 0xFF && mReceiveBytes[1] == (byte) 0x5A) {int userDataLen = ((mReceiveBytes[7] & 0x0FF) << 8) + (mReceiveBytes[6] & 0x0FF);if (userDataLen > 0 && userDataLen < len) {byte[] userDataArray = new byte[userDataLen];System.arraycopy(mReceiveBytes, 10, userDataArray, 0, userDataLen);int band = userDataArray[1];//01是2.4G,02是5.8Gif (iUsbHostDataListner != null) {if (band == 1) {iUsbHostDataListner.onReceiveSignType("2.4G");} else if (band == 2) {iUsbHostDataListner.onReceiveSignType("5.8G");}}}}}private void process0x82(byte[] mReceiveBytes, int len) {if (mReceiveBytes[0] == (byte) 0xFF && mReceiveBytes[1] == (byte) 0x5A) {int userDataLen = ((mReceiveBytes[7] & 0x0FF) << 8) + (mReceiveBytes[6] & 0x0FF);if (userDataLen > 0 && userDataLen < len) {byte[] userDataArray = new byte[userDataLen];System.arraycopy(mReceiveBytes, 10, userDataArray, 0, userDataLen);int groundSign= userDataArray[3];//天空端信号质量(该值必须是模块已连接成功才有效,取值范围 0-100,值越大,信号越好)int airSign = userDataArray[4];if (iUsbHostDataListner != null) {iUsbHostDataListner.onReceiveHdSingQuality(airSign);iUsbHostDataListner.onReceiveRcSignQuality(groundSign);}}}}private void showMsg(Context context, String str) {Toast.makeText(context, str, Toast.LENGTH_SHORT).show();}public void setVisible(boolean isVisible) {if (fiFoUsbStream != null) {fiFoUsbStream.setVisible(isVisible);}}private void stopStatusQuery() {int len = -1;byte[] data = new byte[8];Arrays.fill(data, (byte) 0x00);data[0] = 0x11;data[1] = 0x01;data[2] = 0x00;try {if (USB_CONNECTED == usbState) {len = mDeviceConnection.bulkTransfer(endPointForSendData, data, 3, 1000);}} catch (Exception e) {e.printStackTrace();}}}

Android usb host相关推荐

  1. Android USB Host与HID通讯

    Android USB Host与HID通讯 (一) Android USB Host与HID通讯 (二) Android USB Host与HID通讯Demo android usb host 读写 ...

  2. Android USB Host与HID通讯(二)

    2019独角兽企业重金招聘Python工程师标准>>> 原文出处:http://han21912.lofter.com/post/c3919_51401d 接上一篇:Android ...

  3. 翻译Android USB HOST API

    翻译Android USB HOST API 源码地址:http://developer.android.com/guide/topics/connectivity/usb/host.html 译者注 ...

  4. android usb host hid,Android USB Host与HID通讯

    前端时间捣鼓一个HID的硬件, 需要和android通信, 网上搜索了一圈,收获不小. 其中代码之处有些地方需要注意的, 特此注明一下: /*** USB HOST 连接 HID *@authorIV ...

  5. android USB host编程

    测试手机:华为p8 测试系统:android ------------------------------------------- android的native层usbhost供java层andro ...

  6. Android USB Host开发之manager.getDeviceList()获取不到设备列表【转载】

    原文:https://www.2cto.com/kf/201305/211304.html 同样遇到这样的问题,我的Android设备是原道N12C,官方的4.0.3系统,遇到这个问题,后来找了半天找 ...

  7. Android USB Host开发之manager.getDeviceList()获取不到设备列表

    同样遇到这样的问题,我的Android设备是原道N12C,官方的4.0.3系统,遇到这个问题,后来找了半天找到的,现在汇总一下吧: 1.创建 android.hardware.usb.host.xml ...

  8. android otg读写文件,Android USB Host在USB设备OTG中读/写文件

    我正在编写Android设备是主机的应用程序.用户将USB驱动器连接到Android设备,我的应用程序将在USB驱动器中写入一些文本文件.文本文件的路径就像USB_DRIVE/Data/APP_NAM ...

  9. Android USB转串口编程

    安卓手机的对外通信接口就只有USB跟音频口,我们可采用其进行与外设进行通信.今天,我们来讲讲安卓手机利用USB接口与外设进行通信.此时,有两种情况. 第一:USB(手机)<--->USB( ...

最新文章

  1. 2018中国移动机器人行业十大热词
  2. acwing算法题--完全背包问题
  3. 手写自定义注解实现思路
  4. 老人寻求到一名程序员,用2W行代码给自己打造了一幅肖像画
  5. python病毒usb文件自动安装_将文件自动复制到USB上
  6. 友勤签约中芝软件系统(上海)有限公司Oracle Crystal Ball Suite软件项目
  7. JS中动态创建元素的三种方法
  8. 基于Simulink模型的嵌入式代码生成与实际工程应用
  9. 视觉-相机图像质量测试
  10. iphone 价格的秘密
  11. 第五章 澄清概念意义
  12. kindle paperwhite3 android,Kindle Paperwhite3入手20天感受
  13. html——简单文章发布html页面及富文本编辑器wangEditor的使用
  14. 大数据华而不实么?大数据的本质是什么?
  15. 【软件测试】男生vs女生,谁更加适合?没有你发现不了的bug......
  16. 计算机打印中 纸张不出来,打印机总是卡纸怎么办,打印机卡纸拿不出来怎么办...
  17. C# 程序关闭托盘图标不会自动消失
  18. 哪个省才是高考地狱模式?有你的省吗?
  19. ThinkPad开机停留在boot menu界面、进不了系统的解决方法
  20. mysql dos入门_【Mysql】初学命令行指南

热门文章

  1. Cardv行车记录仪视频恢复方法
  2. MongoDB(5)文档 CRUD 操作
  3. 淘金币抵扣比列设置越多越好吗?
  4. CRM--今日简报(接口实现)
  5. python新闻评论分析_新闻详情页
  6. java实现黄金分割数
  7. 神州数码DCRS设备学习总结心得
  8. C# 学习笔记(十)数组练习
  9. Subspace Clustering详解(附带CLIQUE算法详解)
  10. Python—写个可转债分析器