蓝牙启动的时候,会涉及到各个profile 的启动。这篇文章分析一下,蓝牙中a2dp profile的初始化流程。

我们从AdapterState.java中对于USER_TURN_ON 消息的处理说起:

switch(msg.what) {
case USER_TURN_ON:
notifyAdapterStateChange(BluetoothAdapter.STATE_TURNING_ON);
mPendingCommandState.setTurningOn(true);
transitionTo(mPendingCommandState);
sendMessageDelayed(BREDR_START_TIMEOUT, BREDR_START_TIMEOUT_DELAY);
adapterService.startCoreServices();//开始启动核心的服务,就是各种profile
break;

继续看:

    void startCoreServices()
{
debugLog("startCoreServices()");
Class[] supportedProfileServices = Config.getSupportedProfiles(); //Start profile services
if (!mProfilesStarted && supportedProfileServices.length >) {
//Startup all profile services
setProfileServiceState(supportedProfileServices,BluetoothAdapter.STATE_ON);//start all profile
}
...
}

看看setProfileServiceState的实现,他就是实现一个for 循环,在里面启动所有的profile:

    private void setProfileServiceState(Class[] services, int state) {
if (state != BluetoothAdapter.STATE_ON && state != BluetoothAdapter.STATE_OFF) {
debugLog("setProfileServiceState() - Invalid state, leaving...");
return;
} int expectedCurrentState= BluetoothAdapter.STATE_OFF;
int pendingState = BluetoothAdapter.STATE_TURNING_ON;
if (state == BluetoothAdapter.STATE_OFF) {
expectedCurrentState= BluetoothAdapter.STATE_ON;
pendingState = BluetoothAdapter.STATE_TURNING_OFF;
} for (int i=; i <services.length;i++) {
String serviceName = services[i].getName();
String simpleName = services[i].getSimpleName(); if (simpleName.equals("GattService")) continue; Integer serviceState = mProfileServicesState.get(serviceName);
if(serviceState != null && serviceState != expectedCurrentState) {
debugLog("setProfileServiceState() - Unable to "
+ (state == BluetoothAdapter.STATE_OFF ? "start" : "stop" )
+ " service " + serviceName
+ ". Invalid state: " + serviceState);
continue;
} debugLog("setProfileServiceState() - "
+ (state == BluetoothAdapter.STATE_OFF ? "Stopping" : "Starting")
+ " service " + serviceName); mProfileServicesState.put(serviceName,pendingState);
Intent intent = new Intent(this,services[i]);
intent.putExtra(EXTRA_ACTION,ACTION_SERVICE_STATE_CHANGED);
intent.putExtra(BluetoothAdapter.EXTRA_STATE,state);
startService(intent);//启动服务
}
}

startSerice 启动服务,我们这里只分析a2dp的情况,其他的profile的启动情况类似。a2dp 对应的service 文件是a2dpService.java,下面看看service的启动:

    public void onCreate() {
if (DBG) log("onCreate");
super.onCreate();
mAdapter = BluetoothAdapter.getDefaultAdapter();
mBinder = initBinder();//生成一个Binder
mAdapterService = AdapterService.getAdapterService();
if (mAdapterService != null) {
mAdapterService.addProfile(this);
} else {
Log.w(TAG, "onCreate, null mAdapterService");
}
}

我们看看a2dpService是如何实现这个mBInder的:

    protected IProfileServiceBinder initBinder() {
return new BluetoothA2dpBinder(this);//传入一个 this 指针,那么也就是说a2dpService 这个类本身就是这个service
}

看看这个BluetoothA2dpBinder  是一个什么样的接口?其实他就是对原本的服务的一个封装,包含了原来的服务

    //Binder object: Must be static class or memory leak may occur
private static class BluetoothA2dpBinder extends IBluetoothA2dp.Stub
implements IProfileServiceBinder {
private A2dpService mService; private A2dpService getService() {
if (!Utils.checkCaller()) {
Log.w(TAG,"A2dp call not allowed for non-active user");
return null;
} if (mService != null && mService.isAvailable()) {
return mService;
}
return null;
} BluetoothA2dpBinder(A2dpService svc) {
mService = svc;//构造函数传入的this 参数
} public boolean cleanup() {
mService = null;
return true;
} public boolean connect(BluetoothDevice device) {
A2dpService service = getService();
if (service == null) return false;
return service.connect(device);
} public boolean disconnect(BluetoothDevice device) {
A2dpService service = getService();
if (service == null) return false;
return service.disconnect(device);
} public List<BluetoothDevice> getConnectedDevices() {
A2dpService service = getService();
if (service == null) return new ArrayList<BluetoothDevice>(0);
return service.getConnectedDevices();
} public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
A2dpService service = getService();
if (service == null) return new ArrayList<BluetoothDevice>(0);
return service.getDevicesMatchingConnectionStates(states);
} public int getConnectionState(BluetoothDevice device) {
A2dpService service = getService();
if (service == null) return BluetoothProfile.STATE_DISCONNECTED;
return service.getConnectionState(device);
} public boolean setPriority(BluetoothDevice device, int priority) {
A2dpService service = getService();
if (service == null) return false;
return service.setPriority(device, priority);
} public int getPriority(BluetoothDevice device) {
A2dpService service = getService();
if (service == null) return BluetoothProfile.PRIORITY_UNDEFINED;
return service.getPriority(device);
} public boolean isAvrcpAbsoluteVolumeSupported() {
A2dpService service = getService();
if (service == null) return false;
return service.isAvrcpAbsoluteVolumeSupported();
} public void adjustAvrcpAbsoluteVolume(int direction) {
A2dpService service = getService();
if (service == null) return;
service.adjustAvrcpAbsoluteVolume(direction);
} public void setAvrcpAbsoluteVolume(int volume) {
A2dpService service = getService();
if (service == null) return;
service.setAvrcpAbsoluteVolume(volume);
} public boolean isA2dpPlaying(BluetoothDevice device) {
A2dpService service = getService();
if (service == null) return false;
return service.isA2dpPlaying(device);
}
}

这个mBinder 会在别的应用程序绑定的时候,返回给对方。所以其就是对原本服务的一个封装。

接着我们看onStartCommand:

    public int onStartCommand(Intent intent, int flags, int startId) {
...
String action = intent.getStringExtra(AdapterService.EXTRA_ACTION);
if (AdapterService.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
int state= intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
if(state==BluetoothAdapter.STATE_OFF) {
Log.d(mName, "Received stop request...Stopping profile...");
doStop(intent);
} else if (state == BluetoothAdapter.STATE_ON) {
Log.d(mName, "Received start request. Starting profile...");
doStart(intent);//启动
}
}
return PROFILE_SERVICE_MODE;
}

继续看:

    private void doStart(Intent intent) {
//Start service
if (mAdapter == null) {
Log.e(mName, "Error starting profile. BluetoothAdapter is null");
} else {
if (DBG) log("start()");
mStartError = !start();//这个是虚函数,看看a2dpService 具体如何实现的
if (!mStartError) {
notifyProfileServiceStateChanged(BluetoothAdapter.STATE_ON);上报状态
} else {
Log.e(mName, "Error starting profile. BluetoothAdapter is null");
}
}
}

上报的状态的部分就不分析了,我们直接看 start的部分:
avrcp的部分暂时略过,

    protected boolean start() {
mAvrcp = Avrcp.make(this);
mStateMachine = A2dpStateMachine.make(this, this);//启动状态机
setA2dpService(this);//设置a2dpService服务为本身sAd2dpService = this
return true;
}

看看状态机make 函数到底干嘛?猜想应该是初始化状态机:

    static A2dpStateMachine make(A2dpService svc, Context context) {
A2dpStateMachine a2dpSm = new A2dpStateMachine(svc, context);//新建一个状态机
a2dpSm.start();//start
return a2dpSm;
}

这里的start就是让状态机转起来,和a2dp 的关系不大,这里不分析 了,我们这里的重头戏是new A2dpStateMachine,这里涉及到一些 变量、状态的初始化,以及协议栈中关于a2dp的初始化。

    private A2dpStateMachine(A2dpService svc, Context context) {
super("A2dpStateMachine");
mService = svc;//a2dpService
mContext = context;//a2dpService
mAdapter = BluetoothAdapter.getDefaultAdapter(); initNative();//native 函数 mDisconnected = new Disconnected();//新建各种状态,类中类
mPending = new Pending();
mConnected = new Connected(); addState(mDisconnected);//往状态机中添加状态
addState(mPending);
addState(mConnected); setInitialState(mDisconnected);//设置初始状态 PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BluetoothA2dpService"); mIntentBroadcastHandler = new IntentBroadcastHandler(); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
}

如果我们不往JNI 层以下分析的话,那么应用层的Service的初始化的流程已经完成了。

这里调用initNative其实就是 对JNI 层以及协议栈进行a2dp 的初始化,我们这里也分析一下:

static void initNative(JNIEnv *env, jobject object) {
const bt_interface_t* btInf;
bt_status_t status;
...
if ( (sBluetoothA2dpInterface = (btav_interface_t *)
btInf->get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_ID)) == NULL) {//获取a2dp的接口
ALOGE("Failed to get Bluetooth A2DP Interface");
return;
} if ( (status = sBluetoothA2dpInterface->init(&sBluetoothA2dpCallbacks)) != BT_STATUS_SUCCESS) {//对接口进行初始化
ALOGE("Failed to initialize Bluetooth A2DP, status: %d", status);
sBluetoothA2dpInterface = NULL;
return;
} mCallbacksObj = env->NewGlobalRef(object);
}

我们先看一下 这个接口实现在那里,其实现在btif_av.c中,从这里开始就是C代码实现的了。

static const btav_interface_t bt_av_src_interface = {
.size = sizeof(btav_interface_t),
.init = init_src,
.connect = src_connect_sink,
.disconnect = disconnect,
.cleanup = cleanup_src,
};

那我们继续看 其init的实现:

/*******************************************************************************
**
** Function init_src
**
** Description Initializes the AV interface for source mode
**
** Returns bt_status_t
**
*******************************************************************************/ static bt_status_t init_src(btav_callbacks_t* callbacks)
{
bt_status_t status = btif_av_init();
if (status == BT_STATUS_SUCCESS)
bt_av_src_callbacks = callbacks;//保存JNI层的callback

上面的callback 是JNI 层 注册下来的,猜想,应该都是一些状态上报的callback,我们看看 都有哪些函数:

static btav_callbacks_t sBluetoothA2dpCallbacks = {
sizeof(sBluetoothA2dpCallbacks),
bta2dp_connection_state_callback,//连接状态上报
bta2dp_audio_state_callback//audio的状态上报
};

下面我们关注一下btif_av_init的实现:

/*******************************************************************************
**
** Function btif_av_init
**
** Description Initializes btif AV if not already done
**
** Returns bt_status_t
**
*******************************************************************************/ bt_status_t btif_av_init()
{
if (btif_av_cb.sm_handle == NULL)
{
if (!btif_a2dp_start_media_task())//开启media 的线程
return BT_STATUS_FAIL; /* Also initialize the AV state machine */
btif_av_cb.sm_handle =
btif_sm_init((const btif_sm_handler_t*)btif_av_state_handlers, BTIF_AV_STATE_IDLE);//初始化协议栈层面的btif里的状态机 btif_enable_service(BTA_A2DP_SOURCE_SERVICE_ID);//enable source service #if (BTA_AV_SINK_INCLUDED == TRUE)
btif_enable_service(BTA_A2DP_SINK_SERVICE_ID);
btif_a2dp_on_init();//啥也没干
} return BT_STATUS_SUCCESS;
}

这里还涉及到sink的部分,暂时不讲。

上面的代码流程分为两个部分:

  1. 开启media task的流程
  2. btif_sm 的初始化
  3. btif_enable_service(BTA_A2DP_SOURCE_SERVICE_ID)

我们先看第一部分:

开启media task的流程

bool btif_a2dp_start_media_task(void)
{
...
btif_media_cmd_msg_queue = fixed_queue_new(SIZE_MAX);//新建一个队列用于处理media 相关的cmd /* start a2dp media task */
worker_thread = thread_new("media_worker");//新建一个media_worker 县城 fixed_queue_register_dequeue(btif_media_cmd_msg_queue,
thread_get_reactor(worker_thread),
btif_media_thread_handle_cmd,
NULL);将队列和thread绑定 thread_post(worker_thread, btif_media_thread_init, NULL);//post 到media 线程中继续media 线程的init 行为 APPL_TRACE_EVENT("## A2DP MEDIA THREAD STARTED ##");
return true;
}

这里我们看看,会有哪些cmd 会塞到这个队列里面去处理:

/* BTIF media cmd event definition : BTIF_MEDIA_TASK_CMD */
enum
{
BTIF_MEDIA_START_AA_TX = ,
BTIF_MEDIA_STOP_AA_TX,
BTIF_MEDIA_AA_RX_RDY,
BTIF_MEDIA_UIPC_RX_RDY,
BTIF_MEDIA_SBC_ENC_INIT,
BTIF_MEDIA_SBC_ENC_UPDATE,
BTIF_MEDIA_SBC_DEC_INIT,
BTIF_MEDIA_VIDEO_DEC_INIT,
BTIF_MEDIA_FLUSH_AA_TX,
BTIF_MEDIA_FLUSH_AA_RX,
BTIF_MEDIA_AUDIO_FEEDING_INIT,
BTIF_MEDIA_AUDIO_RECEIVING_INIT,
BTIF_MEDIA_AUDIO_SINK_CFG_UPDATE,
BTIF_MEDIA_AUDIO_SINK_CLEAR_TRACK
}

下面我们分析下 btif_media_thread_init都做了哪些事情:

static void btif_media_thread_init(UNUSED_ATTR void *context) {
memset(&btif_media_cb, , sizeof(btif_media_cb));
UIPC_Init(NULL); #if (BTA_AV_INCLUDED == TRUE)
UIPC_Open(UIPC_CH_ID_AV_CTRL , btif_a2dp_ctrl_cb);//打开了audio的控制通道
#endif raise_priority_a2dp(TASK_HIGH_MEDIA);//提升优先级
media_task_running = MEDIA_TASK_STATE_ON;//标志线程状态
}

我们现在看看

btif_sm 的初始化

/*****************************************************************************
**
** Function btif_sm_init
**
** Description Initializes the state machine with the state handlers
** The caller should ensure that the table and the corresponding
** states match. The location that 'p_handlers' points to shall
** be available until the btif_sm_shutdown API is invoked.
**
** Returns Returns a pointer to the initialized state machine handle.
**
******************************************************************************/ btif_sm_handle_t btif_sm_init(const btif_sm_handler_t *p_handlers, btif_sm_state_t initial_state)
{
btif_sm_cb_t *p_cb; if (p_handlers == NULL)
{
BTIF_TRACE_ERROR("%s : p_handlers is NULL", __FUNCTION__);
return NULL;
} p_cb = (btif_sm_cb_t *)osi_malloc(sizeof(btif_sm_cb_t));
p_cb->state = initial_state;//初始状态
p_cb->p_handlers = (btif_sm_handler_t*)p_handlers;一组函数指针 /* Send BTIF_SM_ENTER_EVT to the initial state */
p_cb->p_handlers[initial_state](BTIF_SM_ENTER_EVT, NULL);//进入到初始状态 return (btif_sm_handle_t)p_cb;
}

这个函数 是要返回一个btif_sm_handle_t 结构给btif_av_cb.sm_handle,这个结构,里面包含一个state以及函数指针 ,如下:

typedef struct {
btif_sm_state_t state;
btif_sm_handler_t *p_handlers;
} btif_sm_cb_t;

我们现在看看,这个函数进行初始化的时候传入的函数指针 有哪些:

static const btif_sm_handler_t btif_av_state_handlers[] =
{
btif_av_state_idle_handler,
btif_av_state_opening_handler,
btif_av_state_opened_handler,
btif_av_state_started_handler,
btif_av_state_closing_handler
};

我们发现是 不同状态下的处理句柄。最后我们看看(BTIF_SM_ENTER_EVT)进入初始状态,有执行什么操作:

static BOOLEAN btif_av_state_idle_handler(btif_sm_event_t event, void *p_data)
{
BTIF_TRACE_DEBUG("%s event:%s flags %x", __FUNCTION__,
dump_av_sm_event_name(event), btif_av_cb.flags); switch (event)
{
case BTIF_SM_ENTER_EVT:
/* clear the peer_bda */
memset(&btif_av_cb.peer_bda, , sizeof(bt_bdaddr_t));
btif_av_cb.flags = ;
btif_av_cb.edr = ;
btif_a2dp_on_idle();//btif_media_cb 设置为idle,reset audio_codec_config
break;

下面我们看看

btif_enable_service(BTA_A2DP_SOURCE_SERVICE_ID);

/*******************************************************************************
**
** Function btif_enable_service
**
** Description Enables the service 'service_ID' to the service_mask.
** Upon BT enable, BTIF core shall invoke the BTA APIs to
** enable the profiles
**
** Returns bt_status_t
**
*******************************************************************************/
bt_status_t btif_enable_service(tBTA_SERVICE_ID service_id)
{
tBTA_SERVICE_ID *p_id = &service_id; /* If BT is enabled, we need to switch to BTIF context and trigger the
* enable for that profile
*
* Otherwise, we just set the flag. On BT_Enable, the DM will trigger
* enable for the profiles that have been enabled */ btif_enabled_services |= ( << service_id);//更新此全局变量 if (btif_is_enabled())
{
btif_transfer_context(btif_dm_execute_service_request,
BTIF_DM_ENABLE_SERVICE,
(char*)p_id, sizeof(tBTA_SERVICE_ID), NULL);
} return BT_STATUS_SUCCESS;
}

我们继续看btif_dm_execute_service_request:

void btif_dm_execute_service_request(UINT16 event, char *p_param)
{
BOOLEAN b_enable = FALSE;
bt_status_t status;
if (event == BTIF_DM_ENABLE_SERVICE)
{
b_enable = TRUE;
}
status = btif_in_execute_service_request(*((tBTA_SERVICE_ID*)p_param), b_enable);
if (status == BT_STATUS_SUCCESS)
{
bt_property_t property;
bt_uuid_t local_uuids[BT_MAX_NUM_UUIDS]; /* Now send the UUID_PROPERTY_CHANGED event to the upper layer */
BTIF_STORAGE_FILL_PROPERTY(&property, BT_PROPERTY_UUIDS,
sizeof(local_uuids), local_uuids);
btif_storage_get_adapter_property(&property);
HAL_CBACK(bt_hal_cbacks, adapter_properties_cb,
BT_STATUS_SUCCESS, , &property);//注意这里调用的是adapter_properties_cb,代表local的属性,不是remote devices的
}
return;
}

我们继续看btif_in_execute_service_request的实现:

bt_status_t btif_in_execute_service_request(tBTA_SERVICE_ID service_id,
BOOLEAN b_enable)
{
BTIF_TRACE_DEBUG("%s service_id: %d", __FUNCTION__, service_id);
/* Check the service_ID and invoke the profile's BT state changed API */
switch (service_id)
{
case BTA_HFP_SERVICE_ID:
case BTA_HSP_SERVICE_ID:
{
btif_hf_execute_service(b_enable);
}break;
case BTA_A2DP_SOURCE_SERVICE_ID:
{
btif_av_execute_service(b_enable);
}break;

我们继续看btif_av_execute_service:

/*******************************************************************************
**
** Function btif_av_execute_service
**
** Description Initializes/Shuts down the service
**
** Returns BT_STATUS_SUCCESS on success, BT_STATUS_FAIL otherwise
**
*******************************************************************************/
bt_status_t btif_av_execute_service(BOOLEAN b_enable)
{
if (b_enable)
{
/* TODO: Removed BTA_SEC_AUTHORIZE since the Java/App does not
* handle this request in order to allow incoming connections to succeed.
* We need to put this back once support for this is added */ /* Added BTA_AV_FEAT_NO_SCO_SSPD - this ensures that the BTA does not
* auto-suspend av streaming on AG events(SCO or Call). The suspend shall
* be initiated by the app/audioflinger layers */ BTA_AvEnable(BTA_SEC_AUTHENTICATE, BTA_AV_FEAT_RCTG|BTA_AV_FEAT_METADATA|BTA_AV_FEAT_VENDOR|BTA_AV_FEAT_NO_SCO_SSPD|BTA_AV_FEAT_RCCT|BTA_AV_FEAT_ADV_CTRL,bte_av_callback);//enable BTA_AvRegister(BTA_AV_CHNL_AUDIO, BTIF_AV_SERVICE_NAME, , bte_av_media_callback);//register
}
else {
BTA_AvDeregister(btif_av_cb.bta_handle);
BTA_AvDisable();
}
return BT_STATUS_SUCCESS;
}

上面的流程又可以分为两个部分:

  1. BTA_AvEnable
  2. BTA_AvRegister

我们下面分别来分析这两个部分:

BTA_AvEnable

void BTA_AvEnable(tBTA_SEC sec_mask, tBTA_AV_FEAT features, tBTA_AV_CBACK *p_cback)
{
tBTA_AV_API_ENABLE *p_buf; /* register with BTA system manager */
bta_sys_register(BTA_ID_AV, &bta_av_reg);//注册了BTA_ID_AV模块 if ((p_buf = (tBTA_AV_API_ENABLE *) GKI_getbuf(sizeof(tBTA_AV_API_ENABLE))) != NULL)
{
p_buf->hdr.event = BTA_AV_API_ENABLE_EVT;
p_buf->p_cback = p_cback;
p_buf->features = features;
p_buf->sec_mask = sec_mask;
bta_sys_sendmsg(p_buf);
}
}

这里依然是熟悉的 线程间通信,这里发出的第一个event 是BTA_AV_API_ENABLE_EVT,

AV nsm event=0x122b(API_ENABLE)

处理这个状态机的函数是bta_av_hdl_event,就是上面 刚刚注册到sys里面的:

看看其实现:

/*******************************************************************************
**
** Function bta_av_hdl_event
**
** Description Advanced audio/video main event handling function.
**
**
** Returns BOOLEAN
**
*******************************************************************************/
BOOLEAN bta_av_hdl_event(BT_HDR *p_msg)
{
UINT16 event = p_msg->event;
UINT16 first_event = BTA_AV_FIRST_NSM_EVT; if (event > BTA_AV_LAST_EVT)
{
return TRUE; /* to free p_msg */
} if(event >= first_event)//BTA_AV_FIRST_NSM_EVT,这里不会在状态机中处理
{
/* non state machine events */
(*bta_av_nsm_act[event - BTA_AV_FIRST_NSM_EVT]) ((tBTA_AV_DATA *) p_msg);
}
else if (event >= BTA_AV_FIRST_SM_EVT && event <= BTA_AV_LAST_SM_EVT)//handled by the AV main state machine
{
/* state machine events */
bta_av_sm_execute(&bta_av_cb, p_msg->event, (tBTA_AV_DATA *) p_msg);
}
else //这里处理by AV stream state machine
{
/* stream state machine events */
bta_av_ssm_execute( bta_av_hndl_to_scb(p_msg->layer_specific),
p_msg->event, (tBTA_AV_DATA *) p_msg);
}
return TRUE;
}

分析上面的bta_av_hdl_event 处理,发现其把event 分成了三类,

  1. 一种是在AV main state machine里面处理的,即调用bta_av_sm_execute 来处理
  2. 第二种是stream state machine 里面处理的,即调用bta_av_ssm_execute
  3. 第三种是不在状态机里面处理,即调用bta_av_nsm_act 里面的函数来处理

下面我们是各种情况对应的event:

bta_av_sm_execute处理如下的event:

 /* these events are handled by the AV main state machine */
BTA_AV_API_DISABLE_EVT = BTA_SYS_EVT_START(BTA_ID_AV),//0x1200
BTA_AV_API_REMOTE_CMD_EVT,
BTA_AV_API_VENDOR_CMD_EVT,
BTA_AV_API_VENDOR_RSP_EVT,
BTA_AV_API_META_RSP_EVT,
BTA_AV_API_RC_CLOSE_EVT,
BTA_AV_AVRC_OPEN_EVT,
BTA_AV_AVRC_MSG_EVT,
BTA_AV_AVRC_NONE_EVT,//0x1208

bta_av_ssm_execute 处理如下的event:

 /* these events are handled by the AV stream state machine */
BTA_AV_API_OPEN_EVT,//0x1209
BTA_AV_API_CLOSE_EVT,
BTA_AV_AP_START_EVT, //0x120b /* the following 2 events must be in the same order as the *API_*EVT */
BTA_AV_AP_STOP_EVT, /*其含义就是从API的相关的状态直接跳转到相应的状态机中执行*/
BTA_AV_API_RECONFIG_EVT,
BTA_AV_API_PROTECT_REQ_EVT,
BTA_AV_API_PROTECT_RSP_EVT,
BTA_AV_API_RC_OPEN_EVT,
BTA_AV_SRC_DATA_READY_EVT,
BTA_AV_CI_SETCONFIG_OK_EVT,
BTA_AV_CI_SETCONFIG_FAIL_EVT,
BTA_AV_SDP_DISC_OK_EVT,
BTA_AV_SDP_DISC_FAIL_EVT,
BTA_AV_STR_DISC_OK_EVT,
BTA_AV_STR_DISC_FAIL_EVT,
BTA_AV_STR_GETCAP_OK_EVT,
BTA_AV_STR_GETCAP_FAIL_EVT,
BTA_AV_STR_OPEN_OK_EVT,
BTA_AV_STR_OPEN_FAIL_EVT,
BTA_AV_STR_START_OK_EVT,
BTA_AV_STR_START_FAIL_EVT,
BTA_AV_STR_CLOSE_EVT,
BTA_AV_STR_CONFIG_IND_EVT,
BTA_AV_STR_SECURITY_IND_EVT,
BTA_AV_STR_SECURITY_CFM_EVT,
BTA_AV_STR_WRITE_CFM_EVT,
BTA_AV_STR_SUSPEND_CFM_EVT,
BTA_AV_STR_RECONFIG_CFM_EVT,
BTA_AV_AVRC_TIMER_EVT,
BTA_AV_AVDT_CONNECT_EVT,
BTA_AV_AVDT_DISCONNECT_EVT,
BTA_AV_ROLE_CHANGE_EVT,
BTA_AV_AVDT_DELAY_RPT_EVT,
BTA_AV_ACP_CONNECT_EVT,

bta_av_nsm_act 处理如下的event:

    /* these events are handled outside of the state machine */
BTA_AV_API_ENABLE_EVT,
BTA_AV_API_REGISTER_EVT,
BTA_AV_API_DEREGISTER_EVT,
BTA_AV_API_DISCONNECT_EVT,
BTA_AV_CI_SRC_DATA_READY_EVT,
BTA_AV_SIG_CHG_EVT,
BTA_AV_SIG_TIMER_EVT,
BTA_AV_SDP_AVRC_DISC_EVT,
BTA_AV_AVRC_CLOSE_EVT,
BTA_AV_CONN_CHG_EVT,
BTA_AV_DEREG_COMP_EVT,
#if (BTA_AV_SINK_INCLUDED == TRUE)
BTA_AV_API_SINK_ENABLE_EVT,
#endif
#if (AVDT_REPORTING == TRUE)
BTA_AV_AVDT_RPT_CONN_EVT,
#endif
BTA_AV_API_START_EVT, //0x1238

下面我们继续看BTA_AV_API_ENABLE_EVT的处理情况:

/*******************************************************************************
**
** Function bta_av_api_enable
**
** Description Handle an API enable event.
**
**
** Returns void
**
*******************************************************************************/
static void bta_av_api_enable(tBTA_AV_DATA *p_data)
{
int i;
tBTA_AV_ENABLE enable; /* initialize control block */
memset(&bta_av_cb, , sizeof(tBTA_AV_CB));//bta_av_cb的生命线从此刻开始 for(i=; i<BTA_AV_NUM_RCB; i++)
bta_av_cb.rcb[i].handle = BTA_AV_RC_HANDLE_NONE;//rcb avrcp control block bta_av_cb.rc_acp_handle = BTA_AV_RC_HANDLE_NONE; /* store parameters */
bta_av_cb.p_cback = p_data->api_enable.p_cback;//bte_av_callback
bta_av_cb.features = p_data->api_enable.features;
bta_av_cb.sec_mask = p_data->api_enable.sec_mask; enable.features = bta_av_cb.features; /* Register for SCO change event */
if (!(bta_av_cb.features & BTA_AV_FEAT_NO_SCO_SSPD))
{
bta_sys_sco_register(bta_av_sco_chg_cback);
} /* call callback with enable event */
(*bta_av_cb.p_cback)(BTA_AV_ENABLE_EVT, (tBTA_AV *)&enable);//调用回调,预示av enable 完成
}

回调函数处理的流程是 找对对应状态的handler 来处理event,当前的btif_av状态是idle对于BTA_AV_ENABLE_EVT  没有处理,这里就不分析了。

下面我们看看

BTA_AvRegister

/*******************************************************************************
**
** Function BTA_AvRegister
**
** Description Register the audio or video service to stack. When the
** operation is complete the callback function will be
** called with a BTA_AV_REGISTER_EVT. This function must
** be called before AVDT stream is open.
**
**
** Returns void
**
*******************************************************************************/
void BTA_AvRegister(tBTA_AV_CHNL chnl, const char *p_service_name, UINT8 app_id, tBTA_AV_DATA_CBACK *p_data_cback)
{
tBTA_AV_API_REG *p_buf; if ((p_buf = (tBTA_AV_API_REG *) GKI_getbuf(sizeof(tBTA_AV_API_REG))) != NULL)
{
p_buf->hdr.layer_specific = chnl;
p_buf->hdr.event = BTA_AV_API_REGISTER_EVT;
if(p_service_name)
{
BCM_STRNCPY_S(p_buf->p_service_name, sizeof(p_buf->p_service_name), p_service_name, BTA_SERVICE_NAME_LEN);
p_buf->p_service_name[BTA_SERVICE_NAME_LEN-] = ;//保存名字
}
else
{
p_buf->p_service_name[] = ;
}
p_buf->app_id = app_id;
p_buf->p_app_data_cback = p_data_cback;//保存callback
bta_sys_sendmsg(p_buf);
}
}

这里还是熟悉的线程间通信,由上面的分析得知,这个事件是由bta_av_nsm_act  来处理:

我们看看具体的实现,这个函数非常的长,里面主要涉及 结构的初始化,以及将结构注册到AVDTP,

/*******************************************************************************
**
** Function bta_av_api_register
**
** Description allocate stream control block,
** register the service to stack
** create SDP record
**
** Returns void
**
*******************************************************************************/
static void bta_av_api_register(tBTA_AV_DATA *p_data)
{
tBTA_AV_REGISTER registr;
tBTA_AV_SCB *p_scb; /* stream control block */
tAVDT_REG reg;
tAVDT_CS cs;
char *p_service_name;
tBTA_AV_CODEC codec_type;
tBTA_UTL_COD cod;
UINT8 index = ;
char p_avk_service_name[BTA_SERVICE_NAME_LEN+];
BCM_STRNCPY_S(p_avk_service_name, sizeof(p_avk_service_name), BTIF_AVK_SERVICE_NAME, BTA_SERVICE_NAME_LEN);//sink service name memset(&cs,,sizeof(tAVDT_CS));//初始化cs,该项最终拷贝到p_scb->cfg,并且AVDT_CreateStream 会使用到 registr.status = BTA_AV_FAIL_RESOURCES;//register 记录注册的信息,回调的时候会上报
registr.app_id = p_data->api_reg.app_id;//
registr.chnl = (tBTA_AV_CHNL)p_data->hdr.layer_specific;//audio 0x40
do //do...while(0)结构
{
p_scb = bta_av_alloc_scb(registr.chnl);//分配了一个 stream control block :bta_av_cb.p_scb[xx] = p_scb registr.hndl = p_scb->hndl;//0x41 or 0x42 --- hndl = (tBTA_AV_HNDL)((xx + 1) | chnl);
p_scb->app_id = registr.app_id;// /* initialize the stream control block */
p_scb->timer.p_cback = (TIMER_CBACK*)&bta_av_timer_cback;
registr.status = BTA_AV_SUCCESS; if((bta_av_cb.reg_audio + bta_av_cb.reg_video) == )//开始没有注册等于0
{
/* the first channel registered. register to AVDTP */
reg.ctrl_mtu = p_bta_av_cfg->sig_mtu;
reg.ret_tout = BTA_AV_RET_TOUT;
reg.sig_tout = BTA_AV_SIG_TOUT;
reg.idle_tout = BTA_AV_IDLE_TOUT;
reg.sec_mask = bta_av_cb.sec_mask;
#if( defined BTA_AR_INCLUDED ) && (BTA_AR_INCLUDED == TRUE)
bta_ar_reg_avdt(&reg, bta_av_conn_cback, BTA_ID_AV);//AR module registration to AVDT.
#endif
bta_sys_role_chg_register(&bta_av_sys_rs_cback); /* create remote control TG service if required */
if (bta_av_cb.features & (BTA_AV_FEAT_RCTG))
{
/* register with no authorization; let AVDTP use authorization instead */
#if( defined BTA_AR_INCLUDED ) && (BTA_AR_INCLUDED == TRUE)
bta_ar_reg_avct(p_bta_av_cfg->avrc_mtu, p_bta_av_cfg->avrc_br_mtu,
(UINT8)(bta_av_cb.sec_mask & (~BTA_SEC_AUTHORIZE)), BTA_ID_AV);//注册到AVCT
#endif bta_ar_reg_avrc(UUID_SERVCLASS_AV_REM_CTRL_TARGET, "AV Remote Control Target", NULL,
p_bta_av_cfg->avrc_tg_cat, BTA_ID_AV);//给AVRCP注册一个sdp record
#endif
} /* Set the Capturing service class bit */ cod.service = BTM_COD_SERVICE_CAPTURING;
utl_set_device_class(&cod, BTA_UTL_SET_COD_SERVICE_CLASS);
} /* if 1st channel */ /* get stream configuration and create stream */
/* memset(&cs.cfg,0,sizeof(tAVDT_CFG)); */
cs.cfg.num_codec = ;
cs.tsep = AVDT_TSEP_SRC;//src端 /*
* memset of cs takes care setting call back pointers to null.
cs.p_data_cback = NULL;
cs.p_report_cback = NULL;
*/
cs.nsc_mask = AVDT_NSC_RECONFIG |
((bta_av_cb.features & BTA_AV_FEAT_PROTECT) ? : AVDT_NSC_SECURITY);
APPL_TRACE_DEBUG("nsc_mask: 0x%x", cs.nsc_mask); p_service_name = p_data->api_reg.p_service_name; p_scb->suspend_sup = TRUE;
p_scb->recfg_sup = TRUE; cs.p_ctrl_cback = bta_av_dt_cback[p_scb->hdi];//根据不同的index 会调用不同bta_av_streamX_cback
if(registr.chnl == BTA_AV_CHNL_AUDIO)
{
/* set up the audio stream control block */
p_scb->p_act_tbl = (const tBTA_AV_ACT *)bta_av_a2d_action;//保存action table
p_scb->p_cos = &bta_av_a2d_cos;
p_scb->media_type= AVDT_MEDIA_AUDIO;
cs.cfg.psc_mask = AVDT_PSC_TRANS;
cs.media_type = AVDT_MEDIA_AUDIO;
cs.mtu = p_bta_av_cfg->audio_mtu;
cs.flush_to = L2CAP_DEFAULT_FLUSH_TO; ...
/* keep the configuration in the stream control block */
memcpy(&p_scb->cfg, &cs.cfg, sizeof(tAVDT_CFG));
while(index < BTA_AV_MAX_SEPS &&
(*bta_av_a2d_cos.init)(&codec_type, cs.cfg.codec_info,
&cs.cfg.num_protect, cs.cfg.protect_info, index) == TRUE)//bta_av_co_audio_init to to initialize audio paths
{ if(AVDT_CreateStream(&p_scb->seps[index].av_handle, &cs) == AVDT_SUCCESS)//Create a stream endpoint.
{
p_scb->seps[index].codec_type = codec_type;
index++;
}
} if(!bta_av_cb.reg_audio)//为profile 创建相关的sdp record
{
/* create the SDP records on the 1st audio channel */
bta_av_cb.sdp_a2d_handle = SDP_CreateRecord();
A2D_AddRecord(UUID_SERVCLASS_AUDIO_SOURCE, p_service_name, NULL,
A2D_SUPF_PLAYER, bta_av_cb.sdp_a2d_handle);
bta_sys_add_uuid(UUID_SERVCLASS_AUDIO_SOURCE); /* start listening when A2DP is registered */
if (bta_av_cb.features & BTA_AV_FEAT_RCTG)
bta_av_rc_create(&bta_av_cb, AVCT_ACP, , BTA_AV_NUM_LINKS + ); /* if the AV and AVK are both supported, it cannot support the CT role */
if (bta_av_cb.features & (BTA_AV_FEAT_RCCT))
{
...
#if( defined BTA_AR_INCLUDED ) && (BTA_AR_INCLUDED == TRUE)
/* create an SDP record as AVRC CT. */
bta_ar_reg_avrc(UUID_SERVCLASS_AV_REMOTE_CONTROL, NULL, NULL,
p_bta_av_cfg->avrc_ct_cat, BTA_ID_AV);
#endif
}
}
bta_av_cb.reg_audio |= BTA_AV_HNDL_TO_MSK(p_scb->hdi);
APPL_TRACE_DEBUG("reg_audio: 0x%x",bta_av_cb.reg_audio);
}
else
{
/*vedio*/
}
} while (); /* call callback with register event */
(*bta_av_cb.p_cback)(BTA_AV_REGISTER_EVT, (tBTA_AV *)&registr);//btif_av_cb.bta_handle = ((tBTA_AV*)p_data)->registr.hndl
}

上面代码的大部分已经 谢了注解,这里解释一下两点:

  1. bta_av_a2d_cos.init
  2. AVDT_CreateStream
  3. 关于AVDTP的注册bta_ar_reg_avdt(&reg, bta_av_conn_cback, BTA_ID_AV) (其实这个应该算第一点,因为只是一些注册性的内容,所以最后讲)

我们先看一下bta_av_a2d_cos.init的实现:

/*******************************************************************************
**
** Function bta_av_co_audio_init
**
** Description This callout function is executed by AV when it is
** started by calling BTA_AvRegister(). This function can be
** used by the phone to initialize audio paths or for other
** initialization purposes.
**
**
** Returns Stream codec and content protection capabilities info.
**
*******************************************************************************/
BOOLEAN bta_av_co_audio_init(UINT8 *p_codec_type, UINT8 *p_codec_info, UINT8 *p_num_protect,
UINT8 *p_protect_info, UINT8 index)
{
FUNC_TRACE(); APPL_TRACE_DEBUG("bta_av_co_audio_init: %d", index); /* By default - no content protection info */
*p_num_protect = ;
*p_protect_info = ; /* reset remote preference through setconfig */
bta_av_co_cb.codec_cfg_setconfig.id = BTIF_AV_CODEC_NONE; switch (index)
{
case BTIF_SV_AV_AA_SBC_INDEX: /* Set up for SBC codec for SRC*/
*p_codec_type = BTA_AV_CODEC_SBC; /* This should not fail because we are using constants for parameters */
A2D_BldSbcInfo(AVDT_MEDIA_AUDIO, (tA2D_SBC_CIE *) &bta_av_co_sbc_caps, p_codec_info);//初始化和audio相关的参数 /* Codec is valid */
return TRUE;
...
default:
/* Not valid */
return FALSE;
}
}

我们再看一下AVDT_CreateStream 的行为:

/*******************************************************************************
**
** Function AVDT_CreateStream
**
** Description Create a stream endpoint. After a stream endpoint is
** created an application can initiate a connection between
** this endpoint and an endpoint on a peer device. In
** addition, a peer device can discover, get the capabilities,
** and connect to this endpoint.
**
**
** Returns AVDT_SUCCESS if successful, otherwise error.
**
*******************************************************************************/
UINT16 AVDT_CreateStream(UINT8 *p_handle, tAVDT_CS *p_cs)
{
UINT16 result = AVDT_SUCCESS;
tAVDT_SCB *p_scb; /* Verify parameters; if invalid, return failure */
if (((p_cs->cfg.psc_mask & (~AVDT_PSC)) != ) || (p_cs->p_ctrl_cback == NULL))
{
result = AVDT_BAD_PARAMS;
}
/* Allocate scb; if no scbs, return failure */
else if ((p_scb = avdt_scb_alloc(p_cs)) == NULL)//分配avdt_cb.scb里面的节点
{
result = AVDT_NO_RESOURCES;
}
else
{
*p_handle = avdt_scb_to_hdl(p_scb);//分配的节点在avdt_cb.scb里面的位置+1 是handle的值-->p_scb->seps[index].av_handle
}
return result;
}

发现这个AVDT_CreateStream,本质上就是在AVDTP层创建stream control block 节点,并把这个节点用一个handle和bta_av_cb.p_scb 里面的节点 相关联。

最后我们来简单看一下

关于AVDTP的注册bta_ar_reg_avdt(&reg, bta_av_conn_cback, BTA_ID_AV)

这里无非就是一些注册性质的行为,我们简单分析一下:

/*******************************************************************************
**
** Function bta_ar_reg_avdt
**
** Description AR module registration to AVDT.
**
** Returns void
**
*******************************************************************************/
void bta_ar_reg_avdt(tAVDT_REG *p_reg, tAVDT_CTRL_CBACK *p_cback, tBTA_SYS_ID sys_id)
{
UINT8 mask = ; if (sys_id == BTA_ID_AV)
{
bta_ar_cb.p_av_conn_cback = p_cback;//保存回调bta_av_conn_cback,其作用暂时不管
mask = BTA_AR_AV_MASK;
}
else if (sys_id == BTA_ID_AVK)
{
bta_ar_cb.p_avk_conn_cback = p_cback;
mask = BTA_AR_AVK_MASK;
} if (mask)
{
if (bta_ar_cb.avdt_registered == )
{
AVDT_Register(p_reg, bta_ar_avdt_cback);//注册AVDTP
}
bta_ar_cb.avdt_registered |= mask;
}
}

我们继续分析 AVDTP_Register,

/*******************************************************************************
**
** Function AVDT_Register
**
** Description This is the system level registration function for the
** AVDTP protocol. This function initializes AVDTP and
** prepares the protocol stack for its use. This function
** must be called once by the system or platform using AVDTP
** before the other functions of the API an be used.
**
**
** Returns void
**
*******************************************************************************/
void AVDT_Register(tAVDT_REG *p_reg, tAVDT_CTRL_CBACK *p_cback)
{
/* register PSM with L2CAP */
L2CA_Register(AVDT_PSM, (tL2CAP_APPL_INFO *) &avdt_l2c_appl); /* set security level */
BTM_SetSecurityLevel(TRUE, "", BTM_SEC_SERVICE_AVDTP, p_reg->sec_mask,
AVDT_PSM, BTM_SEC_PROTO_AVDT, AVDT_CHAN_SIG);
BTM_SetSecurityLevel(FALSE, "", BTM_SEC_SERVICE_AVDTP, p_reg->sec_mask,
AVDT_PSM, BTM_SEC_PROTO_AVDT, AVDT_CHAN_SIG); /* do not use security on the media channel */
BTM_SetSecurityLevel(TRUE, "", BTM_SEC_SERVICE_AVDTP_NOSEC, BTM_SEC_NONE,
AVDT_PSM, BTM_SEC_PROTO_AVDT, AVDT_CHAN_MEDIA);
BTM_SetSecurityLevel(FALSE, "", BTM_SEC_SERVICE_AVDTP_NOSEC, BTM_SEC_NONE,
AVDT_PSM, BTM_SEC_PROTO_AVDT, AVDT_CHAN_MEDIA); #if AVDT_REPORTING == TRUE
/* do not use security on the reporting channel */
BTM_SetSecurityLevel(TRUE, "", BTM_SEC_SERVICE_AVDTP_NOSEC, BTM_SEC_NONE,
AVDT_PSM, BTM_SEC_PROTO_AVDT, AVDT_CHAN_REPORT);
BTM_SetSecurityLevel(FALSE, "", BTM_SEC_SERVICE_AVDTP_NOSEC, BTM_SEC_NONE,
AVDT_PSM, BTM_SEC_PROTO_AVDT, AVDT_CHAN_REPORT);
#endif /* initialize AVDTP data structures */
avdt_scb_init();
avdt_ccb_init();
avdt_ad_init(); /* copy registration struct */
memcpy(&avdt_cb.rcb, p_reg, sizeof(tAVDT_REG));//保存reg信息到avdt_cb.rcb里面
avdt_cb.p_conn_cback = p_cback;//保存回调bta_ar_avdt_cback,发现这个函数的实现就是调用bta_ar_cb.p_av_conn_cback
}

下面我们分析一下,上面的初始化AVDTP 数据结构的三个函数,我们先看看avdt_scb_init,从名字上面看,应该是初始化stream control block,

/*******************************************************************************
**
** Function avdt_scb_init
**
** Description Initialize stream control block module.
**
**
** Returns Nothing.
**
*******************************************************************************/
void avdt_scb_init(void)
{
memset(&avdt_cb.scb[], , sizeof(tAVDT_SCB) * AVDT_NUM_SEPS);
avdt_cb.p_scb_act = (tAVDT_SCB_ACTION *) avdt_scb_action;//保存action,这个是为了之后状态机中执行
}

继续看avdt_ccb_init, 从名字上面看,应该是初始化channel control block,

/*******************************************************************************
**
** Function avdt_ccb_init
**
** Description Initialize channel control block module.
**
**
** Returns Nothing.
**
*******************************************************************************/
void avdt_ccb_init(void)
{
memset(&avdt_cb.ccb[], , sizeof(tAVDT_CCB) * AVDT_NUM_LINKS);
avdt_cb.p_ccb_act = (tAVDT_CCB_ACTION *) avdt_ccb_action;//为了后续在channel 状态机中执行action 使用
}

最后,我们看看avdt_ad_init的实现:Initialize adaption layer

/*******************************************************************************
**
** Function avdt_ad_init
**
** Description Initialize adaption layer.
**
**
** Returns Nothing.
**
*******************************************************************************/
void avdt_ad_init(void)
{
int i;
tAVDT_TC_TBL *p_tbl = avdt_cb.ad.tc_tbl;
memset(&avdt_cb.ad, , sizeof(tAVDT_AD)); /* make sure the peer_mtu is a valid value */
for (i = ; i < AVDT_NUM_TC_TBL; i++, p_tbl++)
{
p_tbl->peer_mtu = L2CAP_DEFAULT_MTU;
}
}

关于a2dp的JNI以下的初始化的过程,这里补了一张图片:

好了,关于BTA_AvRegister 就讲到这里,a2dp的初始化的工作也已经完成了。


A2dp初始化流程源码分析的更多相关文章

  1. A2dp连接流程源码分析

    在上一篇文章中,我们已经分析了:a2dp初始化流程 这篇文章主要分析a2dp的连接流程,其中还是涉及到一些底层的profile以及protocol,SDP.AVDTP以及L2CAP等. 当蓝牙设备与主 ...

  2. A2dp sink 初始化流程源码分析

    A2dp sink的初始化流程和A2dp 的初始化流程,基本一样,这里做简单分析.这里分析的android的版本是Android O. 我们先从service的启动说起吧. 下面 是启动的时候的log ...

  3. a2dp播放流程源码分析

    之前分析了a2dp profile 的初始化的流程,这篇文章分析一下,音频流在bluedroid中的处理流程. 上层的音频接口是调用a2dp hal 里面的接口来进行命令以及数据的发送的. 关于控制通 ...

  4. [Android]从Launcher开始启动App流程源码分析

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5017056.html 从Launcher开始启动App流程源码 ...

  5. [Android]Android系统启动流程源码分析

    以下内容为原创,欢迎转载,转载请注明 来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5013863.html Android系统启动流程源码分析 首先 ...

  6. Spring加载流程源码分析03【refresh】

      前面两篇文章分析了super(this)和setConfigLocations(configLocations)的源代码,本文来分析下refresh的源码, Spring加载流程源码分析01[su ...

  7. Android笔记--View绘制流程源码分析(二)

    Android笔记--View绘制流程源码分析二 通过上一篇View绘制流程源码分析一可以知晓整个绘制流程之前,在activity启动过程中: Window的建立(activit.attach生成), ...

  8. Android笔记--View绘制流程源码分析(一)

    Android笔记--View绘制流程源码分析 View绘制之前框架流程分析 View绘制的分析始终是离不开Activity及其内部的Window的.在Activity的源码启动流程中,一并包含 着A ...

  9. Spark(五十一):Spark On YARN(Yarn-Cluster模式)启动流程源码分析(二)

    上篇<Spark(四十九):Spark On YARN启动流程源码分析(一)>我们讲到启动SparkContext初始化,ApplicationMaster启动资源中,讲解的内容明显不完整 ...

随机推荐

  1. Android-滑动解锁高亮文字自定义TextView

    public class HightLightTextView extends TextView { // 存储view的宽度 private int mTextViewWidth = 0; // 画 ...

  2. mybatis学习系列三(部分)

    1 forearch_oracle下批量保存(47) oracle批量插入 不支持values(),(),()方式 1.多个insert放在begin-end里面 begin insert into ...

  3. DirectX SDK (June 2010)安装错误S1023,解决方法

    转自:http://hi.baidu.com/rootcat/item/6730f15f85e2c1958c12ed81 DirectX SDK (June 2010)安装错误S1023,解决方法 导 ...

  4. [20170623]利用传输表空间恢复部分数据.txt

    [20170623]利用传输表空间恢复部分数据.txt --//昨天我测试使用传输表空间+dblink,上午补充测试发现表空间设置只读才能执行impdp导入原数据,这个也很好理解.--//这样的操作模 ...

  5. 浅谈C#依赖注入

    什么是依赖注入?不管是js中的一些前端框架还是,java,C#,php等中的一些后端开发框架中,都会涉及这个看着逼格略高的词语:依赖注入,越是看着好像很厉害的东西越是会让许多人学习产生恐惧,好像很厉害 ...

  6. apache配置CA证书通过https通信

    Apache Httpd 2.2 实现https加密通讯 实际生产中CA证书一般是向一些专业认证的国际机构来进行申请的.我们会模拟使用OpenSSL生成的证书,来实现Apache的安全加密通讯,这与实 ...

  7. tkinter内嵌Matplotlib系列(一)之解读官网教材

    目录 目录 前言 (一)小目标 1.首页卷面: 2.绘制一条函数曲线: 3.绘制多条曲线: (二)官方教材 1.对GUI框架的支持: 2.内嵌于tkinter的说明文档: (三)对官方教程的解读 目录 ...

  8. January 15th, 2018 Week 03rd Monday

    We got things to do. Places to go. People to see. Futures to make. 我们有很多事情要做,有很多地方要去,有很多人要见,有很多美好的未来 ...

  9. Python进阶(二)

    高阶函数 1.把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式. 2.Python内建了map( )和reduce( ) 函数 map()函数接收两个参数,一个是函数 ...

  10. 个人技术博客——linux服务器配置以及flask框架

    本次的软件工程实践,我负责我们组后台服务的搭建,我选用了bandwagon的服务器,安装的是Debian GNU/Linux,全程在root用户下操作,后端服务是用python的flask框架,数据库 ...