android4.3 Bluetooth(le)分析之startLeScan分析
BluetoothAdapter.java中有low enery(le)的一些方法,android提供了这些方法,但源码中并未找到这些方法的调用之处。本文档主要分析这类方法的执行流程,来了解下le到底做了些什么。
本文主要就是分析下startLeScan方法(两个重载方法)。
- public boolean startLeScan(LeScanCallback callback) {
- return startLeScan(null, callback);
- }
- public boolean startLeScan(UUID[] serviceUuids, LeScanCallback callback) {
- if (DBG) Log.d(TAG, "startLeScan(): " + serviceUuids);
- synchronized(mLeScanClients) {
- if (mLeScanClients.containsKey(callback)) {
- if (DBG) Log.e(TAG, "LE Scan has already started");
- return false;
- }
- try {
- //获取BluetoothGattBinder类的实例,该类的定义在GattService.java中
- IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
- if (iGatt == null) {
- // BLE is not supported
- return false;
- }
- UUID uuid = UUID.randomUUID();
- GattCallbackWrapper wrapper = new GattCallbackWrapper(this, callback, serviceUuids);
- //重点分析该方法。作用是为本地设备进行注册,以及启动扫描
- //wrapper是GattCallbackWrapper类的对象。该类注册了一些Gatt协议的回调方法
- iGatt.registerClient(new ParcelUuid(uuid), wrapper);
- if (wrapper.scanStarted()) {
- mLeScanClients.put(callback, wrapper);
- return true;
- }
- } catch (RemoteException e) {
- Log.e(TAG,"",e);
- }
- }
- return false;
- }
下面来分析下iGatt.registerClient(new ParcelUuid(uuid), wrapper)方法,路径如下:(packages/apps/Bluetooth/src/com/android/bluetooth/gatt/GattService.java::BluetoothGattBinder)
- public void registerClient(ParcelUuid uuid, IBluetoothGattCallback callback) {
- GattService service = getService();
- if (service == null) return;
- service.registerClient(uuid.getUuid(), callback);
- }
接着会调用GattService服务的同名方法
- void registerClient(UUID uuid, IBluetoothGattCallback callback) {
- enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission");
- if (DBG) Log.d(TAG, "registerClient() - UUID=" + uuid);
- mClientMap.add(uuid, callback);
- gattClientRegisterAppNative(uuid.getLeastSignificantBits(),
- uuid.getMostSignificantBits());
- }
接下来会调用jni层com_android_bluetooth_gatt.cpp文件中的gattClientRegisterAppNative方法。
- static void gattClientRegisterAppNative(JNIEnv* env, jobject object,
- jlong app_uuid_lsb, jlong app_uuid_msb )
- {
- bt_uuid_t uuid;
- if (!sGattIf) return;
- set_uuid(uuid.uu, app_uuid_msb, app_uuid_lsb);
- sGattIf->client->register_client(&uuid);
- }
分析sGattIf->client->register_client(&uuid);语句
(1)sGattIf是一个静态变量,定义是static const btgatt_interface_t *sGattIf = NULL;
又是这种类型的变量。第一反应就是去找btgatt_interface_t结构体定义的头文件(一般在hardware目录),然后再搜索调用的c文件(一般在external/bluetooth/bluedroid,有时找到的c文件与头文件同名)。
btgatt_interface_t结构体的定义:hardware/libhardware/include/hardware/bt_gatt.h
- /** Represents the standard Bluetooth GATT interface. */
- typedef struct {
- /** Set to sizeof(btgatt_interface_t) */
- size_t size;
- /**
- * Initializes the interface and provides callback routines
- */
- bt_status_t (*init)( const btgatt_callbacks_t* callbacks );
- /** Closes the interface */
- void (*cleanup)( void );
- /** Pointer to the GATT client interface methods.*/
- const btgatt_client_interface_t* client;
- /** Pointer to the GATT server interface methods.*/
- const btgatt_server_interface_t* server;
- } btgatt_interface_t;
btgatt_interface_t结构体的对象:external/bluetooth/bluedroi/btif/src/btif_gatt.c
- static const btgatt_interface_t btgattInterface = {
- sizeof(btgattInterface),
- btif_gatt_init,
- btif_gatt_cleanup,
- &btgattClientInterface,
- &btgattServerInterface,
- };
回到sGattIf->client->register_client(&uuid);语句,它调用了sGattIf结构体对象中的client对象的register_client函数,那么就是btgattClientInterface对象的register_client函数。
由结构体的定义可知client对象的类型是btgatt_client_interface_t结构体。同理分析可得以下结果,
btgatt_client_interface_t结构体的定义:hardware/libhardware/include/hardware/ bt_gatt_client.h
- typedef struct {
- /** Registers a GATT client application with the stack */
- bt_status_t (*register_client)( bt_uuid_t *uuid );
- /** Unregister a client application from the stack */
- bt_status_t (*unregister_client)(int client_if );
- ......
- }
btgatt_client_interface_t结构体的对象:external/bluetooth/bluedroi/btif/src/btif_gatt_client.c
- const btgatt_client_interface_t btgattClientInterface = {
- btif_gattc_register_app,
- btif_gattc_unregister_app,
- btif_gattc_scan,
- ......
- };
因此client->register_client就是调用了btif_gattc_register_app方法[-->btif_gatt_client.c]。
- static bt_status_t btif_gattc_register_app(bt_uuid_t *uuid)
- {
- CHECK_BTGATT_INIT();
- btif_gattc_cb_t btif_cb;
- memcpy(&btif_cb.uuid, uuid, sizeof(bt_uuid_t));
- return btif_transfer_context(btgattc_handle_event, BTIF_GATTC_REGISTER_APP,
- (char*) &btif_cb, sizeof(btif_gattc_cb_t), NULL);
- }
分析btgattc_handle_event函数
- static void btgattc_handle_event(uint16_t event, char* p_param)
- {
- ......
- btif_gattc_cb_t* p_cb = (btif_gattc_cb_t*)p_param;
- if (!p_cb) return;
- switch (event)
- {
- case BTIF_GATTC_REGISTER_APP:
- btif_to_bta_uuid(&uuid, &p_cb->uuid);
- //为uuid注册回调函数
- BTA_GATTC_AppRegister(&uuid, bte_gattc_cback);
- break;
- .......
- }
- }
分析BTA_GATTC_AppRegister函数
-------------------------------------------------------------------------------------------------------
- void BTA_GATTC_AppRegister(tBT_UUID *p_app_uuid, tBTA_GATTC_CBACK *p_client_cb)
- {
- tBTA_GATTC_API_REG *p_buf;
- /* register with BTA system manager */
- GKI_sched_lock();
- //注册Gatt客户端主事件处理函数bta_gattc_hdl_event,在bta_gatt_reg结构体中定义。
- bta_sys_register(BTA_ID_GATTC, &bta_gatt_reg);
- GKI_sched_unlock();
- if ((p_buf = (tBTA_GATTC_API_REG *) GKI_getbuf(sizeof(tBTA_GATTC_API_REG))) != NULL)
- {
- p_buf->hdr.event = BTA_GATTC_API_REG_EVT;
- if (p_app_uuid != NULL)
- memcpy(&p_buf->app_uuid, p_app_uuid, sizeof(tBT_UUID));
- p_buf->p_cback = p_client_cb;
- bta_sys_sendmsg(p_buf);
- }
- return;
- }
(a)通过bta_sys_register函数注册了bta_gatt_reg结构体中定义的客户端主事件处理函数bta_gattc_hdl_event;然后设置event为BTA_GATTC_API_REG_EV,触发bta_gattc_hdl_event函数。
- BOOLEAN bta_gattc_hdl_event(BT_HDR *p_msg)
- {
- tBTA_GATTC_CB *p_cb = &bta_gattc_cb;
- tBTA_GATTC_CLCB *p_clcb = NULL;
- #if BTA_GATT_DEBUG == TRUE
- APPL_TRACE_DEBUG1("bta_gattc_hdl_event: Event [%s]", gattc_evt_code(p_msg->event));
- #endif
- switch (p_msg->event)
- {
- case BTA_GATTC_API_REG_EVT:
- bta_gattc_register(p_cb, (tBTA_GATTC_DATA *) p_msg);
- break;
- ......
- }
- }
(b)调用bta_gattc_register函数。该函数用来注册一个客户端Gatt应用程序。
- void bta_gattc_register(tBTA_GATTC_CB *p_cb, tBTA_GATTC_DATA *p_data)
- {
- ......
- /* callback with register event */
- if (p_data->api_reg.p_cback)
- {
- (*p_data->api_reg.p_cback)(BTA_GATTC_REG_EVT, (tBTA_GATTC *)&cb_data);
- }
- }
调用相关event(BTA_GATTC_REG_EVT)的回调函数。
到此,BTA_GATTC_AppRegister函数分析完毕,接下来分析BTA_GATTC_AppRegister(&uuid, bte_gattc_cback);中的参数部分。
ps:上述的回调函数就是这里的参数:bte_gattc_cback函数。那么BTA_GATTC_REG_EVT事件就调用该函数处理了。
-------------------------------------------------------------------------------------------------------
分析回调函数bte_gattc_cback
- static void bte_gattc_cback(tBTA_GATTC_EVT event, tBTA_GATTC *p_data)
- {
- bt_status_t status = btif_transfer_context(btif_gattc_upstreams_evt,
- (uint16_t) event, (void*)p_data, sizeof(tBTA_GATTC), NULL);
- ASSERTC(status == BT_STATUS_SUCCESS, "Context transfer failed!", status);
- }
分析btif_gattc_upstreams_evt函数,在该函数中会处理BTA_GATTC_REG_EVT事件。
- static void btif_gattc_upstreams_evt(uint16_t event, char* p_param)
- {
- tBTA_GATTC *p_data = (tBTA_GATTC*)p_param;
- switch (event)
- {
- case BTA_GATTC_REG_EVT:
- {
- bt_uuid_t app_uuid;
- bta_to_btif_uuid(&app_uuid, &p_data->reg_oper.app_uuid);
- HAL_CBACK(bt_gatt_callbacks, client->register_client_cb
- , p_data->reg_oper.status
- , p_data->reg_oper.client_if
- , &app_uuid
- );
- break;
- }
- ......
- }
- }
bt_gatt_callbacks对象的类型是btgatt_callbacks_t,其定义在hardware/libhardware/include/hardware/bt_gatt.h文件中。现在对bt_gatt_callbacks对象从头开始分析其来源。
在GattService.java::start()方法中,调用了initializeNative方法。继而调用JNI层initializeNative方法。贴出该方法。
- static const btgatt_interface_t *sGattIf = NULL;
- static const bt_interface_t* btIf;
- ......
- static void initializeNative(JNIEnv *env, jobject object) {
- /* getBluetoothInterface 函数返回sBluetoothInterface对象,在android4.3 bt 扫描分析.docx中已说明该对象的来源*/
- if ( (btIf = getBluetoothInterface()) == NULL) {
- error("Bluetooth module is not loaded");
- return;
- }
- ......
- //(a)
- // BT_PROFILE_GATT_ID的值是”gatt”
- if ( (sGattIf = (btgatt_interface_t *)
- btIf->get_profile_interface(BT_PROFILE_GATT_ID)) == NULL) {
- error("Failed to get Bluetooth GATT Interface");
- return;
- }
- bt_status_t status;
- //(b)
- /* sGattCallbacks的定义
- static const btgatt_callbacks_t sGattCallbacks = {
- sizeof(btgatt_callbacks_t),
- &sGattClientCallbacks,
- &sGattServerCallbacks
- };*/
- if ( (status = sGattIf->init(&sGattCallbacks)) != BT_STATUS_SUCCESS) {
- error("Failed to initialize Bluetooth GATT, status: %d", status);
- sGattIf = NULL;
- return;
- }
- mCallbacksObj = env->NewGlobalRef(object);
- }
(a) 分析
- static const void* get_profile_interface (const char *profile_id)
- {
- ......
- #if BTA_GATT_INCLUDED == TRUE
- if (is_profile(profile_id, BT_PROFILE_GATT_ID))
- return btif_gatt_get_interface();
- #endif
- return NULL;
- }
分析btif_gatt_get_interface函数
- const btgatt_interface_t *btif_gatt_get_interface()
- {
- return &btgattInterface;
- }
btgattInterface对象的类型是btgatt_interface_t结构体。再贴一遍该结构体的定义,如下:
- typedef struct {
- /** Set to sizeof(btgatt_interface_t) */
- size_t size;
- /**
- * Initializes the interface and provides callback routines
- */
- bt_status_t (*init)( const btgatt_callbacks_t* callbacks );
- /** Closes the interface */
- void (*cleanup)( void );
- /** Pointer to the GATT client interface methods.*/
- const btgatt_client_interface_t* client;
- /** Pointer to the GATT server interface methods.*/
- const btgatt_server_interface_t* server;
- } btgatt_interface_t;
另,btgattInterface对象定义如下:
- static const btgatt_interface_t btgattInterface = {
- sizeof(btgattInterface),
- btif_gatt_init,
- btif_gatt_cleanup,
- &btgattClientInterface,
- &btgattServerInterface,
- };
所以sGattIf 就是btgattInterface对象。
(b) 接下来调用sGattIf->init函数。由上可知,即为btif_gatt_init函数。
- static bt_status_t btif_gatt_init( const btgatt_callbacks_t* callbacks )
- {
- /*bt_gatt_callbacks由参数赋值,该参数是sGattCallbacks。
- sGattCallbacks的定义
- static const btgatt_callbacks_t sGattCallbacks = {
- sizeof(btgatt_callbacks_t),
- &sGattClientCallbacks,
- &sGattServerCallbacks
- };*/
- bt_gatt_callbacks = callbacks;
- BTA_GATTC_Init();
- BTA_GATTS_Init();
- return BT_STATUS_SUCCESS;
- }
到此为止,调用语句中bt_gatt_callbacks对象我们已经清楚了,就是sGattCallbacks对象。现在分析client->register_client_cb。
- HAL_CBACK(bt_gatt_callbacks, client->register_client_cb
- , p_data->reg_oper.status
- , p_data->reg_oper.client_if
- , &app_uuid
- );
client对象是在btgatt_callbacks_t结构体中定义的一个变量,其初始化是在bt_gatt_callbacks对象(即sGattCallbacks对象)中。
btgatt_callbacks_t结构体如下:
- typedef struct {
- /** Set to sizeof(btgatt_callbacks_t) */
- size_t size;
- /** GATT Client callbacks */
- const btgatt_client_callbacks_t* client;
- /** GATT Server callbacks */
- const btgatt_server_callbacks_t* server;
- } btgatt_callbacks_t;
因此client对应的就是sGattCallbacks对象中的sGattClientCallbacks对象。sGattClientCallbacks对象定义如下(在JNI层的com_android_bluetooth_gatt.cpp文件中定义):
- static const btgatt_client_callbacks_t sGattClientCallbacks = {
- btgattc_register_app_cb,
- btgattc_scan_result_cb,
- ......
- };
而sGattClientCallbacks对象的类型是btgatt_client_callbacks_t结构体,如下
- typedef struct {
- register_client_callback register_client_cb;
- scan_result_callback scan_result_cb;
- connect_callback open_cb;
- disconnect_callback close_cb;
- ......
- } btgatt_client_callbacks_t;
因此,client->register_client_cb就是调用了sGattClientCallbacks 对象中的btgattc_register_app_cb函数。
- void btgattc_register_app_cb(int status, int clientIf, bt_uuid_t *app_uuid)
- {
- CHECK_CALLBACK_ENV
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onClientRegistered, status,
- clientIf, UUID_PARAMS(app_uuid));
- checkAndClearExceptionFromCallback(sCallbackEnv, __FUNCTION__);
- }
JNI层的method_onClientRegistered 函数对应java层的onClientRegistered方法[-->GattService.java]。
- void onClientRegistered(int status, int clientIf, long uuidLsb, long uuidMsb)
- throws RemoteException {
- UUID uuid = new UUID(uuidMsb, uuidLsb);
- if (DBG) Log.d(TAG, "onClientRegistered() - UUID=" + uuid + ", clientIf=" + clientIf);
- ClientMap.App app = mClientMap.getByUuid(uuid);
- if (app != null) {
- app.id = clientIf;
- app.linkToDeath(new ClientDeathRecipient(clientIf));
- app.callback.onClientRegistered(status, clientIf);
- }
- }
此callback其实是GattCallbackWrapper类的对象。
分析mClientMap对象,在registerClient方法中调用了ClientMap的父类ContextMap::add方法,将GattCallbackWrapper类对象wrapper作为callback参数添加到mClientMap对象中。
接下来重新分析:
onClientRegistered方法[--->BluetoothAdapter::GattCallbackWrapper类]
- public void onClientRegistered(int status, int clientIf) {
- if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status +
- " clientIf=" + clientIf);
- synchronized(this) {
- if (mLeHandle == -1) {
- if (DBG) Log.d(TAG, "onClientRegistered LE scan canceled");
- }
- if (status == BluetoothGatt.GATT_SUCCESS) {
- mLeHandle = clientIf;
- IBluetoothGatt iGatt = null;
- try {
- BluetoothAdapter adapter = mBluetoothAdapter.get();
- if (adapter != null) {
- iGatt = adapter.getBluetoothManager().getBluetoothGatt();
- //调用startLeScan方法时,传递过来的参数为null,执行此处
- if (mScanFilter == null) {
- iGatt.startScan(mLeHandle, false);
- } else {
- ParcelUuid[] uuids = new ParcelUuid[mScanFilter.length];
- for(int i = 0; i != uuids.length; ++i) {
- uuids[i] = new ParcelUuid(mScanFilter[i]);
- }
- iGatt.startScanWithUuids(mLeHandle, false, uuids);
- }
- } else {
- Log.e(TAG, "onClientRegistered, BluetoothAdapter null");
- mLeHandle = -1;
- }
- } catch (RemoteException e) {
- Log.e(TAG, "fail to start le scan: " + e);
- mLeHandle = -1;
- }
- ......
- }
接下来分析startScan方法,在GattService.java中。
- void startScan(int appIf, boolean isServer) {
- ......
- if (getScanClient(appIf, isServer) == null) {
- if (DBG) Log.d(TAG, "startScan() - adding client=" + appIf);
- mScanQueue.add(new ScanClient(appIf, isServer));
- }
- gattClientScanNative(appIf, true);
- }
JNI层gattClientScanNative函数
- static void gattClientScanNative(JNIEnv* env, jobject object, jint clientIf, jboolean start)
- {
- if (!sGattIf) return;
- sGattIf->client->scan(clientIf, start);
- }
同之前分析register_client的步骤,分析的scan函数对应btif_gattc_scan函数。
- static bt_status_t btif_gattc_scan( int client_if, bool start )
- {
- CHECK_BTGATT_INIT();
- btif_gattc_cb_t btif_cb;
- btif_cb.client_if = (uint8_t) client_if;
- return btif_transfer_context(btgattc_handle_event, start ? BTIF_GATTC_SCAN_START : BTIF_GATTC_SCAN_STOP,
- (char*) &btif_cb, sizeof(btif_gattc_cb_t), NULL);
- }
在btgattc_handle_event函数中处理BTIF_GATTC_SCAN_START事件
- case BTIF_GATTC_SCAN_START:
- btif_gattc_init_dev_cb();
- // BTA_DmBleObserve发出消息,包含BTA_DM_API_BLE_OBSERVE_EVT事件
- BTA_DmBleObserve(TRUE, 0, bte_scan_results_cb);
- break;
调用bte_scan_results_cb函数,
- static void bte_scan_results_cb (tBTA_DM_SEARCH_EVT event, tBTA_DM_SEARCH *p_data)
- {
- btif_gattc_cb_t btif_cb;
- uint8_t len;
- switch (event)
- {
- case BTA_DM_INQ_RES_EVT:
- ......
- case BTA_DM_INQ_CMPL_EVT:
- ......
- btif_transfer_context(btif_gattc_upstreams_evt, BTIF_GATT_OBSERVE_EVT,
- (char*) &btif_cb, sizeof(btif_gattc_cb_t), NULL);
- }
在btif_gattc_upstreams_evt函数中处理BTIF_GATT_OBSERVE_EVT事件。
- case BTIF_GATT_OBSERVE_EVT:
- {
- btif_gattc_cb_t *p_btif_cb = (btif_gattc_cb_t*)p_param;
- if (!btif_gattc_find_bdaddr(p_btif_cb->bd_addr.address))
- {
- btif_gattc_add_remote_bdaddr(p_btif_cb->bd_addr.address, p_btif_cb->addr_type);
- btif_gattc_update_properties(p_btif_cb);
- }
- HAL_CBACK(bt_gatt_callbacks, client->scan_result_cb,
- &p_btif_cb->bd_addr, p_btif_cb->rssi, p_btif_cb->value);
- break;
- }
同分析register_client_cb函数,在JNI层com_android_bluetooth_gatt.cpp文件中定义,分析得scan_result_cb对应函数btgattc_scan_result_cb。
- void btgattc_scan_result_cb(bt_bdaddr_t* bda, int rssi, uint8_t* adv_data)
- {
- ......
- sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onScanResult
- , address, rssi, jb);
- ......
- }
对应java层文件GattService类onScanResult方法。
- void onScanResult(String address, int rssi, byte[] adv_data) {
- for (ScanClient client : mScanQueue) {
- ......
- if (!client.isServer) {
- ClientMap.App app = mClientMap.getById(client.appIf);
- if (app != null) {
- try {
- app.callback.onScanResult(address, rssi, adv_data);
- } catch (RemoteException e) {
- Log.e(TAG, "Exception: " + e);
- mClientMap.remove(client.appIf);
- mScanQueue.remove(client);
- }
- }
- }
- ......
- }
- }
callback为GattCallbackWrapper类的对象,因此调用GattCallbackWrapper类中的onScanResult方法。
- public void onScanResult(String address, int rssi, byte[] advData) {
- ......
- try {
- BluetoothAdapter adapter = mBluetoothAdapter.get();
- if (adapter == null) {
- Log.d(TAG, "onScanResult, BluetoothAdapter null");
- return;
- }
- mLeScanCb.onLeScan(adapter.getRemoteDevice(address), rssi, advData);
- } catch (Exception ex) {
- Log.w(TAG, "Unhandled exception: " + ex);
- }
- }
mLeScanCb对象为LeScanCallback接口的对象,不过源码中并没有类来实现该接口,故只能分析到这里了。扫描到此结束,over~~
-------------------------------------------------------------------------------------------------------------
贴出流程图,see see,5个步骤:
1.startLeScan(JAVA-->JNI)
-------------------------------------------------------------------------------------------------------------
2.startLeScan(蓝牙栈)
-------------------------------------------------------------------------------------------------------------
3.startLeScan(JNI-->JAVA)
-------------------------------------------------------------------------------------------------------------
4.startLeScan(蓝牙栈)
-------------------------------------------------------------------------------------------------------------
5.startLeScan(JNI-->JAVA)
-------------------------------------------------------------------------------------------------------------
android4.3 Bluetooth(le)分析之startLeScan分析的更多相关文章
- ZT Android4.2关于bluetooth在HAL层的分析(1)
我的电子杂烩饭 http://blog.sina.com.cn/wuchuchu2012 [订阅][手机订阅] 首页 博文目录 图片 关于我 正文 字体大小:大 中 小 Android4.2关于blu ...
- Bluetooth LE(低功耗蓝牙) - 第三部分
回顾 在本系列的前两篇文章中,我们已经了解了一些关于Bluetooth LE的背景并建立一个简单的Activity / Service框架. 在这篇文章中,我们将探讨Bluetooth LE的细节 ...
- 常用 Java 静态代码分析工具的分析与比较
常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...
- Bluetooth LE(低功耗蓝牙) - 第二部分
回顾 在前面的文章中我们介绍了Bluetooth LE的背景也说明了我们在本系列文章中将要开发什么,但是还没有实际的代码.我们将在这篇文章中纠正这一点,我们将通过定义 Service/Activity ...
- Bluetooth LE(低功耗蓝牙) - 第一部分
前言 在写这篇文章的时候,谷歌刚刚发布了Android Wear ,摩托罗拉也发布了 Moto 360 智能手表.Android Wear的API还是相当基本的,是很好的文档材料,而且还会不断的更新, ...
- 低功耗之战!ANT VS Bluetooth LE
利用近距离无线通信技术将手机及可穿戴式传感器终端等与智能电话连接起来,实现新的功能.最近,以此为目标的行动正在展开.其中备受关注的近距离无线方式是“ANT”和“Bluetooth LE”.为了在各种便 ...
- Memcached源代码分析 - Memcached源代码分析之消息回应(3)
文章列表: <Memcached源代码分析 - Memcached源代码分析之基于Libevent的网络模型(1)> <Memcached源代码分析 - Memcached源代码分析 ...
- mysql 分析3使用分析sql 性能 show profiles ;
show variables like '%profiling%'; 查看状态 查看时间去哪了``` set profiling=1;// 打开 show profiles; 查看执行过的 ...
- [转载] 常用 Java 静态代码分析工具的分析与比较
转载自http://www.oschina.net/question/129540_23043 简介: 本文首先介绍了静态代码分析的基本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代 ...
随机推荐
- DELPHI高性能大容量SOCKET并发(九):稳定性问题解决
http://blog.csdn.net/sqldebug_fan/article/details/9043699
- my first article
BLOG: http://codetask.org GIT: http://git.oschina.net/codetimer
- CSS :hover伪类选择定义和用法
伪类选择符E:hover的定义和用法: 设置元素在其鼠标悬停时的样式.E元素可以通过其他选择器进行选择,比如使用类选择符.id选择符.类型选择符等等.特别说明:IE6并非不支持此选择符,但能够支持a元 ...
- 用自己的ID在appstore中更新app-黑苹果之路
由于之前套用了别人的镜像,在appstore中更新XCode时总要输别人id的密码,id还不能改.网上有的说要把XCode删掉,然后再用自己的ID更新,找到另外一个方法,更简单: 1.打开引用程序目录 ...
- 【练习】flashback基于scn的闪回查询
1.创建表dept1: :: SCOTT@ORA11GR2>create table dept1 as select * from dept; Table created. :: SCOTT@O ...
- Python从题目中学习:random() module
最近在给公司培训Python,布置了一道题: ----------------------------------------------------------------------------- ...
- 【T电商 1】Nginx服务器搭建
在项目中,首先是需要Nginx服务器作为一个图片服务器来使用.那么,久涉及到服务器的搭建.这次服务器的搭建,主要是在三个环境上进行了学习:CentOS6.2,CentOS7,和Ubuntu16.那么本 ...
- LINUX下查看负载
1,查看磁盘 df -h 2,查看内存大小 free free [-m|g]按MB,GB显示内存 vmstat 3,查看cpu cat /proc/cpuinfo 只看cpu数量grep " ...
- golang 格式化时间为字符串
package main import ( "fmt" "reflect" "time" ) func main() { //格式化字符串为 ...
- android前端开发 布局学习
元素背景设置 -------------------------------- Android中shape中的属性大全 http://www.oschina.net/question/166763_3 ...