目录

  • 前言
  • BLE是个什么鬼
  • BLE中的角色分工
  • 主要的关键词和概念
    • GATT(Generic Attribute Profile )
    • Characteristic
    • Service
  • Android如何使用BLE
    • 蓝牙权限
    • APP和BLE外设交互流程
  • 后记

本文作者MichaelX,博客地址:http://blog.csdn.net/xiong_it 转载请注明来源


前言

前些年,智能硬件炒的挺火的,那今天,咱就来说说智能硬件那些事。BLE是智能硬件的一种通讯方式,通过BLE连接,iOS & Android手机和智能硬件就可以进行自定义的交互了。交互的体验如何,很大程度上取决于智能硬件的驱动工程师驱动写的好不好,以及App的代码质量如何。

笔者曾参与过多款BLE智能硬件的开发,许久不用,怕忘了,把自己的整理的一些知识记录与此,同时也希望能够给一些同学带来帮助。本文将尽力向读者讲清楚BLE是什么,以及在实际Android开发中该如何使用BLE。

前方高能:文章有点长,笔者经历了好几次改版,也花费了好几个月的业余时间,读者可能需要点耐心。着急的读者可直接跳转至Android如何使用BLE

BLE是个什么鬼

BLE:Bluetooth Low Energy,低功耗蓝牙。Android官方介绍如下:

Android 4.3 (API Level 18) introduces built-in platform support for
Bluetooth Low Energy in the central role and provides APIs that apps
can use to discover devices, query for services, and read/write
characteristics. In contrast to Classic Bluetooth, Bluetooth Low Energy
(BLE) is designed to provide significantly lower power consumption. This
allows Android apps to communicate with BLE devices that have low power
requirements, such as proximity sensors, heart rate monitors, fitness
devices, and so on.

什么意思呢?自从API18/Android4.3开始,Android开始支持低功耗蓝牙并给APP提供了一套api调用。相比传统蓝牙来说,BLE技术旨在降低蓝牙功耗。至于我们Android开发者来说,要做的就是调用这套api,和具备蓝牙的智能硬件沟通,通过蓝牙读写操控智能硬件。

BLE技术允许APP和那些有着低功耗需求的BLE设备进行通讯,这些设备包括但不限于:距离传感器设备,心跳率检测仪,健身器材等。

约定:文中提到的”外设”,”BLE外设”和”智能硬件”是等价的.请读者知悉.


角色分工

Once the phone and the activity tracker have established a
connection, they start transferring GATT metadata to one another.
Depending on the kind of data they transfer, one or the other might act
as the server. For example, if the activity tracker wants to report
sensor data to the phone, it might make sense for the activity tracker
to act as the server. If the activity tracker wants to receive updates
from the phone, then it might make sense for the phone to act as the
server.

在Android APP和BLE外设进行交互时,他们分别扮演两个角色.这两个角色是不固定的.
GATT server:发送数据的一方.
GATT client:接收数据的一方.
当APP向外设写入数据时,APP就是server,外设就是client;当APP读取外设数据时,APP就是client.外设就是server.


主要的关键词和概念

GATT(Generic Attribute Profile )

The GATT profile is a general specification for sending and
receiving short pieces of data known as “attributes” over a BLE link.
All current Low Energy APPlication profiles are based on GATT.

这个是BLE通讯的基本协议,这个协议定义了BLE发送和接收一小段数据的规范,这些被传输的小段数据被称为”attributes”.

Characteristic

A characteristic contains a single value and 0-n descriptors that
describe the characteristic’s value. A characteristic can be thought of
as a type, analogous to a class.

博主的理解中,”Characteristic”是BLE通讯之间的沟通”搬运工”,因为这是我们从智能硬件直接读写的东西,它依附于下文的Service存在,有自己的标志码:uuid。它『分为读取BLE外设数据的Characteristic & 向BLE外设写入数据的Characteristic』。
下面章节中将用代码说话.

Service

A service is a collection of characteristics. For example, you
could have a service called “Heart Rate Monitor” that includes
characteristics such as “heart rate measurement.”

此Service非彼Android四大组件中的彼Service,而是BluetoothGattService.这个Service是一个characteristics的集合,它可以理解为针对某个信号的通讯线路。


Android如何使用BLE

蓝牙权限

使用BLE需要两个权限

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
  • 1
  • 2
  • 1
  • 2

如果你想要APP只适配具备BLE的手机,那个可以再添加一个硬件权限特性

<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
  • 1
  • 1

APP和BLE外设交互流程

APP和BLE外设交互的一个大概流程就是:

  1. BLE外设打开电源
  2. APP初始化蓝牙
  3. APP扫描周边BLE外设
  4. APP连接到周边BLE外设
  5. APP读写BLE外设
  6. 交互完成,APP向BLE外设写入关机/待机指令(可选)
  7. BLE外设关机
  8. APP关闭本地蓝牙连接

以下将逐步利用代码进行讲解APP和BLE外设交互.

初始化BLE

Java代码判断当前手机是否支持BLE低功耗蓝牙

// 判断手机是否支持BLE
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();// 如果手机不支持BLE就关闭程序,仅供参考
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

初始化蓝牙管理者和适配器,这2个对象是ble通讯的基石.

// 初始化蓝牙管理者和适配器,这2个对象是ble通讯的基石.
private BluetoothAdapter mBluetoothAdapter;
...
final BluetoothManager bluetoothManager =
(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

跳转到系统蓝牙设置界面

private BluetoothAdapter mBluetoothAdapter;
...
// 验证蓝牙是否已打开,如果没打开就提示用户跳转打开.
if (mBluetoothAdapter == null || !mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

APP扫描周边BLE外设

需要实现一个BluetoothAdapter.LeScanCallback回调接口,得到扫描结果。该接口只有一个回调方法:

/**
* @param device 被手机蓝牙扫描到的BLE外设实体对象
* @param rssi 大概就是表示BLE外设的信号强度,如果为0,则表示BLE外设不可连接。
* @param scanRecord 被扫描到的BLE外围设备提供的扫描记录,一般没什么用
*/
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

由于扫描BLE设备比较消耗资源,官方推荐间歇性扫描,示例代码如下

    private BluetoothAdapter mBluetoothAdapter;
private boolean mScanning;
private Handler mHandler; // 每扫描10s休息一下
private static final long SCAN_PERIOD = 10000; private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
// TODO 这里可以进行连接操作,连接操作见下一小节
if (device != null && device.getName() != null && device.getName().contain("你的产品名称")){
// 连接设备
connectDevice(device);
// 停止扫描
scanLeDevice(false);
}
}
});
}
}; ...
/**
* @param enable 是否进行扫描,false则停止扫描
*/
private void scanLeDevice(final boolean enable) {
if (enable) {
// 利用Handler进行间歇性扫描,每次扫描时间:10s
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);
}
...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47

APP连接周边BLE外设

连接操作是进行手机和BLE外设交互的基础,请看下面connectDevice(BluetoothDevice)方法实现。

分两步走:
1. 判断该设备是否连接过,连接过则首先尝试直接连接:BluetoothGatt.connect()
2. 首次连接或者直连失败使用:BluetoothDevice.connectGatt(Context context, boolean autoConnect, BluetoothGattCallback callback)

public boolean connectDevice(final BluetoothDevice device) {
if (mBluetoothAdapter == null || device == null) {
Log.w(TAG,
"BluetoothAdapter not initialized or unspecified address.");
return false;
} String address = device.getAddress();
// 之前连接过的设备,尝试直接连接。mBluetoothDeviceAddress表示刚才连接过的设备地址
if (mBluetoothDeviceAddress != null
&& address.equals(mBluetoothDeviceAddress)
&& mBluetoothGatt != null) {
Log.d(TAG,
"Trying to use an existing mBluetoothGatt for connection.");
if (mBluetoothGatt.connect()) {// 连接成功
// 修改连接状态变量
mConnectionState = STATE_CONNECTING;
return true;
} else {
return false;
}
} final BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(address);
if (remoteDevice == null) {
Log.w(TAG, "Device not found. Unable to connect.");
return false;
}
mBluetoothGatt = remoteDevice.connectGatt(context, false, mGattCallback);
Log.d(TAG, "Trying to create a new connection.");
// 将当前连接上的设备地址赋值给连接过的设备地址变量
mBluetoothDeviceAddress = address;
// 改变连接状态变量
mConnectionState = STATE_CONNECTING;
return true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

连接BEL外设时,需要一个实现回调接口以得到连接状态,BluetoothGattCallback大概实现如下:

private final BluetoothGattCallback mGattCallback =
new BluetoothGattCallback() {
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
// 该方法在连接状态改变时回调,newState即代表当前连接状态
String intentAction;
// 连接上了
if (newState == BluetoothProfile.STATE_CONNECTED) {
intentAction = ACTION_GATT_CONNECTED;
// 改变蓝牙连接状态变量
mConnectionState = STATE_CONNECTED;
// 发送自定义广播:连接上了
broadcastUpdate(intentAction);
// 当前外设相当于前面章节提到的Server角色:提供数据被手机读取
Log.i(TAG, "Connected to GATT server.");
// 获取读/写服务:Service。该方法会触发下面的onServicesDiscovered()回调
mBluetoothGatt.discoverServices(); } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {// 断开连接了
intentAction = ACTION_GATT_DISCONNECTED;
mConnectionState = STATE_DISCONNECTED;
Log.i(TAG, "Disconnected from GATT server.");
// 发送自定义广播:断开了连接
broadcastUpdate(intentAction);
}
} @Override
// 该方法在蓝牙服务被发现时回调。由上述的mBluetoothGatt.discoverServices()触发结果。
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
// 发现服务。status表示发现服务的结果码
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_GATT_SERVICES_DISCOVERED);
// TODO 从发现的Service来找出读数据用的BluetoothGattCharacteristic和写数据用的BluetoothGattCharacteristic。
initReadAndWriteCharacteristic(gatt.getServices()); } else {// 未发现服务
Log.w(TAG, "onServicesDiscovered received: " + status);
}
} @Override
// 读取操作的回调结果
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
}
} @Override
// 写入操作的回调结果
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
};
...
};
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

找出读写”数据包”的”搬运工”

下面是找出读写”搬运工”BluetoothGattCharacteristic的initReadAndWriteCharacteristic()代码实现

BluetoothGattCharacteristic mReadCharacteristic;
BluetoothGattCharacteristic mWriteCharacteristic; public void initReadAndWriteCharacteristic(
List<BluetoothGattService> gattServices) {
if (gattServices == null)
return;
// 遍历所有的 GATT Services.
for (BluetoothGattService gattService : gattServices) {
if (!gattService.getUuid().toString().trim().equalsIgnoreCase("这里是你期望的Service的uuid,由你司智能外色的驱动工程师决定"))
continue;
List<BluetoothGattCharacteristic> gattCharacteristics = gattService.getCharacteristics();
// 遍历当前Service中所有的Characteristics.
for (BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) { if (gattCharacteristic.getUuid().toString().trim().equalsIgnoreCase(""这里是你期望的写数据的uuid,由你司驱动工程师决定"")) {
mWriteCharacteristic = gattCharacteristic;
} else if (gattCharacteristic.getUuid().toString().trim().equalsIgnoreCase("这里是你期望的读数据的uuid,由你司驱动工程师决定")) {
mReadCharacteristic = gattCharacteristic;
}
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

至此,我们就拿到了可携带读写数据的“搬运工”-『mReadCharacteristic & mWriteCharacteristic』,下面就可以和智能硬件进行交互了。

APP读取BLE外设蓝牙数据

想要读取BLE外设的数据时,比如:心跳速率,电量等等。可通过下面方式。

// 告诉”搬运工“我想知道BLE外设当前数据,将回调BluetoothGattCallback接口的onCharacteristicRead()方法
mBluetoothGatt.readCharacteristic(mReadCharacteristic); // 读取BLE蓝牙数据操作的回调方法
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
broadcastUpdate(ACTION_DATA_AVAILABLE, characteristic);
// ”搬运工“把”数据包“搬出来了
byte[] data = characteristic.getValue();
// 根据驱动工程师给的协议文档,解析该数组,该处假设数组0位上表示心跳速率
int heartRateR = data[0];// 得到心跳速率,做相应UI更新和操作
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

APP向BLE外设写入数据

比如说你想告诉BLE外设让他锁屏,或者进行某个动作,APP向操纵BLE外设时可通过以下方式

// 根据驱动工程师给的协议文档,组织一个数组命令
byte[] data = getData();
// 将该条命令“数据包”给“搬运工"
mWriteCharacteristic.setValue(data); // ”搬运工“将数据搬到BLE外设里面了,将回调BluetoothGattCallback接口的onCharacteristicWrite()方法
mBluetoothGatt.writeCharacteristic(characteristic); // 向BLE蓝牙外设写入数据操作的回调方法
@Override
public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if(status == BluetoothGatt.GATT_SUCCESS) {
// 命令写入成功,数据包成功写入BLE外设中
}
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

多说一句,其实,手机关闭外设也是一条写入命令,外设得到该命令后即进入省电待机状态,一般外设也可以通过开/关机键彻底关机。

APP关闭蓝牙连接

交互完了,不需要了,还是把APP蓝牙连接给断掉吧

public void close() {
if (mBluetoothGatt == null) {
return;
}
mBluetoothGatt.close();
mBluetoothGatt = null;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

后记

Android官方在SDK中提供了许多demo供开发者参考(1年前左右),其实关于BLE api调用也是有的,不过只涉及了蓝牙外设的连接,未涉及蓝牙数据读写.BLE官方demo路径:User/AndroidSDK/samples/android-19/connectivity/BluetoothLeGatt
以上路径是笔者举例的路径,如果你的SDK目录下没有samples目录,现在(20170308)SDK Manager已经不开放sample下载了,请点击下载:android-sample-api19 文件提取密码: y87g

====update====
根据评论区网友xinyang_code & weiyouren_c指正:

在Android 6.0+搜索蓝牙是需要定位权限的,还有BLE搜索在Android 5.0以前和以后是不一样的。最后你还会发现使用官方这套搜索在一些手机型号上也是搜不到的!只能通过传统蓝牙(非BLE方式)搜索然后过滤出BLE设备。

笔者当年做BLE开发时基于api 19,当时5.0还未风靡大陆,4.4大行其道,非常感谢以上两位网友指正赐教!

欢迎各位朋友评论区留言交流。

本文原创作者:MichaelX,博客地址:http://blog.csdn.net/xiong_it.转载请注明来源

欢迎光临:MichaelX’s Blog

参考链接

https://developer.android.com/guide/topics/connectivity/bluetooth-le.html#terms

Android:BLE智能硬件开发详解的更多相关文章

  1. Android 时间日期Widget 开发详解

    桌面Widget其实就是一个显示一些信息的工具(现在也有人开发了一些有实际操作功能的widget.例如相机widget,可以直接桌面拍照).不过总的来说,widget主要功能就是显示一些信息.我们今天 ...

  2. Android USB 开发详解

    Android USB 开发详解 先附上 Android USB 官方文档 Android通过两种模式支持各种 USB 外设和 Android USB 附件(实现Android附件协议的硬件):USB ...

  3. Android高效率编码-第三方SDK详解系列(二)——Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能

    Android高效率编码-第三方SDK详解系列(二)--Bmob后端云开发,实现登录注册,更改资料,修改密码,邮箱验证,上传,下载,推送消息,缩略图加载等功能 我的本意是第二篇写Mob的shareSD ...

  4. Android WebView 开发详解

    Android WebView 开发详解 参见 http://blog.csdn.net/typename/article/details/39030091

  5. JMessage Android 端开发详解

    目前越来越多的应用会需要集成即时通讯功能,这里就为大家详细讲一下如何通过集成 JMessage 来为你的 App 增加即时通讯功能. 首先,一个最基础的 IM 应用会需要有哪些功能? 用户注册 / 登 ...

  6. 《Android游戏开发详解》一1.7 控制流程第1部分——if和else语句

    本节书摘来异步社区<Android游戏开发详解>一书中的第1章,第1.7节,译者: 李强 责编: 陈冀康,更多章节内容可以访问云栖社区"异步社区"公众号查看. 1.7 ...

  7. Drawable实战解析:Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)

    Android XML shape 标签使用详解   一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 circle 作为一个 View 的背景. ...

  8. 热烈祝贺华清远见《ARM处理器开发详解》第2版正式出版

    2014年6月,由华清远见研发中心组织多名业 内顶尖讲师编写的<ARM处理器开发详解>一书正式出版.本书以S5PV210处理器为平台,详细介绍了嵌入式系统开发的各个主要环节,并注重实践,辅 ...

  9. [Android新手区] SQLite 操作详解--SQL语法

    该文章完全摘自转自:北大青鸟[Android新手区] SQLite 操作详解--SQL语法  :http://home.bdqn.cn/thread-49363-1-1.html SQLite库可以解 ...

随机推荐

  1. ssh 免密码登录 与 密钥公钥原理讲解

    前言 由于最近频繁需要登录几个服务器,每次登录都需要输入密码,故相对麻烦. 由于个人服务器用于实验,故对安全性要求不是很高,故想实现ssh免密登录. 通过阅读ssh 公钥私钥认证操作及原理以及ssh公 ...

  2. H5移动端触摸事件:touchstart、touchend、touchmove

    第一部分代码事例: <html><head> <meta charset="utf-8"> <style> #main,#main1 ...

  3. Shell中各种括号的作用

    一.小括号,圆括号() 1.单小括号 () ① 命令组.括号中的命令将会新开一个子shell顺序执行,所以括号中的变量不能够被脚本余下的部分使用.括号中多个命令之间用分号隔开,最后一个命令可以没有分号 ...

  4. docker:安装mysql

    文章来源:https://www.cnblogs.com/hello-tl/p/9234429.html 1.添加镜像 docker pull mysql 2.在/data下新建文件夹mysql,进入 ...

  5. 【转】怎么把本地项目和远程git仓库相连通

    1. 打开在你的项目文件夹,输入下面的命令 git init 输完上面的命令,文件夹中会出现一个.git文件夹,如下图所示,其他的的文件也会出现蓝色小问号的标志 2. 添加所有文件 git add . ...

  6. Codeforces Round #439 (Div. 2) B. The Eternal Immortality

    B. The Eternal Immortality 题目链接http://codeforces.com/contest/869/problem/B 解题心得:题意就是给出a,b,问(a!)/(b!) ...

  7. PAT Basic 1015

    1015 德才论 宋代史学家司马光在<资治通鉴>中有一段著名的“德才论”:“是故才德全尽谓之圣人,才德兼亡谓之愚人,德胜才谓之君子,才胜德谓之小人.凡取人之术,苟不得圣人,君子而与之,与其 ...

  8. python基础——15(加密、excel操作、ini文件操作、xml操作模块及数据格式分类)

    一.加密模块 1.有解密的加密方式(base64) #base64加密 import base64 str_encrypt = input("输入要加密的字符串:\n") base ...

  9. linux open()文件操作

    python程序中经常用到的读文件: f = open("___", 'r') for line in f:#这里每次读取文件的一行,line为字符串,串尾包括了'\n'!!!   ...

  10. ActionProxy相关实现类

    package com.opensymphony.xwork2; import com.opensymphony.xwork2.config.Configuration; import com.ope ...