Bluedroid中的线程介绍

来源:互联网 发布:2017年的同志网络剧 编辑:程序博客网 时间:2024/05/21 01:58

Bluedroid中的线程介绍 

版权所有,转载时请注明出处
luowh0822@outlook.com

本文基于android O的代码进行分析。通过本文档,能够了解bluedroid的线程结构和协议栈架构。

线程的基本用法

osi/src/thread.cc
对posix的线程函数进行了封装

thread_t* thread_new(const char* name);bool thread_post(thread_t* thread, thread_fn func, void* context);

使用thread_new()创建一个线程,参数为线程名字的字符串。
使用thread_post()把子线程的处理函数传递给子线程。
Bluedroid封装的后的thread函数与POSIX的thread有点不同。
- thread_new不需要传入子线程的入口函数,内部会创建一个默认的入口函数,等待用户传入任务。
- thread_post传递用户真正需要的执行函数给子线程。

线程详解

struct thread_t {  std::atomic_bool is_joined{false}; //是否joined  pthread_t pthread; //pthread的handle  pid_t tid; //线程id  char name[THREAD_NAME_MAX + 1]; //线程名  reactor_t* reactor; //很重要的一个参数,循环epoll所有注册的queue,当queue中有数据就会调用相应的函数处理  fixed_queue_t* work_queue; //处理函数队列,用户调用thread_post()把处理函数及参数放到这个队列中,在reactor中处理。}; thread_t* thread_new_sized(const char* name, size_t work_queue_capacity) {  thread_t* ret = static_cast<thread_t*>(osi_calloc(sizeof(thread_t)));  ret->reactor = reactor_new();  ret->work_queue = fixed_queue_new(work_queue_capacity);  // Start is on the stack, but we use a semaphore, so it's safe  struct start_arg start;  start.start_sem = semaphore_new(0);  strncpy(ret->name, name, THREAD_NAME_MAX);  start.thread = ret;  start.error = 0;  pthread_create(&ret->pthread, NULL, run_thread, &start);  semaphore_wait(start.start_sem);  semaphore_free(start.start_sem);}static void* run_thread(void* start_arg) {  struct start_arg* start = static_cast<struct start_arg*>(start_arg);  thread_t* thread = start->thread;  thread->tid = gettid();  semaphore_post(start->start_sem);  int fd = fixed_queue_get_dequeue_fd(thread->work_queue);  void* context = thread->work_queue;  reactor_object_t* work_queue_object =    reactor_register(thread->reactor, fd, context, work_queue_read_cb, NULL);  reactor_start(thread->reactor);  ...}
  1. 当用户调用thread_new后,内部首先创建线程的reactor和work_queue,semaphore用来创建线程时跟父线程同步用的。
  2. 然后调用pthread_create(&ret->pthread, NULL, run_thread, &start)函数,创建并执行run_thread()接口函数。
  3. 在run_thread()函数里,调用reactor_register(),把work_queue加入到epoll的集合里。具体细节是work_queue有semaphore,而semaphore是使用eventfd实现的,eventfd可以使用epoll来监听。当有数据放到queue里面,semaphore就会改变,epoll就可以被唤醒。
  4. reactor_start(thread->reactor),函数里面有死循环,通过epoll,监听所有)通过reactor_register()传入的queue。当queue有数据传入,执行fixed_queue_register_dequeue()传入的对应处理函数。

创建线程后,线程会把自己内部的work_queue注册到reactor,当用户调用thread_post,传入函数指针和参数后,子线程就会处理。当创建线程后,至少会调用一次thread_post。我们可以看到btif_a2dp_sink.cc和btif_a2dp_source.cc中多次调用thread_post()的情况。以btif_a2dp_source.cc为例,当创建线程时,会把cmd_msg_queue注册到reactor中,用来接收a2dp的控制命令,另外当a2dp工作时,会设置一个周期的定时器,当超时,会调用thread_post,做音频数据编码再发送的工作,另外a2dp停止时,也会调用thread_post去停止。
thread还有另一种工作方式。用户调用fixed_queue_register_dequeue(),把外部的queue传给thread。这时候,向queue里添加元素(Event),就可以处理。后面bta_btu_thread会有详解的讲解。

bluedroid中的线程列表

在/system/bt/目录下”grep -rns thread_new *”就可以列出所有的thread。
下面列出的的是去掉测试代码中的线程后的线程列表:

thread description thread_new(“module_wrapper”) module manager thread_new(“stack_manager”) stack manager, enable other thread thread_new(“btif_sock”) btif socket thread_new(“btif_a2dp_sink_worker_thread”) a2dp sink thread_new_sized(“media_worker”, MAX_MEDIA_WORKQUEUE_SEM_COUNT) a2dp source thread_new(BT_JNI_WORKQUEUE_NAME) jni thread thread_new(“hci_thread”) hci thread thread_new(“hci_inject”) send hci raw data thread_new_sized(“alarm_default_callbacks”, SIZE_MAX) alarm callback execution thread_new(“alarm_dispatcher”) alarm management thread_new(BT_WORKQUEUE_NAME) bta_btu thread, core stack thread.

另外,Android N及之前的版本有bt transport layer的线程,用来读uart的数据。在Android O的时候,这部分的工作在hidl里完成了。

主要线程分析

接下来分析bluedroid流程分析时最重要的三个线程,从下往上分别是:hci_thread, bta_btu_thread, jni_thread。
hci_thread:靠近底层传输层,完成HCI的拆包和组包工作,处理HCI命令,事件及数据包。
bta_btu_thread:实现协议栈的核心功能
jni_thread:衔接协议栈及上层JNI

jni_thread

  • 上层的java service调用bluredroid接口时,切换到这个线程执行调用程序,并跟下层(协议栈核心模块,bta_btu_thread)交互
  • bta_btu_thread向上发送的event,切换到这个线程处理event。
btif_core.ccbt_status_t btif_init_bluetooth() {    bt_jni_workqueue_thread = thread_new(BT_JNI_WORKQUEUE_NAME);    thread_post(bt_jni_workqueue_thread, run_message_loop, nullptr);}bt_status_t btif_transfer_context(tBTIF_CBACK* p_cback, uint16_t event, ...)

创建线程后,会执行run_message_loop的入口函数。这个跟Android N不同。Android O引入chrominum的MessageLoop机制。创建MessageLoop后,调用PostTask,就可以往这个线程里发任务(处理函数和数据)让这个线程处理。跟thread_post功能类似,还没想到为什么要用MessageLoop。
btif_transfer_context()是其他线程(jni上层和bta_btu_thread)需要把task切换到btif线程处理时,调用的函数。可以看到这个函数最终会调用到PostTask()。
jni_thread会调用上层初始化时注册的callback,向上层发送event.
jni_thread通过bta_sys_sendmsg()来向bta_btu_thread发送消息。实现的细节是向btu_bta_msg_queue里添加消息。bta_btu_thread会处理这个queue中的消息。

bta_btu_thread

蓝牙核心协议栈的处理都在这个线程里完成。

void BTU_StartUp(void) {    btu_bta_msg_queue = fixed_queue_new(SIZE_MAX);     btu_general_alarm_queue = fixed_queue_new(SIZE_MAX);    bt_workqueue_thread = thread_new(BT_WORKQUEUE_NAME);    thread_post(bt_workqueue_thread, btu_task_start_up, NULL);}void btu_task_start_up(UNUSED_ATTR void* context) {    ...    fixed_queue_register_dequeue(btu_bta_msg_queue,             thread_get_reactor(bt_workqueue_thread),             btu_bta_msg_ready, NULL);  fixed_queue_register_dequeue(btu_hci_msg_queue,        thread_get_reactor(bt_workqueue_thread),        btu_hci_msg_ready, NULL);  alarm_register_processing_queue(btu_general_alarm_queue,        bt_workqueue_thread);}

这个线程比较简单,创建线程后,该线程就epoll三个队列。有事件后就处理,处理完后再epoll等待。
1. btu_bta_msg_queue: jni_thread发送过来的事件
2. btu_hci_msg_queue: hci发送的事件,主要是hci的event和l2cap层的数据
3. btu_general_alarm_queue: 协议栈中用到的定时器超时产生的事件

协议栈下行和上行的处理流程如下:
下行:jni_thread通过bta_sys_sendmsg()发送消息给bta_btu_thread, bta_btu_thread处理完后,最终会调用hci_layer中的enqueue_packet(),把完整的包发给hci_thread,在hci_thread会进行拆包并发送(fragment_and_dispatch())。
上行:hci_thread组成一个完整的包后,送到btu_hci_msg_queue(搜索fixed_queue_enqueue(btu_hci_msg_queue, event)), bta_btu_thread处理完成后,再通过btif_transfer_context()发送给jni_thread。

hci_thread

hci_layer.cc
hci_thread跟jni_thread类似,也是使用MessageLoop来工作的。
主要的工作是收发命令(包括命令的超时管理),接收组包和发送拆包等工作。
向下发送HCI命令时,调用enqueue_command(),切换到hci_thread处理。
向下发送数据时,调用enqueue_packet(),切换到hci_thread处理。
向上发送组包完成后的数据或Event时,会调用fixed_queue_enqueue(upwards_data_queue, packet),这个upwards_data_queue是初始化时bta_btu传进来的,其实就是hci_msg_queue。

分析问题时怎么跟协议栈的代码流程

由于多线程的存在,并且协议栈有很多的状态机,不了解代码框架的话,很容易就糊涂了。下面讲解一下怎么从jni_thread跳转到bta_btu_thread工作的。

前面介绍过的,jni_thread跳转到bta_btu_thread是通过bta_sys_sendmsg()发送event,来切换线程的。

接下来重点介绍event的定义和处理方式。

模块定义

bluedroid定义了一个system manager的模块。它把协议栈分成了很多个小模块(包括蓝牙的各个profile)。看下面的定义。

bta/sys/bta_sys.h#define BTA_ID_SYS 0 /* system manager *//* BLUETOOTH PART - from 0 to BTA_ID_BLUETOOTH_MAX */#define BTA_ID_DM 1             /* device manager */#define BTA_ID_DM_SEARCH 2      /* device manager search */#define BTA_ID_DM_SEC 3         /* device manager security */#define BTA_ID_DG 4             /* data gateway */#define BTA_ID_AG 5             /* audio gateway */#define BTA_ID_OPC 6            /* object push client */#define BTA_ID_OPS 7            /* object push server */#define BTA_ID_FTS 8            /* file transfer server */#define BTA_ID_CT 9             /* cordless telephony terminal */#define BTA_ID_FTC 10           /* file transfer client */#define BTA_ID_SS 11            /* synchronization server */#define BTA_ID_PR 12            /* Printer client */#define BTA_ID_BIC 13           /* Basic Imaging Client */#define BTA_ID_PAN 14           /* Personal Area Networking */#define BTA_ID_BIS 15           /* Basic Imaging Server */#define BTA_ID_ACC 16           /* Advanced Camera Client */#define BTA_ID_SC 17            /* SIM Card Access server */#define BTA_ID_AV 18            /* Advanced audio/video */#define BTA_ID_AVK 19           /* Audio/video sink */#define BTA_ID_HD 20            /* HID Device */#define BTA_ID_CG 21            /* Cordless Gateway */#define BTA_ID_BP 22            /* Basic Printing Client */#define BTA_ID_HH 23            /* Human Interface Device Host */#define BTA_ID_PBS 24           /* Phone Book Access Server */#define BTA_ID_PBC 25           /* Phone Book Access Client */#define BTA_ID_JV 26            /* Java */#define BTA_ID_HS 27            /* Headset */#define BTA_ID_MSE 28           /* Message Server Equipment */#define BTA_ID_MCE 29           /* Message Client Equipment */#define BTA_ID_HL 30            /* Health Device Profile*/#define BTA_ID_GATTC 31         /* GATT Client */#define BTA_ID_GATTS 32         /* GATT Client */#define BTA_ID_SDP 33           /* SDP Client */#define BTA_ID_BLUETOOTH_MAX 34 /* last BT profile *//* GENERIC */ #define BTA_ID_PRM 38 #define BTA_ID_SYSTEM 39  /* platform-specific */#define BTA_ID_SWRAP 40   /* Insight script wrapper */#define BTA_ID_MIP 41     /* Multicase Individual Polling */#define BTA_ID_RT 42      /* Audio Routing module: This module is always on. */#define BTA_ID_CLOSURE 43 /* Generic C++ closure  *//* JV */#define BTA_ID_JV1 44 /* JV1 */#define BTA_ID_JV2 45 /* JV2 */#define BTA_ID_MAX (44 + BTA_DM_NUM_JV_ID)

模块内的event定义

接着,我们可以看到system manager给每个子模块划分了8bit来定义子模块的event。每个event值是模块ID向右位移8位后加上模块内部的枚举值。模块内的event定义文件在bta/xx/bta_xx_int.h中,xx代表一个子模块。 如下面的代码所示。用dm search模块来具体说明。

#define BTA_SYS_EVT_START(id) ((id) << 8) dm search模块的event跟dm模块都定义在bta/dm/bta_dm_int.h中,带API的是jni_thread发下来的请求,不带的话是从下层或本层发送的event。/* DM search events */enum {  /* DM search API events */  //每个模块的event的都是从模块ID向右位移8位作为起始id。  BTA_DM_API_SEARCH_EVT = BTA_SYS_EVT_START(BTA_ID_DM_SEARCH),   BTA_DM_API_SEARCH_CANCEL_EVT,  BTA_DM_API_DISCOVER_EVT,  BTA_DM_INQUIRY_CMPL_EVT,  BTA_DM_REMT_NAME_EVT,  BTA_DM_SDP_RESULT_EVT,  BTA_DM_SEARCH_CMPL_EVT,  BTA_DM_DISCOVERY_RESULT_EVT,   BTA_DM_API_DI_DISCOVER_EVT,   BTA_DM_DISC_CLOSE_TOUT_EVT}; 

当event发到bta_btu_thread后,system manager是怎么处理的呢?
我们接着往下看。

当event 发到bta_btu_thread后,会执行btu_bta_msg_ready(),调用bta_sys_event()。

void bta_sys_event(BT_HDR* p_msg) {  uint8_t id;   /* get subsystem id from event */  id = (uint8_t)(p_msg->event >> 8);   /* verify id and call subsystem event handler */  if ((id < BTA_ID_MAX) && (bta_sys_cb.reg[id] != NULL)) {    freebuf = (*bta_sys_cb.reg[id]->evt_hdlr)(p_msg);  } else {  }}

从上面的代码,我们可以看到,在bta_sys_event()里,会根据具体的event值,移位操作得到子模块的id,再根据这个id值,得到子模块注册时候传入的evt_hdlr函数,然后调用这个函数去处理。我们再以dm search模块来分析。

tBTA_STATUS BTA_EnableBluetooth(tBTA_DM_SEC_CBACK* p_cback) {    bta_sys_register(BTA_ID_DM_SEARCH, &bta_dm_search_reg);}static const tBTA_SYS_REG bta_dm_search_reg = {bta_dm_search_sm_execute,  bta_dm_search_sm_disable};/* registration structure */typedef struct {  tBTA_SYS_EVT_HDLR* evt_hdlr;  tBTA_SYS_DISABLE* disable;} tBTA_SYS_REG;bool bta_dm_search_sm_execute(BT_HDR* p_msg) {  tBTA_DM_ST_TBL state_table;  uint8_t action;  int i;  /* look up the state table for the current state */  //查找当前状态的状态转换表  state_table = bta_dm_search_st_tbl[bta_dm_search_cb.state];  //处理完成后,要跳转的状态  bta_dm_search_cb.state =     state_table[p_msg->event & 0x00ff][BTA_DM_SEARCH_NEXT_STATE];  /* execute action functions */  //查找状态表中的action进行处理。多个action的话依次执行。  for (i = 0; i < BTA_DM_SEARCH_ACTIONS; i++) {    action = state_table[p_msg->event & 0x00ff][i];    if (action < BTA_DM_SEARCH_IGNORE) {     (*bta_dm_search_action[action])((tBTA_DM_MSG*)p_msg);    } else {     break;    }  }  return true;} 

从上面的代码我们可以看到,在蓝牙启动时(BTA_EnableBluetooth)会在system manager注册dm search的处理函数。从结构体的定义可以看出,bta_system_evt()用到的evt_hdlr(dm search模块)就是bta_dm_search_execute()。我们下面再分析这个函数。
在函数内部,我们可以看到p_msg->event & 0x00ff这个操作,它的作用就是去掉event中模块id的值,那么剩下来的值就是模块内部的event id了。

在bta_dm_search_sm_execute中有状态机的工作的代码。在bluedroid中会有很多这样的状态机,搞明白一个后,其他的都一样,这是非常典型的c语言状态机的实现,查找状态表的时间复杂度为O(1)。

/* state machine action enumeration list */enum {  BTA_DM_API_SEARCH,                 /* 0 bta_dm_search_start */  BTA_DM_API_SEARCH_CANCEL,          /* 1 bta_dm_search_cancel */  BTA_DM_API_DISCOVER,               /* 2 bta_dm_discover */  BTA_DM_INQUIRY_CMPL,               /* 3 bta_dm_inq_cmpl */  BTA_DM_REMT_NAME,                  /* 4 bta_dm_rmt_name */  BTA_DM_SDP_RESULT,                 /* 5 bta_dm_sdp_result */  BTA_DM_SEARCH_CMPL,                /* 6 bta_dm_search_cmpl*/  BTA_DM_FREE_SDP_DB,                /* 7 bta_dm_free_sdp_db */  BTA_DM_DISC_RESULT,                /* 8 bta_dm_disc_result */  BTA_DM_SEARCH_RESULT,              /* 9 bta_dm_search_result */  BTA_DM_QUEUE_SEARCH,               /* 10 bta_dm_queue_search */  BTA_DM_QUEUE_DISC,                 /* 11 bta_dm_queue_disc */  BTA_DM_SEARCH_CLEAR_QUEUE,         /* 12 bta_dm_search_clear_queue */  BTA_DM_SEARCH_CANCEL_CMPL,         /* 13 bta_dm_search_cancel_cmpl */  BTA_DM_SEARCH_CANCEL_NOTIFY,       /* 14 bta_dm_search_cancel_notify */  BTA_DM_SEARCH_CANCEL_TRANSAC_CMPL, /* 15 bta_dm_search_cancel_transac_cmpl */  BTA_DM_DISC_RMT_NAME,              /* 16 bta_dm_disc_rmt_name */  BTA_DM_API_DI_DISCOVER,            /* 17 bta_dm_di_disc */  BTA_DM_CLOSE_GATT_CONN,            /* 18 bta_dm_close_gatt_conn */  BTA_DM_SEARCH_NUM_ACTIONS          /* 19 */};/* action function list */const tBTA_DM_ACTION bta_dm_search_action[] = {    bta_dm_search_start,               /* 0 BTA_DM_API_SEARCH */    bta_dm_search_cancel,              /* 1 BTA_DM_API_SEARCH_CANCEL */    bta_dm_discover,                   /* 2 BTA_DM_API_DISCOVER */    bta_dm_inq_cmpl,                   /* 3 BTA_DM_INQUIRY_CMPL */    bta_dm_rmt_name,                   /* 4 BTA_DM_REMT_NAME */    bta_dm_sdp_result,                 /* 5 BTA_DM_SDP_RESULT */    bta_dm_search_cmpl,                /* 6 BTA_DM_SEARCH_CMPL */    bta_dm_free_sdp_db,                /* 7 BTA_DM_FREE_SDP_DB */    bta_dm_disc_result,                /* 8 BTA_DM_DISC_RESULT */    bta_dm_search_result,              /* 9 BTA_DM_SEARCH_RESULT */    bta_dm_queue_search,               /* 10 BTA_DM_QUEUE_SEARCH */    bta_dm_queue_disc,                 /* 11 BTA_DM_QUEUE_DISC */    bta_dm_search_clear_queue,         /* 12 BTA_DM_SEARCH_CLEAR_QUEUE */    bta_dm_search_cancel_cmpl,         /* 13 BTA_DM_SEARCH_CANCEL_CMPL */    bta_dm_search_cancel_notify,       /* 14 BTA_DM_SEARCH_CANCEL_NOTIFY */    bta_dm_search_cancel_transac_cmpl, /* 15 BTA_DM_SEARCH_CANCEL_TRANSAC_CMPL*/    bta_dm_disc_rmt_name,              /* 16 BTA_DM_DISC_RMT_NAME */    bta_dm_di_disc,                    /* 17 BTA_DM_API_DI_DISCOVER */    bta_dm_close_gatt_conn};#define BTA_DM_SEARCH_IGNORE BTA_DM_SEARCH_NUM_ACTIONS/* state table information */#define BTA_DM_SEARCH_ACTIONS 2    /* number of actions */#define BTA_DM_SEARCH_NEXT_STATE 2 /* position of next state */#define BTA_DM_SEARCH_NUM_COLS 3   /* number of columns in state tables *//* DM search state */enum {   BTA_DM_SEARCH_IDLE,   BTA_DM_SEARCH_ACTIVE,  BTA_DM_SEARCH_CANCELLING,  BTA_DM_DISCOVER_ACTIVE};typedef const uint8_t (*tBTA_DM_ST_TBL)[BTA_DM_SEARCH_NUM_COLS];/* state table */const tBTA_DM_ST_TBL bta_dm_search_st_tbl[] = {    bta_dm_search_idle_st_table, bta_dm_search_search_active_st_table,    bta_dm_search_search_cancelling_st_table,    bta_dm_search_disc_active_st_table};const uint8_t bta_dm_search_disc_active_st_table[][BTA_DM_SEARCH_NUM_COLS] = {    /* Event                        Action 1           Action 2                 Next State */    /* API_SEARCH */      {BTA_DM_SEARCH_IGNORE,     BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},    /* API_SEARCH_CANCEL */  {BTA_DM_SEARCH_CANCEL_NOTIFY, BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_CANCELLING},    /* API_SEARCH_DISC */   {BTA_DM_SEARCH_IGNORE,     BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},    /* INQUIRY_CMPL */     {BTA_DM_SEARCH_IGNORE,     BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},    /* REMT_NAME_EVT */    {BTA_DM_DISC_RMT_NAME,     BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},    /* SDP_RESULT_EVT */    {BTA_DM_SDP_RESULT,       BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},    /* SEARCH_CMPL_EVT */   {BTA_DM_SEARCH_CMPL,      BTA_DM_SEARCH_IGNORE, BTA_DM_SEARCH_IDLE},    /* DISCV_RES_EVT */    {BTA_DM_DISC_RESULT,      BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},    /* API_DI_DISCOVER_EVT */ {BTA_DM_SEARCH_IGNORE,     BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE},    /* DISC_CLOSE_TOUT_EVT */ {BTA_DM_SEARCH_IGNORE,     BTA_DM_SEARCH_IGNORE, BTA_DM_DISCOVER_ACTIVE}};

首先看上面代码的state machine action enumeration list 和action function list。这两个数组是一一对应的。用法就是状态机表中action这一列填的是enum的值,查表获得这个enum值后,可以对应到具体的action函数。
接下来,这个状态机有4个状态,所以bta_dm_search_st_tbl[]里有包含4张状态表,4张状态表的格式都是完全一样的,区别就是里面的action和next state的值不同。我只贴了其中一个状态表来说明。为了看得更明白,我把状态表的格式做了一下调整。

下面再来分析状态表的细节。
状态机的工作方式就是当处于某个特定状态时,收到event后,会执行一些操作,然后再跳转到下一个状态。假设我们现在是BTA_DM_DISCOVER_ACTIVE状态,那么会选中bta_dm_search_disc_active_st_table这张表(下文说的表默认为bta_dm_search_disc_active_st_table)。在表中,我们看到有event,action, next state这些列。表的行数由event的数目决定,就是前面定义的DM search event的枚举。一般状态机只需要一个action,所有可能没有Action2列。
我们再结合代码来看看怎么查状态机。
假设当前在bta_dm_search_cb.state保存的状态是BTA_DM_DISCOVER_ACTIVE。这时候来了SEARCH_CMPL_EVT事件。
首先根据当前状态,查到的state_table为bta_dm_search_disc_active_st_table。
接着查状态表可知下一个状态为BTA_DM_SEARCH_IDLE。保存到bta_dm_search_cb.state中。
最后用for循环依次查找action的函数,依次执行,此时执行bta_dm_search_cmpl,action2为空。执行完后就返回。
当有新事件来时,再按照上面的顺序执行。


以上就是文档的所有内容,希望对你研究bluedroid,解决蓝牙问题时有帮助。如有问题请及时指正。谢谢。
原创粉丝点击