Android 上面的蓝牙inquiry 是在设置界面,打开蓝牙就会自动搜索周边的蓝牙设备,其最终调用到协议栈的start_discovery接口,此篇文章分析该接口的调用流程以及与controller交互过程。

static int start_discovery(void)
{/*sanity check*/if (interface_ready() ==FALSE)returnBT_STATUS_NOT_READY;return btif_dm_start_discovery();
}

bt_status_t btif_dm_start_discovery(void)
{tBTA_DM_INQ inq_params;tBTA_SERVICE_MASK services= 0;tBTA_DM_BLE_PF_FILT_PARAMS adv_filt_param;#if (defined(BLE_INCLUDED) && (BLE_INCLUDED == TRUE))memset(&adv_filt_param, 0, sizeof(tBTA_DM_BLE_PF_FILT_PARAMS));/*Cleanup anything remaining on index 0*/BTA_DmBleScanFilterSetup(BTA_DM_BLE_SCAN_COND_DELETE,0, &adv_filt_param, NULL,bte_scan_filt_param_cfg_evt,0);/*Add an allow-all filter on index 0*/adv_filt_param.dely_mode=IMMEDIATE_DELY_MODE;adv_filt_param.feat_seln=ALLOW_ALL_FILTER;adv_filt_param.filt_logic_type=BTA_DM_BLE_PF_FILT_LOGIC_OR;adv_filt_param.list_logic_type=BTA_DM_BLE_PF_LIST_LOGIC_OR;adv_filt_param.rssi_low_thres=LOWEST_RSSI_VALUE;adv_filt_param.rssi_high_thres=LOWEST_RSSI_VALUE;BTA_DmBleScanFilterSetup(BTA_DM_BLE_SCAN_COND_ADD,0, &adv_filt_param, NULL,bte_scan_filt_param_cfg_evt,0);/*TODO: Do we need to handle multiple inquiries at the same time?*//*Set inquiry params and call API*/inq_params.mode= BTA_DM_GENERAL_INQUIRY|BTA_BLE_GENERAL_INQUIRY;//设置inquiry模式,ble和bredr
#elseinq_params.mode=BTA_DM_GENERAL_INQUIRY;#endifinq_params.duration= BTIF_DM_DEFAULT_INQ_MAX_DURATION;//设置超时时间
inq_params.max_resps= BTIF_DM_DEFAULT_INQ_MAX_RESULTS;//设置接收的最大的response数量inq_params.report_dup =TRUE;inq_params.filter_type= BTA_DM_INQ_CLR;//0/*TODO: Filter device by BDA needs to be implemented here*//*Will be enabled to TRUE once inquiry busy level has been received*/btif_dm_inquiry_in_progress=FALSE;/*find nearby devices*/BTA_DmSearch(&inq_params, services, bte_search_devices_evt);//执行搜索returnBT_STATUS_SUCCESS;
}

上面代码的主要做的事情是,设置相关的搜索的参数,然后调用BTA_DmSearch(&inq_params, services, bte_search_devices_evt); 进行搜索。

我们这里再次整理一下 传入BTA_DmSearch的参数:

    inq_params.mode = BTA_DM_GENERAL_INQUIRY|BTA_BLE_GENERAL_INQUIRY;inq_params.duration=BTIF_DM_DEFAULT_INQ_MAX_DURATION;inq_params.max_resps=BTIF_DM_DEFAULT_INQ_MAX_RESULTS;inq_params.report_dup=TRUE;inq_params.filter_type= BTA_DM_INQ_CLR;//0
tBTA_SERVICE_MASK services= 0;bte_search_devices_evt 是一个回调函数,实现也是在btif_dm.c,最终会被赋值给bta_dm_search_cb.p_search_cback= bte_search_devices_evt

我们看看BTA_DmSearch具体的实现,

/*******************************************************************************
**
** Function         BTA_DmSearch
**
** Description      This function searches for peer Bluetooth devices. It performs
**                  an inquiry and gets the remote name for devices. Service
**                  discovery is done if services is non zero
**
**
** Returns          void
**
*******************************************************************************/
void BTA_DmSearch(tBTA_DM_INQ *p_dm_inq, tBTA_SERVICE_MASK services, tBTA_DM_SEARCH_CBACK *p_cback)
{tBTA_DM_API_SEARCH*p_msg;if ((p_msg = (tBTA_DM_API_SEARCH *) GKI_getbuf(sizeof(tBTA_DM_API_SEARCH))) !=NULL){memset(p_msg,0, sizeof(tBTA_DM_API_SEARCH));p_msg->hdr.event =BTA_DM_API_SEARCH_EVT;memcpy(&p_msg->inq_params, p_dm_inq, sizeof(tBTA_DM_INQ));p_msg->services =services;p_msg->p_cback =p_cback;p_msg->rs_res  =BTA_DM_RS_NONE;bta_sys_sendmsg(p_msg);}}

这里发现就是组装了一个msg,然后发送给btu 线程处理,这里整理一下发送给btu 线程的参数:

p_msg->hdr.event = BTA_DM_API_SEARCH_EVT;
memcpy(&p_msg->inq_params, p_dm_inq, sizeof(tBTA_DM_INQ)); 上面赋值的参数
p_msg->services = services;//service = 0
p_msg->p_cback = p_cback;//bte_search_devices_evt

p_msg->rs_res = BTA_DM_RS_NONE;

我们查找一下, BTA_DM_API_SEARCH_EVT 执行的函数:bta_dm_search_start

/*******************************************************************************
**
** Function         bta_dm_search_start
**
** Description      Starts an inquiry
**
**
** Returns          void
**
*******************************************************************************/
void bta_dm_search_start (tBTA_DM_MSG *p_data)
{tBTM_INQUIRY_CMPL result;#if (BLE_INCLUDED == TRUE && BTA_GATT_INCLUDED == TRUE)UINT16 len= (UINT16)(sizeof(tBT_UUID) * p_data->search.num_uuid);bta_dm_gattc_register();#endifif (p_bta_dm_cfg->avoid_scatter &&(p_data->search.rs_res == BTA_DM_RS_NONE) &&bta_dm_check_av(BTA_DM_API_SEARCH_EVT)){memcpy(&bta_dm_cb.search_msg, &p_data->search, sizeof(tBTA_DM_API_SEARCH));return;}BTM_ClearInqDb(NULL);/*save search params*/bta_dm_search_cb.p_search_cback= p_data->search.p_cback;bta_dm_search_cb.services= p_data->search.services;#if (BLE_INCLUDED == TRUE && BTA_GATT_INCLUDED == TRUE)utl_freebuf((void **)&bta_dm_search_cb.p_srvc_uuid);if ((bta_dm_search_cb.num_uuid = p_data->search.num_uuid) != 0 &&p_data->search.p_uuid != NULL)//如果在搜索的时候设置了uuid,那么也会将这个值copy给bta_dm_search_cb.p_srvc_uuid
{bta_dm_search_cb.p_srvc_uuid= (tBT_UUID *)GKI_getbuf(len)memcpy(bta_dm_search_cb.p_srvc_uuid, p_data->search.p_uuid, len);}#endifresult.status= BTM_StartInquiry(   (tBTM_INQ_PARMS*)&p_data->search.inq_params,bta_dm_inq_results_cb,//扫描到设备调用此函数(tBTM_CMPL_CB*) bta_dm_inq_cmpl_cb);//扫描完成会调用到此函数if (result.status !=BTM_CMD_STARTED){result.num_resp= 0;bta_dm_inq_cmpl_cb ((void *)&result);//如果开始失败,直接调用回调函数汇报
}
}

上面的代码执行完,我们看看bta_dm_search_cb的各个结构的值:

bta_dm_search_cb.p_search_cback = bte_search_devices_evt

bta_dm_search_cb.services = 0;

bta_dm_search_cb.num_uuid  =0;

bta_dm_search_cb.p_srvc_uuid = NULL;

下面继续看看BTM_StartInquiry 的实现,这里可以简单分析一下,传入的三个参数:

第一个参数是,上面组装的inquiry相关的设置。

第二个参数是 扫描到设备调用

第三个参数是 扫描完成 (inquiry complete)的时候调用

/*******************************************************************************
**
** Function         BTM_StartInquiry
**
** Description      This function is called to start an inquiry.
**
** Parameters:      p_inqparms - pointer to the inquiry information
**                      mode - GENERAL or LIMITED inquiry, BR/LE bit mask seperately
**                      duration - length in 1.28 sec intervals (If '0', the inquiry is CANCELLED)
**                      max_resps - maximum amount of devices to search for before ending the inquiry
**                      filter_cond_type - BTM_CLR_INQUIRY_FILTER, BTM_FILTER_COND_DEVICE_CLASS, or
**                                         BTM_FILTER_COND_BD_ADDR
**                      filter_cond - value for the filter (based on filter_cond_type)
**
**                  p_results_cb   - Pointer to the callback routine which gets called
**                                upon receipt of an inquiry result. If this field is
**                                NULL, the application is not notified.
**
**                  p_cmpl_cb   - Pointer to the callback routine which gets called
**                                upon completion.  If this field is NULL, the
**                                application is not notified when completed.
** Returns          tBTM_STATUS
**                  BTM_CMD_STARTED if successfully initiated
**                  BTM_BUSY if already in progress
**                  BTM_ILLEGAL_VALUE if parameter(s) are out of range
**                  BTM_NO_RESOURCES if could not allocate resources to start the command
**                  BTM_WRONG_MODE if the device is not up.
**
*******************************************************************************/tBTM_STATUS BTM_StartInquiry (tBTM_INQ_PARMS*p_inqparms, tBTM_INQ_RESULTS_CB *p_results_cb,tBTM_CMPL_CB*p_cmpl_cb)
{tBTM_STATUS  status=BTM_CMD_STARTED;tBTM_INQUIRY_VAR_ST*p_inq = &btm_cb.btm_inq_vars;p_inq->scan_type =INQ_GENERAL;
.../*Save the inquiry parameters to be used upon the completion of setting/clearing the inquiry filter*/p_inq->inqparms = *p_inqparms;//保存inquiry的参数/*Initialize the inquiry variables*/p_inq->state = BTM_INQ_ACTIVE_STATE;//activep_inq->p_inq_cmpl_cb =p_cmpl_cb;p_inq->p_inq_results_cb =p_results_cb;p_inq->inq_cmpl_info.num_resp = 0;         /*Clear the results counter*/p_inq->inq_active = p_inqparms->mode;//0x11/*start LE inquiry here if requested*/if ((p_inqparms->mode &BTM_BLE_INQUIRY_MASK)){if ((status = btm_ble_start_inquiry((UINT8)(p_inqparms->mode &BTM_BLE_INQUIRY_MASK),p_inqparms->duration)) !=BTM_CMD_STARTED){BTM_TRACE_ERROR("Err Starting LE Inquiry.");p_inq->inqparms.mode &= ~BTM_BLE_INQUIRY_MASK;}#if (!defined(BTA_HOST_INTERLEAVE_SEARCH) || BTA_HOST_INTERLEAVE_SEARCH == FALSE)p_inqparms->mode &= ~BTM_BLE_INQUIRY_MASK;//开始inquiry之后,mode参数置0
#endif}/*BR/EDR inquiry portion*/.../*Before beginning the inquiry the current filter must be cleared, so initiate the command*/if ((status = btm_set_inq_event_filter (p_inqparms->filter_cond_type,&p_inqparms->filter_cond)) != BTM_CMD_STARTED)//这里没有看到BRRDR的扫描,而只有这个清楚过滤的函数,那我可以想象,肯定是这个函数做完之后会自动开始bredr设备的inquiryp_inq->state =BTM_INQ_INACTIVE_STATE;return(status);
}

上面的代码主要做了三件事:

  1. 将一些参数保存在tBTM_INQUIRY_VAR_ST *p_inq = &btm_cb.btm_inq_vars  这个结构。

    1. p_inq->scan_type = INQ_GENERAL;
    2. p_inq->inqparms = *p_inqparms;
    3. p_inq->state = BTM_INQ_ACTIVE_STATE;
    4. p_inq->p_inq_cmpl_cb = p_cmpl_cb;
    5. p_inq->p_inq_results_cb = p_results_cb;
    6. p_inq->inq_cmpl_info.num_resp = 0; /* Clear the results counter */

    7. p_inq->inq_active = p_inqparms->mode = 0x11(BLE|BREDR)
  2. 做BLE 的inquiry :btm_ble_start_inquiry((UINT8)(p_inqparms->mode & BTM_BLE_INQUIRY_MASK),p_inqparms->duration)
  3. 做BREDR的inquiry:btm_set_inq_event_filter (p_inqparms->filter_cond_type,&p_inqparms->filter_cond)

btm_ble_start_inquiry

tBTM_STATUS btm_ble_start_inquiry (UINT8 mode, UINT8   duration)
{tBTM_STATUS status=BTM_CMD_STARTED;tBTM_BLE_CB*p_ble_cb = &btm_cb.ble_ctr_cb;tBTM_INQUIRY_VAR_ST*p_inq = &btm_cb.btm_inq_vars;
...if (!BTM_BLE_IS_SCAN_ACTIVE(p_ble_cb->scan_activity))//如果没有scan 正在进行,开始scan
{btsnd_hcic_ble_set_scan_params(BTM_BLE_SCAN_MODE_ACTI,//active scan
BTM_BLE_LOW_LATENCY_SCAN_INT,BTM_BLE_LOW_LATENCY_SCAN_WIN,btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type,SP_ADV_ALL);#if (defined BLE_PRIVACY_SPT && BLE_PRIVACY_SPT == TRUE)/*enable IRK list*/btm_ble_enable_resolving_list_for_platform(BTM_BLE_RL_SCAN);#endifp_ble_cb->inq_var.scan_duplicate_filter  =BTM_BLE_DUPLICATE_DISABLE;status= btm_ble_start_scan();//开始scan
}else if ((p_ble_cb->inq_var.scan_interval != BTM_BLE_LOW_LATENCY_SCAN_INT) ||(p_ble_cb->inq_var.scan_window != BTM_BLE_LOW_LATENCY_SCAN_WIN)) {//如果已经有正在scan的行为,先停掉当前的scan,启动新的scan
btsnd_hcic_ble_set_scan_enable(BTM_BLE_SCAN_DISABLE, BTM_BLE_DUPLICATE_ENABLE);btsnd_hcic_ble_set_scan_params(BTM_BLE_SCAN_MODE_ACTI,BTM_BLE_LOW_LATENCY_SCAN_INT,BTM_BLE_LOW_LATENCY_SCAN_WIN,btm_cb.ble_ctr_cb.addr_mgnt_cb.own_addr_type,SP_ADV_ALL);btsnd_hcic_ble_set_scan_enable(BTM_BLE_SCAN_ENABLE, BTM_BLE_DUPLICATE_DISABLE);}if (status ==BTM_CMD_STARTED){p_inq->inq_active |= mode;//置位p_ble_cb->scan_activity |=mode;if (duration != 0){/*start inquiry timer*/btu_start_timer (&p_inq->inq_timer_ent, BTU_TTYPE_BLE_INQUIRY, duration);//开始定时器,当定时器时间到的时候会 disable scan
}}returnstatus;
}

上面的代码的逻辑非常的简单,需要留意一下就是 p_inq->inq_active |= mode 和 p_ble_cb->scan_activity |= mode;

现在Ble 的扫描就开始了,持续时间10s = 10*1000ms,定时器超时时间到了之后会取消scan。

从上面我们还可以看出从inquiry 下发的ble scan的优先级还是很高,它会停掉当前的scan的行为。如果此时后台有observe 行为,也会被停掉。

btm_set_inq_event_filter

statictBTM_STATUS btm_set_inq_event_filter (UINT8 filter_cond_type,tBTM_INQ_FILT_COND*p_filt_cond)
{
...btm_cb.btm_inq_vars.inqfilt_active=TRUE;设置了标志位/*Filter the inquiry results for the specified condition type and value*/if(btsnd_hcic_set_event_filter(HCI_FILTER_INQUIRY_RESULT, filter_cond_type,p_cond, condition_length))return(BTM_CMD_STARTED);elsereturn(BTM_NO_RESOURCES);
}

上面主要是发送了HCI_FILTER_INQUIRY_RESULT ,以及设置了状态位:btm_cb.btm_inq_vars.inqfilt_active = TRUE

搜索的行为肯定是在这个函数的完成事件中进行的。

static void btu_hcif_hdl_command_complete (UINT16 opcode, UINT8 *p, UINT16 evt_len,void *p_cplt_cback)
{switch(opcode){caseHCI_INQUIRY_CANCEL:/*Tell inquiry processing that we are done*/btm_process_cancel_complete(HCI_SUCCESS, BTM_BR_INQUIRY_MASK);break;caseHCI_SET_EVENT_FILTER:btm_event_filter_complete (p);//执行此函数break;

void btm_event_filter_complete (UINT8 *p)
{UINT8            hci_status;tBTM_STATUS      status;tBTM_INQUIRY_VAR_ST*p_inq = &btm_cb.btm_inq_vars;tBTM_CMPL_CB*p_cb = p_inq->p_inqfilter_cmpl_cb;
.../*Only process the inquiry filter; Ignore the connection filter until itis used by the upper layers*/if (p_inq->inqfilt_active == TRUE ) //在命令下发的时候已经设置
{/*Extract the returned status from the buffer*/STREAM_TO_UINT8 (hci_status, p);if (hci_status !=HCI_SUCCESS){/*If standalone operation, return the error status; if embedded in the inquiry, continue the inquiry*/BTM_TRACE_WARNING ("BTM Warning: Set Event Filter Failed (HCI returned 0x%x)", hci_status);status=BTM_ERR_PROCESSING;}elsestatus=BTM_SUCCESS;/*If the set filter was initiated externally (via BTM_SetInqEventFilter), call thecallback function to notify the initiator that it has completed*/if (p_inq->state == BTM_INQ_INACTIVE_STATE)//此时是active状态
{p_inq->inqfilt_active =FALSE;if(p_cb)(*p_cb) (&status);}else    /*An inquiry is active (the set filter command was internally generated),process the next state of the process (Set a new filter or start the inquiry).*/{if(status !=BTM_SUCCESS){/*Process the inquiry complete (Error Status)*/btm_process_inq_complete (BTM_ERR_PROCESSING, (UINT8)(p_inq->inqparms.mode &BTM_BR_INQUIRY_MASK));/*btm_process_inq_complete() does not restore the following settings on periodic inquiry*/p_inq->inqfilt_active =FALSE;p_inq->inq_active =BTM_INQUIRY_INACTIVE;p_inq->state =BTM_INQ_INACTIVE_STATE;return;}/*Check to see if a new filter needs to be set up*/if (p_inq->state ==BTM_INQ_CLR_FILT_STATE){if ((status = btm_set_inq_event_filter (p_inq->inqparms.filter_cond_type, &p_inq->inqparms.filter_cond)) ==BTM_CMD_STARTED){p_inq->state =BTM_INQ_SET_FILT_STATE;}else    /*Error setting the filter: Call the initiator's callback function to indicate a failure*/{p_inq->inqfilt_active =FALSE;/*Process the inquiry complete (Error Status)*/btm_process_inq_complete (BTM_ERR_PROCESSING, (UINT8)(p_inq->inqparms.mode &BTM_BR_INQUIRY_MASK));}}else    /*Initiate the Inquiry or Periodic Inquiry*/{p_inq->state = BTM_INQ_ACTIVE_STATE;//处于该状态p_inq->inqfilt_active = FALSE;//设置falsebtm_initiate_inquiry (p_inq);//开始扫描
}}}
}

上面代码的做的主要的事情:

p_inq->state = BTM_INQ_ACTIVE_STATE;
p_inq->inqfilt_active = FALSE;
btm_initiate_inquiry (p_inq);

继续看btm_initiate_inquiry的实现:

static void btm_initiate_inquiry (tBTM_INQUIRY_VAR_ST *p_inq)
{const LAP       *lap;tBTM_INQ_PARMS*p_inqparms = &p_inq->inqparms;btm_acl_update_busy_level (BTM_BLI_INQ_EVT);
...lap= (p_inq->inq_active & BTM_LIMITED_INQUIRY_ACTIVE) ? &limited_inq_lap : &general_inq_lap;//设置LAP
{btm_clr_inq_result_flt();//清数据库/*Allocate memory to hold bd_addrs responding*/if ((p_inq->p_bd_db = (tINQ_BDADDR *)GKI_getbuf(GKI_MAX_BUF_SIZE)) !=NULL){p_inq->max_bd_entries = (UINT16)(GKI_MAX_BUF_SIZE / sizeof(tINQ_BDADDR));memset(p_inq->p_bd_db, 0, GKI_MAX_BUF_SIZE);/*BTM_TRACE_DEBUG("btm_initiate_inquiry: memory allocated for %d bdaddrs",p_inq->max_bd_entries);*/}if (!btsnd_hcic_inquiry(*lap, p_inqparms->duration, 0))//发送inquiry 命令btm_process_inq_complete (BTM_NO_RESOURCES, (UINT8)(p_inqparms->mode &BTM_BR_INQUIRY_MASK));}
}

上面代码中,重点就是btsnd_hcic_inquiry(*lap, p_inqparms->duration, 0)

其实现就是向controller 发送inquiry 的命令。这里注意其duration参数是10,对应的实际的值是10*1.28s,在12.8秒之后会有inquiry complete 事件传上来。

对于inquiry 的命令发送流程就分析到这里。

转载于:https://www.cnblogs.com/libs-liu/p/9229253.html

蓝牙inquiry流程之命令下发相关推荐

  1. 蓝牙inquiry流程之Advertising Report

    setting 界面开始搜索的时候,通常也会同时进行le scan,这一点在inquiry流程之命令下发中已经讲述.此篇文章主要是分析一下对于controller 搜索到的广播包的处理.这里以Andr ...

  2. NVMe over TCP Write/Read命令下发流程梳理

    总结 本文对NVMe over TCP的write和read命令下发流程进行了梳理 1. 环境 只针对Linux5.4.0版本的nvme内核模块源代码,使用命令 sudo nvme io-passth ...

  3. 蓝牙配对码配置错误_安卓系统蓝牙配对流程分析

    安卓系统蓝牙配对流程分析 配对流程基本上始于首次连接一个蓝牙设备的过程中,本端的搜索流程结束获取到该设备的BluetoothDevice信息,就可以开启配对流程. 配对,顾名思义就是将两个设备通过相关 ...

  4. 安卓application_安卓系统蓝牙配对流程分析

    安卓系统蓝牙配对流程分析 配对流程基本上始于首次连接一个蓝牙设备的过程中,本端的搜索流程结束获取到该设备的BluetoothDevice信息,就可以开启配对流程. 配对,顾名思义就是将两个设备通过相关 ...

  5. 经典蓝牙 蓝牙连接 - 从AIR LOG和HCI LOG分别分析蓝牙连接流程

    1.目录 文章目录 1.目录 2.LMP连接 3.从HCI LOG分析蓝牙连接流程 1.HCI_Inquiry 1.1 命令参数解析 1.1.1 LAP IAC 1.1.2 Inquiry_Lengt ...

  6. 电信物联网平台,java后台对接电信北向应用,命令下发到设备

    最近公司设备需要对接电信物联网平台,需要实现数据下发到设备 实现流程 第三方应用 ----- > 电信平台 ( 下发给设备) --------> 设备   ------ > 电信平台 ...

  7. java对接移动物联网onenet平台实现接收设备上报数据命令下发

    java对接移动物联网onenet平台实现接收设备上报数据命令下发. 最近由于工作需要,公司设备集成了物联网卡,需要实现数据上报命令下发等(目前集成了电信,移动,联通三个平台).电信和移动都有自己的开 ...

  8. 小白学习IOT之模拟设备RRPC命令下发

    RPC通讯可以从设备向服务器端上报消息,也可以通过服务器下发命令给设备,这种通讯又叫RRPC,这次我们来介绍和实践一下RRPC的命令通讯: 通过服务端下发命令模拟设备指示灯的开关,来测试RRPC通信. ...

  9. Android wifi carlife,carlife可以无线连接吗?carlife蓝牙连接流程

    Carlife可以无线连接.目前无线连接支持iPhone手机使用,而安卓手机只有少部分可以使用.连接方法如下: 1.打开手机设置启用蜂窝移动数据,再打开手机WiFi,并且开一个无线热点. 2.找到车机 ...

  10. 细数APDL中的流程控制命令

    作者:水哥ANSYS,获授权转载 一.概述 有过其他编程语言经验的同学都知道,流程控制类语言命令在编程中是必须掌握的一门技巧,这类命令能大幅提高我们的编程效率,增加程序可读性.类似地,在APDL中也有 ...

最新文章

  1. python 把一个字典赋值给一个空的字典,或者是列表赋值给一个空的列表显示黄色警告
  2. java chunked 解码_模拟http请求 带 chunked解析办法一
  3. System之Ubuntu:VMware虚拟机 Ubuntu安装详细过程(图文教程,最强攻略,步骤详细,建议收藏)
  4. [LeetCode]--20. Valid Parentheses
  5. 基于Dapper的开源Lambda扩展,且支持分库分表自动生成实体之基础
  6. leetcode - 1021. 删除最外层的括号
  7. asp.net Core 中间件Hello world
  8. 大华的支持rtmp推流吗_RTSP安防摄像机(海康大华宇视等)如何推送到RTMP流媒体服务器进行直播...
  9. MySQL高可用之主备同步:javafor循环乘法表
  10. 案例:需求问题的解决方案
  11. 做数据可视化有什么好
  12. UCHOME ajaxmenU()用法
  13. ServletContextListener 的应用
  14. Java实现附近地点搜索
  15. 快门光圈感光度口诀_曝光补偿怎么调,快门光圈感光度口诀,深度解析曝光补偿...
  16. 计算机启动显示不正确的分区表,双硬盘启动失败提示“无效分区表”无法打开机器...
  17. Android图片压缩尺寸和质量
  18. 机器学习python基础(一)魔法命令
  19. Java—求绝对值(选择结构)
  20. Java项目:springboot在线心理咨询系统

热门文章

  1. 【转帖】文件系统驱动编程基础篇
  2. Java实现邮件抓取(亲测126,163,新浪都可以抓取到)
  3. C++洛谷题解(17)——P5713
  4. 靶机Tempus Fugit wirteup
  5. 高中数学怎么学好学好高中数学的技巧
  6. 百度编辑器UEditor图片上传尺寸大小设置
  7. 软件测试中报表测试用例设计方法总结
  8. YS-M8C自助终端主板RK3288详解和说明
  9. ICCV 2021 Oral | 新类别发现(Novel Class Discovery)的统一目标
  10. 乾坤大挪移——使用PQ分区魔术师扩大C盘空间