USB(通用串行总线)主机模式向外设进行供电,使 Android 设备能够驱动 USB 总线,并且可以使用各种 USB 外设(包括音频接口,存储,MIDI),USB 和蓝牙低功耗连接都可以用于传输 MIDI 协议。USB配件模式,受外设供电驱动,包括数据传输,充电。USB开发模式,应用调试,唯一可见的外设功能是 Android fastboot 或 Android 调试桥 (adb)。fastboot 和 adb 协议所在层高于 USB 批量数据传输模式所在层。

Android 平台支持使用即插即用的 USB 摄像头(例如网络摄像头),但前提是这些摄像头采用标准的 Android Camera2 API 和摄像头 HIDL 接口,全新的 USB 摄像头 HAL 进程是外接摄像头提供程序的一部分,该提供程序会监听 USB 设备可用性,并相应地枚举外接摄像头设备。该进程具有与内置摄像头 HAL 进程类似的权限和 SE 策略。直接与 USB 设备通信的第三方网络摄像头应用访问 UVC 设备时所需的摄像头权限与所有常规摄像头应用所需的权限相同。

1. Usb服务启动

frameworks\base\services\usb\java\com\android\server\usb\UsbService.java

public static class Lifecycle extends SystemService {private UsbService mUsbService;......@Overridepublic void onStart() { mUsbService = new UsbService(getContext());} //USB服务初始化@Overridepublic void onBootPhase(int phase) {if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {mUsbService.systemReady(); //系统准备就绪} else if (phase == SystemService.PHASE_BOOT_COMPLETED) {mUsbService.bootCompleted();//系统启动完成}}......}

frameworks\base\services\usb\java\com\android\server\usb\UsbService.java
初始化USB服务

 public UsbService(Context context) {mContext = context;//多用户管理mUserManager = context.getSystemService(UserManager.class);//用户管理设置mSettingsManager = new UsbSettingsManager(context);//高级音频管理mAlsaManager = new UsbAlsaManager(context);//包管理final PackageManager pm = mContext.getPackageManager();//USB主机模式if (pm.hasSystemFeature(PackageManager.FEATURE_USB_HOST)) {mHostManager = new UsbHostManager(context, mAlsaManager, mSettingsManager);}//USB设备管理if (new File("/sys/class/android_usb").exists()) {mDeviceManager = new UsbDeviceManager(context, mAlsaManager, mSettingsManager);}//USB端口管理if (mHostManager != null || mDeviceManager != null) {mPortManager = new UsbPortManager(context);}//切换为系统用户onSwitchUser(UserHandle.USER_SYSTEM);//注册设备代理管理final IntentFilter filter = new IntentFilter();filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);mContext.registerReceiver(mReceiver, filter, null, null);}

frameworks\base\services\usb\java\com\android\server\usb\UsbService.java

 public void systemReady() {mAlsaManager.systemReady();if (mDeviceManager != null) {mDeviceManager.systemReady();}if (mHostManager != null) {mHostManager.systemReady();}if (mPortManager != null) {mPortManager.systemReady();}}

frameworks\base\services\usb\java\com\android\server\usb\UsbAlsaManager.java
frameworks\base\services\usb\java\com\android\server\usb\UsbDeviceManager.java
frameworks\base\services\usb\java\com\android\server\usb\UsbHostManager.java
frameworks\base\services\usb\java\com\android\server\usb\UsbPortManager.java

 //系统音频服务
public void systemReady() {mAudioService = IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));mAlsaObserver.startWatching(); //开始高级音频监听......}//创建USB设备Notification
public void systemReady() {mNotificationManager = (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE);// Ensure that the notification channels are set upif (isTv()) {// TV-specific notification channelmNotificationManager.createNotificationChannel(new NotificationChannel(ADB_NOTIFICATION_CHANNEL_ID_TV,mContext.getString(com.android.internal.R.string.adb_debugging_notification_channel_tv),NotificationManager.IMPORTANCE_HIGH));}......mHandler.sendEmptyMessage(MSG_SYSTEM_READY);}//启动USB主机模式总线
public void systemReady() {synchronized (mLock) {// Create a thread to call into native code to wait for USB host events.// This thread will call us back on usbDeviceAdded and usbDeviceRemoved.Runnable runnable = new Runnable() {public void run() {monitorUsbHostBus();}};new Thread(null, runnable, "UsbService host thread").start();}}//查询USB设备端口转状态
public void systemReady() {if (mProxy != null) {try {mProxy.queryPortStatus();} catch (RemoteException e) {.......}}mSystemReady = true;}

2. USB设备打开

frameworks\base\core\java\android\hardware\usb\UsbManager.java

public UsbDeviceConnection openDevice(UsbDevice device) {try {String deviceName = device.getDeviceName();//打开USB设备,返回文件描述符FDParcelFileDescriptor pfd = mService.openDevice(deviceName);if (pfd != null) {//创建Socket连接通道,用于数据指令传输UsbDeviceConnection connection = new UsbDeviceConnection(device);boolean result = connection.open(deviceName, pfd, mContext);pfd.close();if (result) {return connection;}}} catch (Exception e) {Log.e(TAG, "exception in UsbManager.openDevice", e);}return null;}

frameworks\base\services\usb\java\com\android\server\usb\UsbService.java

/* Opens the specified USB device (host mode) */@Overridepublic ParcelFileDescriptor openDevice(String deviceName) {ParcelFileDescriptor fd = null;if (mHostManager != null) {synchronized (mLock) {if (deviceName != null) {int userIdInt = UserHandle.getCallingUserId();boolean isCurrentUser = isCallerInCurrentUserProfileGroupLocked();//以主机模式打开制定USBif (isCurrentUser) {fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt));} else {Slog.w(TAG, "Cannot open " + deviceName + " for user " + userIdInt +" as user is not active.");}}}}return fd;}

frameworks\base\services\usb\java\com\android\server\usb\UsbHostManager.java

    /* Opens the specified USB device */public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) {synchronized (mLock) {if (isBlackListed(deviceName)) {throw new SecurityException("USB device is on a restricted bus");}//从已存在的USB设备列表中查找一个UsbDevice device = mDevices.get(deviceName);......settings.checkPermission(device);return nativeOpenDevice(deviceName);}}

frameworks\base\services\core\jni\com_android_server_UsbHostManager.cpp

static jobject android_server_UsbHostManager_openDevice(JNIEnv *env, jobject /* thiz */,jstring deviceName)
{const char *deviceNameStr = env->GetStringUTFChars(deviceName, NULL);//调用系统提供的USB设备打开函数struct usb_device* device = usb_device_open(deviceNameStr);env->ReleaseStringUTFChars(deviceName, deviceNameStr);//获得USB设备的文件描述符int fd = usb_device_get_fd(device);if (fd < 0) {usb_device_close(device);return NULL;}int newFD = dup(fd);usb_device_close(device);jobject fileDescriptor = jniCreateFileDescriptor(env, newFD);if (fileDescriptor == NULL) {return NULL;}return env->NewObject(gParcelFileDescriptorOffsets.mClass,gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
}

system\core\libusbhost\usbhost.c

struct usb_device *usb_device_open(const char *dev_name)
{int fd, did_retry = 0, writeable = 1;D("usb_device_open %s\n", dev_name);retry:fd = open(dev_name, O_RDWR); //打开设备if (fd < 0) {/* if we fail, see if have read-only access *///失败则以只读模式打开fd = open(dev_name, O_RDONLY);D("usb_device_open open returned %d errno %d\n", fd, errno);if (fd < 0 && (errno == EACCES || errno == ENOENT) && !did_retry) {/* work around race condition between inotify and permissions management */sleep(1);did_retry = 1;goto retry;}if (fd < 0)return NULL;writeable = 0;D("[ usb open read-only %s fd = %d]\n", dev_name, fd);}//新建一个USB设备struct usb_device* result = usb_device_new(dev_name, fd);if (result)result->writeable = writeable;return result;
}

system\core\libusbhost\usbhost.c

struct usb_device *usb_device_new(const char *dev_name, int fd)
{struct usb_device *device = calloc(1, sizeof(struct usb_device)); //分配内存int length;D("usb_device_new %s fd: %d\n", dev_name, fd);if (lseek(fd, 0, SEEK_SET) != 0)goto failed;length = read(fd, device->desc, sizeof(device->desc)); //读取设备描述符长度D("usb_device_new read returned %d errno %d\n", length, errno);if (length < 0)goto failed;strncpy(device->dev_name, dev_name, sizeof(device->dev_name) - 1);device->fd = fd;device->desc_length = length;// assume we are writeable, since usb_device_get_fd will only return writeable fdsdevice->writeable = 1;return device;failed:close(fd);free(device);return NULL;
}

frameworks\base\core\java\android\hardware\usb\UsbDeviceConnection.java

public UsbDeviceConnection(UsbDevice device) {mDevice = device;}/* package */ boolean open(String name, ParcelFileDescriptor pfd, @NonNull Context context) {mContext = context.getApplicationContext();boolean wasOpened = native_open(name, pfd.getFileDescriptor());......return wasOpened;}

frameworks\base\core\jni\android_hardware_UsbDeviceConnection.cpp
调用JNI层打开指定的USB设备

static jboolean
android_hardware_UsbDeviceConnection_open(JNIEnv *env, jobject thiz, jstring deviceName,jobject fileDescriptor)
{int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);// duplicate the file descriptor, since ParcelFileDescriptor will eventually close its copyfd = dup(fd);if (fd < 0)return JNI_FALSE;const char *deviceNameStr = env->GetStringUTFChars(deviceName, NULL);struct usb_device* device = usb_device_new(deviceNameStr, fd); //新建if (device) {env->SetLongField(thiz, field_context, (jlong)device);} else {ALOGE("usb_device_open failed for %s", deviceNameStr);close(fd);}env->ReleaseStringUTFChars(deviceName, deviceNameStr);return (device != NULL) ? JNI_TRUE : JNI_FALSE;
}

3.USB设备检测

frameworks\base\services\usb\java\com\android\server\usb\UsbPortManager.java
设备端口管理

 public UsbPortManager(Context context) {mContext = context;try {//HIDL层的Serviceboolean ret = IServiceManager.getService().registerForNotifications("android.hardware.usb@1.0::IUsb","", mServiceNotification);......} catch (RemoteException e) {......return;}connectToProxy(null);}

frameworks\base\services\usb\java\com\android\server\usb\UsbPortManager.java

private void connectToProxy(IndentingPrintWriter pw) {synchronized (mLock) {try {//获取HIDL服务mProxy = IUsb.getService(); mProxy.linkToDeath(new DeathRecipient(pw), USB_HAL_DEATH_COOKIE);//设置回调,一会儿再回头看mProxy.setCallback(mHALCallback); mProxy.queryPortStatus();} catch (NoSuchElementException e) {......}}}

hardware\interfaces\usb\1.0\default\Usb.cpp

Return<void> Usb::setCallback(const sp<IUsbCallback>& callback) {pthread_mutex_lock(&mLock);if ((mCallback == NULL && callback == NULL) ||(mCallback != NULL && callback != NULL)) {mCallback = callback;pthread_mutex_unlock(&mLock);return Void();}......destroyThread = false;signal(SIGUSR1, sighandler);//创建线程,运行workif (pthread_create(&mPoll, NULL, work, this)) {ALOGE("pthread creation failed %d", errno);mCallback = NULL;}pthread_mutex_unlock(&mLock);return Void();
}

hardware\interfaces\usb\1.0\default\Usb.cpp
使用EPOLL,UEVENT机制,多路IO阻塞复用

void* work(void* param) {int epoll_fd, uevent_fd;struct epoll_event ev;int nevents = 0;struct data payload;uevent_fd = uevent_open_socket(64*1024, true); //创建套接//payload是个转换结构体payload.uevent_fd = uevent_fd;payload.usb = (android::hardware::usb::V1_0::implementation::Usb *)param;fcntl(uevent_fd, F_SETFL, O_NONBLOCK);ev.events = EPOLLIN;//绑定事件处理函数uevent_eventev.data.ptr = (void *)uevent_event;epoll_fd = epoll_create(64);//循环等待UEVENT事件while (!destroyThread) {struct epoll_event events[64];nevents = epoll_wait(epoll_fd, events, 64, -1);for (int n = 0; n < nevents; ++n) {if (events[n].data.ptr)(*(void (*)(int, struct data *payload))events[n].data.ptr)(events[n].events, &payload);}}
}

hardware\interfaces\usb\1.0\default\Usb.cpp
处理来自内核的USB驱动事件

static void uevent_event(uint32_t /*epevents*/, struct data *payload) {char msg[UEVENT_MSG_LEN + 2];char *cp;int n;n = uevent_kernel_multicast_recv(payload->uevent_fd, msg, UEVENT_MSG_LEN);if (n <= 0)return;if (n >= UEVENT_MSG_LEN)   /* overflow -- discard */return;msg[n] = '\0';msg[n + 1] = '\0';cp = msg;//如果有数据,继续处理while (*cp) {if (!strcmp(cp, "SUBSYSTEM=dual_role_usb")) {ALOGE("uevent received %s", cp);if (payload->usb->mCallback != NULL) {hidl_vec<PortStatus> currentPortStatus;Status status = getPortStatusHelper(currentPortStatus);//执行上层的回调Return<void> ret =payload->usb->mCallback->notifyPortStatusChange(currentPortStatus, status);}break;}/* advance to after the next \0 */while (*cp++);}
}

frameworks\base\services\usb\java\com\android\server\usb\UsbPortManager.java
现在我们来看看刚刚的遇到的mHALCallback

 private static class HALCallback extends IUsbCallback.Stub {public IndentingPrintWriter pw;public UsbPortManager portManager;.......public void notifyPortStatusChange(ArrayList<PortStatus> currentPortStatus, int retval) {ArrayList<RawPortInfo> newPortInfo = new ArrayList<RawPortInfo>();//处理USB端口状态for (PortStatus current : currentPortStatus) {RawPortInfo temp = new RawPortInfo(current.portName,current.supportedModes, current.currentMode,current.canChangeMode, current.currentPowerRole,current.canChangePowerRole,current.currentDataRole, current.canChangeDataRole);newPortInfo.add(temp);logAndPrint(Log.INFO, pw, "ClientCallback: " + current.portName);}//转给UsbPortManager的Handler来处理Message message = portManager.mHandler.obtainMessage();Bundle bundle = new Bundle();bundle.putParcelableArrayList(PORT_INFO, newPortInfo);message.what = MSG_UPDATE_PORTS;message.setData(bundle);portManager.mHandler.sendMessage(message);return;}......};

frameworks\base\services\usb\java\com\android\server\usb\UsbPortManager.java

 private final Handler mHandler = new Handler(FgThread.get().getLooper()) {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_UPDATE_PORTS: {Bundle b = msg.getData();ArrayList<RawPortInfo> PortInfo = b.getParcelableArrayList(PORT_INFO);synchronized (mLock) {updatePortsLocked(null, PortInfo);}break;}}}};

frameworks\base\services\usb\java\com\android\server\usb\UsbPortManager.java

private void updatePortsLocked(IndentingPrintWriter pw, ArrayList<RawPortInfo> newPortInfo) {//处理USB设备的插入删除移除// Process the updates.// Once finished, the list of ports will only contain ports in DISPOSITION_READY.for (int i = mPorts.size(); i-- > 0; ) {final PortInfo portInfo = mPorts.valueAt(i);switch (portInfo.mDisposition) {case PortInfo.DISPOSITION_ADDED:handlePortAddedLocked(portInfo, pw);portInfo.mDisposition = PortInfo.DISPOSITION_READY;break;case PortInfo.DISPOSITION_CHANGED:handlePortChangedLocked(portInfo, pw);portInfo.mDisposition = PortInfo.DISPOSITION_READY;break;case PortInfo.DISPOSITION_REMOVED:mPorts.removeAt(i);portInfo.mUsbPortStatus = null; // must do this earlyhandlePortRemovedLocked(portInfo, pw);break;}}}

frameworks\base\services\usb\java\com\android\server\usb\UsbPortManager.java
终于发送了一个广播将端口状态信息发送出去

 private void sendPortChangedBroadcastLocked(PortInfo portInfo) {final Intent intent = new Intent(UsbManager.ACTION_USB_PORT_CHANGED);intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND |Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);intent.putExtra(UsbManager.EXTRA_PORT, portInfo.mUsbPort);intent.putExtra(UsbManager.EXTRA_PORT_STATUS, portInfo.mUsbPortStatus);// Guard against possible reentrance by posting the broadcast from the handler// instead of from within the critical section.mHandler.post(new Runnable() {@Overridepublic void run() {mContext.sendBroadcastAsUser(intent, UserHandle.ALL);}});}

4. USB设备事务处理

frameworks\base\services\usb\java\com\android\server\usb\UsbDeviceManager.java

  public UsbDeviceManager(Context context, UsbAlsaManager alsaManager,UsbSettingsManager settingsManager) {//USB配件模式检查mHasUsbAccessory = pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY);if (nativeIsStartRequested()) {if (DEBUG) Slog.d(TAG, "accessory attached at boot");startAccessoryMode();}mHandler = new UsbHandler(FgThread.get().getLooper());//开发人员adb调试是否打开boolean secureAdbEnabled = SystemProperties.getBoolean("ro.adb.secure", false);boolean dataEncrypted = "1".equals(SystemProperties.get("vold.decrypt"));if (secureAdbEnabled && !dataEncrypted) {mDebuggingManager = new UsbDebuggingManager(context); //usb调试管理}//对应上面发送的广播mContext.registerReceiver(mHostReceiver,new IntentFilter(UsbManager.ACTION_USB_PORT_CHANGED));mContext.registerReceiver(mChargingReceiver,new IntentFilter(Intent.ACTION_BATTERY_CHANGED));}

frameworks\base\services\usb\java\com\android\server\usb\UsbDeviceManager.java
监听消息转发处理事务

 //adb调试模式开关监听private class AdbSettingsObserver extends ContentObserver {public AdbSettingsObserver() {super(null);}@Overridepublic void onChange(boolean selfChange) {boolean enable = (Settings.Global.getInt(mContentResolver,Settings.Global.ADB_ENABLED, 0) > 0);mHandler.sendMessage(MSG_ENABLE_ADB, enable);}}//监听来自内核的Ueventprivate final UEventObserver mUEventObserver = new UEventObserver() {@Overridepublic void onUEvent(UEventObserver.UEvent event) {if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());String state = event.get("USB_STATE");String accessory = event.get("ACCESSORY");if (state != null) {mHandler.updateState(state);} else if ("START".equals(accessory)) {if (DEBUG) Slog.d(TAG, "got accessory start");startAccessoryMode();}}};//接收USB状态变化private final BroadcastReceiver mHostReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {UsbPort port = intent.getParcelableExtra(UsbManager.EXTRA_PORT);UsbPortStatus status = intent.getParcelableExtra(UsbManager.EXTRA_PORT_STATUS);mHandler.updateHostState(port, status); //交给内部Handler处理,不再深入了}};//接受USB拔插状态private final BroadcastReceiver mChargingReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {int chargePlug = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);boolean usbCharging = chargePlug == BatteryManager.BATTERY_PLUGGED_USB;mHandler.sendMessage(MSG_UPDATE_CHARGING_STATE, usbCharging);}};

最后在UsbHandler里处理包括MIDI,主机/配件模式,音频,adb调试,状态Notification,设备的增减状态变化等事务。具体就不再细究了,以后遇到再分析。

Android8.0 USB系统框架相关推荐

  1. Android8.0 USB系统框架

    USB(通用串行总线)主机模式向外设进行供电,使 Android 设备能够驱动 USB 总线,并且可以使用各种 USB 外设(包括音频接口,存储,MIDI),USB 和蓝牙低功耗连接都可以用于传输 M ...

  2. Android8.0 蓝牙系统

    Android 提供支持经典蓝牙和蓝牙低功耗的默认蓝牙堆栈.借助蓝牙,Android 设备可以创建个人区域网络,以便通过附近的蓝牙设备发送和接收数据,在 Android 4.3 及更高版本中,Andr ...

  3. Android8.0以上系统ROOT时,Magisk框架替代SpuerSU

    9月末,我负责的一个手机评测业务,有ROOT手机的刚需. 手机是刚上市华为Nova3,Android版本8.1. 按照"祖传"刷机手法,使用SuperSU来ROOT手机一直没有成功 ...

  4. Android8.0 Audio系统之AudioFlinger

    继上一篇AudioTrack的分析,本篇我们来看AudioFlinger,AF主要承担音频混合输出,是Audio系统的核心,从AudioTrack来的数据最终都会在这里处理,并被写入到Audio的HA ...

  5. Android8.0 Media系统(一)

    以上四篇对Audio系统的简要分析,由于Audio涉及的范围比较广,以后分析其他子系统时在做详细分析.我们继续Media系统的征程,Media系统仍然是一个庞大的系统,以MediaPlayer为例,贯 ...

  6. Android8.0 Audio系统之AudioPolicy

    上一篇我们跟踪分析了AudioFlinger,它是Audio系统的核心,但是AudioFlinger却不能脱离AudioPolicy工作.AudioPolicy模块承载着音频切换,音轨路由的重要工作, ...

  7. android8.0调用系统浏览器,ie浏览器在线使用,ie浏览器8.0手机安卓版-

    Ie浏览器是微软开发的浏览器,这种浏览器是目前最常见的兼容性最强的浏览器. Ie浏览器也是世界上最早期出现的网络浏览器,主要是用于查资料和上网使用的. 过去的年代里没有太多的播放器和图片查看软件,人们 ...

  8. android8.1.0官方下载,官方Xposed框架For Android8.0/8.1(Oreo)发布v90-beta3版本

    去年12月份的时候记得跟大家提到过Android8.0(Oreo)版本的Xposed框架的作者也带来的新消息,说是代码部分与已经完成了大约95%,但是剩余的5%都是比较难的,而且由于上面那个aosp的 ...

  9. 华为android贡献度,Android8.0系统占比大幅提升,华为、荣耀、小米成为主要贡献...

    原标题:Android8.0系统占比大幅提升,华为.荣耀.小米成为主要贡献 安卓推出Android8.0系统至今已半年有余,如今Android8.1稳定版.Android9.0开发者预览版也发布了,但 ...

最新文章

  1. window 获取进程运行长
  2. [转载]web集群时利用memcache来同步session
  3. Nestjs 微服务
  4. zen3架构_全新Zen3架构,AMD发布最新Ryzen5000系列处理器
  5. delphi 运行外部程序函数winexec WinExecAndWait32 CreateProcess
  6. 文本分类入门(四)训练Part 1
  7. 7-11 有重复的数据 (10 分)
  8. 实例讲解——系统登录
  9. C++ 虚函数,纯虚函数,抽象类整理
  10. python博弈论代码_科学网—两篇关于社交网络和博弈论的论文及源代码分享 - 陈俊东的博文...
  11. ORA-00600: internal error code, arguments: [2037]
  12. Attention机制原理
  13. 脉脉的高聘有意义么?
  14. sklearn 随机森林(Random Forest)多分类问题
  15. 【英语语法入门】第41讲 原形不定式(2)感官动词
  16. 陈艾盐:春燕百集访谈节目第十九集
  17. 又双叒叕夺冠!5年厚积薄发,汇佳学校绿龙冰球队今夏“京城双冠王”!
  18. 未定义标识符ULONG_PTR,BOOL等错误解决方法 vs2013
  19. 超级码力在线编程大赛初赛 第2场 题解
  20. 渗透测试工具实战技巧合集

热门文章

  1. 电脑搜不到wifi?新换的路由器
  2. 真正的 IT 男是什么样的?
  3. mongodb count查询记录条数
  4. 6-使用VMWARE虚拟机调试XPE的方法
  5. SVM问题的求解方法SMO算法
  6. BZOJ4408:[FJOI2016]神秘数
  7. 20180402-F · US Tuition Costs · pheatmap 绘制热图 · R 语言数据可视化 案例 源码
  8. 微信统一下单prepay_id为空php,微信支付-普通下单开发者文档
  9. 【杂题】cf1041fF. Ray in the tube
  10. 美杜莎扫描器使用教程