博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【Android应用开发】Android 蓝牙低功耗 (BLE) ( 第一篇 . 概述 . 蓝牙低功耗文档 翻译)...
阅读量:7061 次
发布时间:2019-06-28

本文共 16747 字,大约阅读时间需要 55 分钟。

转载请注明出处

參考 : 

-- 官方文档 : ;

1. 概述

BLE 概述

-- 版本号支持Android 4.3 (API Level 18) 内置框架引入了 蓝牙低功耗方案 (Bluetooth Low Energy, BLE) 支持; 

-- 角色支持 : Android 手机仅仅能作为 主设备 (central role), 开发人员开发的 APP 能够使用其提供的 API 接口, 用于 发现设备, 遍历服务 (services),  读写服务中的特性 (characteristics). 

-- 传统蓝牙对照 : 与传统的蓝牙对照, 蓝牙低功耗方案 (Bluetooth Low Energy) 是出于更低的电量消耗考虑而设计的. 这能够使 Android 应用能够与 BLE 设备进行交流, 这些设备须要非常低的电量, 如 近距离传感器, 心率測量设备, 健康设备 等等.

2. 关键术语 和 概念

(1) Generic Attribute Profile (GATT) 通用属性规范

Generic Attribute Profile (GATT) 通用属性规范 : 

-- GATT 作用 : GATT 规范是一个针对 在 BLE 连接上的, 发送 和 接收 少量数据的一个规范, 全部的现有的低功耗应用的规范都是基于这个 GATT 规范制定的.

-- 制定者 : 蓝牙技术联盟 (Bluetooth SIG) 为低功耗设备定义了很多规范, 一个 规范 (Profile) 就是 设备怎样在特定的应用中工作的详述. 

-- 设备规范相应关系 : 此外, 一个设备能够实现多个规范, 如 : 一个设备能够包括一个心率检測器, 和 电量检測器.

(2) Attribute Protocol (ATT) 属性协议

Attribute Protocol (ATT) 属性协议

-- ATT 与 GATT 关系 : GATT 规范是建立在 ATT 的上一层的, 这套改改通常被称为 GATT/ATT. 

-- ATT 作用 : ATT 被用于优化 BLE 设备的执行, 为了这个目的, ATT (属性协议) 使用尽可能少的字节. 

-- ATT 唯一标识 : ATT 中的每一个属性都被 一个 UUID (Universally Unique Identifier) 独一无二的进行标识, UUID 是一个 128 比特的标准的字符串 ID, 用于信息的唯一标识. 

-- ATT 属性 : ATT 中定义的属性就是 Charicteristics (特性) 和 Services (服务);

(3) Characteristic 特性

Characteristic 特性

-- Characteristic 概念 : 一个 Characteristic 特性包括了一个值 和 多个 Descriptor (描写叙述符) 用于描写叙述这个特性的值. 

-- 本质 : 一个特性能够被觉得是一个类型, 相似于一个类.

(4) Descriptor 描写叙述符

Descriptor 描写叙述符

-- 作用 : 描写叙述符 被定义为一些属性, 这些属性用于描写叙述 Characteristic (特性) 的值. 

-- 演示样例 : 比如, 一个 描写叙述符 能够说明一个 可读的描写叙述, 一个 特性值的可接受范围, 或者 一个特性值的測量单元.

(5) Service 服务

Service 服务

-- 服务本质 : 服务是 Characteristic (特性) 的集合. 

-- 演示样例 : 如, 你能够有一个 名称为 "Heart Rate Monitor (心率监控)" 的服务, 包括了特性 "Heart Rate Measurement (心率測量)". 

-- 參考资料 : 你能够在 bluetooth.org 官网查询到一个基于 GATT 服务 和 规范的列表.

3. 角色 和 职责

(1) 四种角色

Android 设备 与 BLE 设备互动时, 设备的角色 和 职责

-- 中心设备 和 外围设备 : 这个角色体系适用于 BLE 连接. 中心设备角色 能够扫描, 查找广播. 外围设备角色 发送广播.

-- GATT server 和 GATT client : 这个决定了两个设备之间, 一旦建议连接后, 怎样进行互相通信.

(2) 中心设备 和 外围设备

BLE 连接须要两种设备都存在 : 为了理解当中的差别, 想象一下 你有一个 Android 设备 和 一个激活的 智能腕表 蓝牙设备. 手机支持作为 中心设备 角色, 智能腕表 蓝牙设备支持作为外围设备角色, 为了建立 BLE 连接, 仅仅有外围设备 或者 仅仅有 中心设备 都不能建立 BLE 连接.

(3) GATT server 和 GATT client

GATT server 和 GATT client 简单介绍

-- GATT server 和 GATT client 角色不是固定的 : 一旦手机 和 智能腕表 设备建立了 BLE 连接, 它们開始互相交换 GATT 元数据. 依据它们之间传输的数据类型, 当中的一个会扮演 GATT server的角色. 

-- 角色改变演示样例 : 假设 智能腕表 设备想要向手机报告传感器数据, 那么智能腕表必须当做 GATT server. 假设智能腕表 想要从手机上接受更新数据, 那么 Android 手机就是 GATT server.

-- 手机 和 设备 都能够作为 GATT server 和 client : 在本文档中使用的演示样例代码, 在 Android 设备上执行的 Android APP 就是 GATT client, BLE 外围设备 就是 GATT server. Android APP 从 GATT server上获取数据, server的 BLE "heart rate monitor (心率监測)" 支持 "Heart Rate Profile (心率规范 - 一种 BLE 蓝牙标准规范)". Android APP 也能够作为 GATT server;

4. BLE 权限

(1) 蓝牙权限简单介绍

Android 蓝牙权限简单介绍

-- 权限作用 : 为了在应用中使用蓝牙功能, 必须在 AndroidManifest.xml 中 声明蓝牙权限. 全部的蓝牙通信操作都须要 蓝牙权限 来同意执行, 比如 搜索蓝牙, 蓝牙连接, 数据交互等操作.

-- 搜索设置蓝牙权限 : 假设 APP 要发起设备搜索 或者 管理 蓝牙设置, 须要 提前声明 BLUETOOTH_ADMIN 权限. 

-- 注意 : 使用 BLUETOOTH_ADMIN 权限的前提是 必须声明 BLUETOOTH 权限.

(2) 蓝牙权限简单介绍

蓝牙权限演示样例

-- AndroidManifest.xml 声明蓝牙权限演示样例

-- 充当 BLE 设备权限 : 假设你的 APP 仅仅须要胜任 BLE 设备的工作, 仅仅须要例如以下配置 : 

(3) 动态控制 BLE 功能是否使用

动态控制 BLE 是否可用 : 无论怎样, 假设你想要让你的 APP 能够当做 BLE 设备, 可是手机不支持这个操作, 你仍然能够进行例如以下配置, 仅仅是将当中的 android:required 设置成 false. 此时在执行时, 你能够使用 "PackageManager.hasSystemFeature()" 方法决定 BLE 是否可用.

//使用以下的函数决定 设备上的 BLE 功能 是否可用//此时你能够选择性的关闭 BLE 相关的功能if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {    Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();    finish();}

5. 创建 BLE

(1) 创建 BLE 简单介绍

创建 BLE 简单介绍

-- 验证 BLE 功能 : 在应用能够通过 BLE 交互之前, 你须要验证设备是否支持 BLE 功能, 假设支持, 确定它是能够使用的. 

-- 注意 : 这个检查仅仅有在 以下的配置 设置为 false 时才是必须的;

-- 不支持 BLE 关闭相关功能 : 假设 Android 手机不支持 BLE 功能, 你应该优雅的 关闭 BLE 相关功能. 

-- 支持 BLE 打开蓝牙 : 假设 BLE 支持 BLE 功能, 可是设备的蓝牙是关闭的, 你能够在应用中请求打开设备的蓝牙模块. 

-- 步骤总结 : 创建 BLE 蓝牙的过程分成两个步骤, 1. 获取 BluetoothAdapter, 2. 打开 设备的蓝牙模块.

(2) 获取 BluetoothAdapter (蓝牙适配器)

获取 BluetoothAdapter 蓝牙适配器

-- BluetoothAdapter 类作用 : 全部的蓝牙活动都须要 BluetoothAdapter, BluetoothAdapter 代表了设备本身的蓝牙适配器 (蓝牙无线设备). 整个系统中仅仅有一个 蓝牙适配器, 应用能够使用 BluetoothAdapter 对象与 蓝牙适配器硬件进行交互. 

-- 获取 BluetoothAdapter 代码演示样例

// 初始化蓝牙适配器final BluetoothManager bluetoothManager =        (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);mBluetoothAdapter = bluetoothManager.getAdapter();

-- 注意 : 这种方法使用了 getSystemService() 方法, 返回了一个 BluetoothManager 实例对象, 从 BluetoothManager 实例对象中能够获取 BluetoothAdapter 对象;

(3) 打开蓝牙功能

打开蓝牙

-- 检查是否可用 : 为了保证 蓝牙功能是打开的, 调用 BluetoothAdapter 的 isEnable() 方法, 检查蓝牙在当前是否可用. 假设返回 false, 说明当前蓝牙不可用. 

-- 演示样例代码

private BluetoothAdapter mBluetoothAdapter;...// 确认当前设备的蓝牙是否可用, // 假设不可用, 弹出一个对话框, 请求打开设备的蓝牙模块if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {    Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);    startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);}

6. 查找 BLE 设备

(1) 查找全部的 BLE 设备

查找 BLE 设备

-- 查找方法參数 : 为了搜索到 BLE 设备, 调用 BluetoothAdapter 的 startLeScan() 方法, 该方法须要一个 BluetoothAdapter.LeScanCallback 类型的參数. 你必须实现这个 LeScanCallback 接口, 由于 BLE 蓝牙设备扫描结果在这个接口中返回. 

-- 查找策略 : 蓝牙搜索是非常耗电的, 你须要遵守以下的 中断策略 和 不循环策略.

-- 中断策略 : 仅仅要一发现蓝牙设备, 立即中断扫描.

-- 不循环策略 : 不要循环扫描, 设置一个扫描的最大时间限制. 一个设备在之前可用, 继续扫描可能会使设备不可用, 此外继续扫描会持续浪费电池电量.

-- 源代码演示样例

/** * 搜索 和 展示 可用的蓝牙设备 的 Activity 界面 */public class DeviceScanActivity extends ListActivity {    private BluetoothAdapter mBluetoothAdapter;    private boolean mScanning;    private Handler mHandler;    // 10 秒后停止搜索    private static final long SCAN_PERIOD = 10000;    ...    private void scanLeDevice(final boolean enable) {        if (enable) {            // 在一个预先定义的时间段后停止扫描.            mHandler.postDelayed(new Runnable() {                @Override                public void run() {                    mScanning = false;					//開始扫描                    mBluetoothAdapter.stopLeScan(mLeScanCallback);                }            }, SCAN_PERIOD);            mScanning = true;            mBluetoothAdapter.startLeScan(mLeScanCallback);        } else {            mScanning = false;            mBluetoothAdapter.stopLeScan(mLeScanCallback);        }        ...    }...}

(2) 查找特定 BLE 设备

查找特定 BLE 设备

-- 方法调用 : 查找特定类型的外围设备, 能够调用以下的方法, 这种方法须要提供一个 UUID 对象数组, 这个 UUID 数组是 APP 支持的 GATT 服务的特殊标识.

-- 演示样例

startLeScan(UUID[], BluetoothAdapter.LeScanCallback)

(3) BluetoothAdapter.LeScanCallback 回调接口

扫描回调接口

-- 接口作用 : BluetoothAdapter.LeScanCallback 实现类, 在这个实现类的接口中返回 BLE 设备扫描结果;

-- 源代码演示样例

private LeDeviceListAdapter mLeDeviceListAdapter;...// 设备扫描回调接口private BluetoothAdapter.LeScanCallback mLeScanCallback =        new BluetoothAdapter.LeScanCallback() {    @Override    public void onLeScan(final BluetoothDevice device, int rssi,            byte[] scanRecord) {        runOnUiThread(new Runnable() {           @Override           public void run() {               mLeDeviceListAdapter.addDevice(device);               mLeDeviceListAdapter.notifyDataSetChanged();           }       });   }};

(4) 设备扫描类型

设备扫描类型 : 蓝牙设备扫描 在同一个时间扫描时, 仅仅能扫描 BLE 设备 或者 SPP 设备中的一种, 不能同一时候扫描两种设备.

7. 连接到 GATT 服务

(1) 连接指定 BluetoothDevice 蓝牙设备

连接指定设备

-- 连接到 GATT 服务 : 与 BLE 设备交互的第一步是 连接到 BLE 设备中的 GATT 服务. 

-- 实现方法 : 调用 BluetoothDevice 的 connectGatt() 方法能够连接到 BLE 设备的 GATT 服务. 

-- 參数解析 : connectGatt() 方法须要三个參数, 參数一 Context 上下文对象, 參数二 boolean autoConnect 是否自己主动连接扫描到的蓝牙设备, 參数三 BluetoothGattCallback 接口实现类. 

-- 使用方法演示样例

mBluetoothGatt = device.connectGatt(this, false, mGattCallback);

-- 获取 BluetoothGatt 对象 : 调用 connectGatt() 方法能够连接到 BLE 设备上的 GATT 服务, 返回一个 BluetoothGatt 实例对象, 你能够使用这个对象去 管理 GATT client操作. 

-- GATT client操作 : Android APP 能够调用 GATT Client (client). BluetoothGattCallback 能够用于传递结果到 GATT client, 如 连接状态 和 更进一步的 GATT Client 操作.

(2) GATT 数据交互演示样例

BLE 蓝牙数据交互

-- 界面 : 在以下的演示样例中, BLE 应用提供了一个 Activity 界面, 该 Activity 界面用于 连接, 展示数据, 展示 GATT 服务 和 设备支持的特性. 

-- BLE 蓝牙服务类 : 基于用户的输入, 这个 Activity 界面能够与一个 BluetoothLeService 的服务进行交流, 该交流的本质就是 BLE 设备的 GATT 服务 与 Android 的 BLE API 进行交流.

-- BLE 蓝牙服务类 演示样例代码

// BLE 设备能够通过该服务 与 Android 的 BLE API 进行互动public class BluetoothLeService extends Service {    private final static String TAG = BluetoothLeService.class.getSimpleName();    private BluetoothManager mBluetoothManager;    private BluetoothAdapter mBluetoothAdapter;    private String mBluetoothDeviceAddress;    private BluetoothGatt mBluetoothGatt;    private int mConnectionState = STATE_DISCONNECTED;    private static final int STATE_DISCONNECTED = 0;    private static final int STATE_CONNECTING = 1;    private static final int STATE_CONNECTED = 2;    public final static String ACTION_GATT_CONNECTED =            "com.example.bluetooth.le.ACTION_GATT_CONNECTED";    public final static String ACTION_GATT_DISCONNECTED =            "com.example.bluetooth.le.ACTION_GATT_DISCONNECTED";    public final static String ACTION_GATT_SERVICES_DISCOVERED =            "com.example.bluetooth.le.ACTION_GATT_SERVICES_DISCOVERED";    public final static String ACTION_DATA_AVAILABLE =            "com.example.bluetooth.le.ACTION_DATA_AVAILABLE";    public final static String EXTRA_DATA =            "com.example.bluetooth.le.EXTRA_DATA";    public final static UUID UUID_HEART_RATE_MEASUREMENT =            UUID.fromString(SampleGattAttributes.HEART_RATE_MEASUREMENT);    // BLE API 中定义的不同的回调方法.    private final BluetoothGattCallback mGattCallback =            new BluetoothGattCallback() {        @Override		// BLE 设备的状态改变 连接 断开        public void onConnectionStateChange(BluetoothGatt gatt, int status,                int newState) {            String intentAction;            if (newState == BluetoothProfile.STATE_CONNECTED) {                intentAction = ACTION_GATT_CONNECTED;                mConnectionState = STATE_CONNECTED;                broadcastUpdate(intentAction);                Log.i(TAG, "连接到了 GATT 服务.");                Log.i(TAG, "尝试搜索服务:" +                        mBluetoothGatt.discoverServices());            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {                intentAction = ACTION_GATT_DISCONNECTED;                mConnectionState = STATE_DISCONNECTED;                Log.i(TAG, "于 GATT 服务断开连接.");                broadcastUpdate(intentAction);            }        }        @Override        // BLE 设备中 新的 GATT 服务被发现        public void onServicesDiscovered(BluetoothGatt gatt, int status) {            if (status == BluetoothGatt.GATT_SUCCESS) {                broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);            } else {                Log.w(TAG, "发现 GATT 服务 : " + status);            }        }        @Override        // 特性读取操作返回的数据        public void onCharacteristicRead(BluetoothGatt gatt,                BluetoothGattCharacteristic characteristic,                int status) {            if (status == BluetoothGatt.GATT_SUCCESS) {                broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);            }        }     ...    };...}

-- 广播发送 : 当一个特定的回调被触发, 它调用适当的 broadcastUpdate() 帮助方法, 将其当做一个 Action 操作传递出去. 

-- 注意蓝牙心率 : 这部分的数据解析 与 蓝牙心率測量 是一起被执行的.

-- 广播发送 演示样例代码

private void broadcastUpdate(final String action) {    final Intent intent = new Intent(action);    sendBroadcast(intent);}private void broadcastUpdate(final String action,                             final BluetoothGattCharacteristic characteristic) {    final Intent intent = new Intent(action);    // This is special handling for the Heart Rate Measurement profile. Data    // parsing is carried out as per profile specifications.	// 心率监測规范的特殊处理	// 数据解析在每一个规范中完毕    if (UUID_HEART_RATE_MEASUREMENT.equals(characteristic.getUuid())) {        int flag = characteristic.getProperties();        int format = -1;        if ((flag & 0x01) != 0) {            format = BluetoothGattCharacteristic.FORMAT_UINT16;            Log.d(TAG, "心率格式 UINT16.");        } else {            format = BluetoothGattCharacteristic.FORMAT_UINT8;            Log.d(TAG, "心率格式 UINT8.");        }        final int heartRate = characteristic.getIntValue(format, 1);        Log.d(TAG, String.format("接收到心跳检測 : %d", heartRate));        intent.putExtra(EXTRA_DATA, String.valueOf(heartRate));    } else {		// 对于其他的规范, 写出 HEX 十六进制格式的数据        final byte[] data = characteristic.getValue();        if (data != null && data.length > 0) {            final StringBuilder stringBuilder = new StringBuilder(data.length);            for(byte byteChar : data)                stringBuilder.append(String.format("%02X ", byteChar));            intent.putExtra(EXTRA_DATA, new String(data) + "\n" +                    stringBuilder.toString());        }    }    sendBroadcast(intent);}
--
处理广播事件 : 在 DeviceControlActivity 中处理广播事件, 演示样例代码 : 

// 处理 Service 发起的的不同事件// ACTION_GATT_CONNECTED: 连接到 GATT 服务.// ACTION_GATT_DISCONNECTED: 与 GATT 服务断开.// ACTION_GATT_SERVICES_DISCOVERED: 发现 GATT 服务.// ACTION_DATA_AVAILABLE: 从 BLE 设备中接收数据, 数据能够是 read 或者 notification 操作的结果.private final BroadcastReceiver mGattUpdateReceiver = new BroadcastReceiver() {    @Override    public void onReceive(Context context, Intent intent) {        final String action = intent.getAction();        if (BluetoothLeService.ACTION_GATT_CONNECTED.equals(action)) {            mConnected = true;            updateConnectionState(R.string.connected);            invalidateOptionsMenu();        } else if (BluetoothLeService.ACTION_GATT_DISCONNECTED.equals(action)) {            mConnected = false;            updateConnectionState(R.string.disconnected);            invalidateOptionsMenu();            clearUI();        } else if (BluetoothLeService.                ACTION_GATT_SERVICES_DISCOVERED.equals(action)) {            // 在用户界面 显示全部支持的服务 和 特性.             displayGattServices(mBluetoothLeService.getSupportedGattServices());        } else if (BluetoothLeService.ACTION_DATA_AVAILABLE.equals(action)) {            displayData(intent.getStringExtra(BluetoothLeService.EXTRA_DATA));        }    }};

8. 读取 BLE 属性

读写属性简单介绍

-- 读写属性前提 : Android 应用连接到了 设备中的 GATT 服务, 而且发现了 各种服务 (特性集合), 能够读写当中的属性. 

-- 读写属性代码演示样例 : 遍历服务 (特性集合) 和 特性, 将其展示在 UI 界面中.

public class DeviceControlActivity extends Activity {    ...    // 示范怎样通过其所支持的 GATT 遍历 服务 (Services) 和 特性 (Characteristics)    // 在这个演示样例中, 我们将查询出的数据填充到 UI 界面中的 ExpandableListView 中    private void displayGattServices(List
gattServices) { if (gattServices == null) return; String uuid = null; String unknownServiceString = getResources(). getString(R.string.unknown_service); String unknownCharaString = getResources(). getString(R.string.unknown_characteristic); ArrayList
> gattServiceData = new ArrayList
>(); ArrayList
>> gattCharacteristicData = new ArrayList
>>(); mGattCharacteristics = new ArrayList
>(); // 遍历 GATT 服务 for (BluetoothGattService gattService : gattServices) { HashMap
currentServiceData = new HashMap
(); uuid = gattService.getUuid().toString(); currentServiceData.put( LIST_NAME, SampleGattAttributes. lookup(uuid, unknownServiceString)); currentServiceData.put(LIST_UUID, uuid); gattServiceData.add(currentServiceData); ArrayList
> gattCharacteristicGroupData = new ArrayList
>(); // 获取服务中的特性集合 List
gattCharacteristics = gattService.getCharacteristics(); ArrayList
charas = new ArrayList
(); // 循环遍历特性集合 for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { charas.add(gattCharacteristic); HashMap
currentCharaData = new HashMap
(); uuid = gattCharacteristic.getUuid().toString(); currentCharaData.put( LIST_NAME, SampleGattAttributes.lookup(uuid, unknownCharaString)); currentCharaData.put(LIST_UUID, uuid); gattCharacteristicGroupData.add(currentCharaData); } mGattCharacteristics.add(charas); gattCharacteristicData.add(gattCharacteristicGroupData); } ... }...}

9. 接收 GATT 通知

GATT 通知简单介绍

--
特性改变通知 : 当 BLE 设备中的一些特殊的特性改变, 须要通知与之连接的 Android BLE 应用.

-- 代码演示样例 : 使用 setCharacteristicNotification() 方法为特性设置通知.

private BluetoothGatt mBluetoothGatt;BluetoothGattCharacteristic characteristic;boolean enabled;...// 设置是否监听某个特性改变mBluetoothGatt.setCharacteristicNotification(characteristic, enabled);...BluetoothGattDescriptor descriptor = characteristic.getDescriptor(        UUID.fromString(SampleGattAttributes.CLIENT_CHARACTERISTIC_CONFIG));descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);mBluetoothGatt.writeDescriptor(descriptor);
--
特性改变回调 : 一但特性开启了改变通知监听, 假设特性发生了改变, 就会回调 BluetoothGattCallback 接口中的 onCharacteristicChanged() 方法.

@Override// 特性通知public void onCharacteristicChanged(BluetoothGatt gatt,        BluetoothGattCharacteristic characteristic) {    broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);}

10. 关闭 APP 中的 BLE 连接

关闭 BLE 设备连接

-- 关闭方法 : 一旦结束了 BLE 设备的使用, 调用 BluetoothGatt 的 close() 方法, 关闭 BLE 连接, 释放相关的资源.

-- 关闭演示样例

public void close() {    if (mBluetoothGatt == null) {        return;    }    mBluetoothGatt.close();    mBluetoothGatt = null;}

转载请注明出处 

你可能感兴趣的文章
Akka笔记之消息传递
查看>>
带你实现开发者头条(一) 启动页实现
查看>>
Android Listview中Button按钮点击事件冲突解决办法
查看>>
【技术篇】SQL的四种连接-左外连接、右外连接、内连接、全连接
查看>>
可翻折的TextViewExpandableTextView
查看>>
【软件工程】1.软件工程概述
查看>>
IOS学习和总结KVO
查看>>
Java EE7和Maven工程入门(2)
查看>>
MVP 模式在 Android 中的使用
查看>>
《数据结构》—— 队列
查看>>
hadoop 3.0 集群部署,超详细-Ali0th
查看>>
MySQL数据库MyISAM存储引擎转为Innodb
查看>>
我的友情链接
查看>>
在OpenSUSE上配置Apache+MySQL+PHP(写给自己看的笔记)
查看>>
初识Nginx
查看>>
Android WebView 缓存处理
查看>>
我的友情链接
查看>>
Nmap网络扫描从入门到精通实战视频课程
查看>>
我的友情链接
查看>>
简单讲解加密技术
查看>>