成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Android BLE 藍牙開發,連接藍牙設備進行通訊

移動開發 Android
我們通過bluetoothAdapter 查找到藍牙設備之后,再通過GATT服務進行藍牙設備與手機之間的配對。直接比對UUID,而不再需要PIN碼進行配對了。

1. 介紹

本篇主要基于 Android 官方的低功耗藍牙連接服務。

講解如何通過 UUID 連接藍牙設備。如果你針對 GATT 服務不太了解。那么這篇應該能夠稍微幫助到你。

官方文檔地址:https://developer.android.google.cn/guide/topics/connectivity/bluetooth-le?hl=zh_cn#connect

2. 概念

如果是老用戶了,那么就應該知道曾經藍牙設備是一個高耗電的部件。根本不可能長時間開啟。而在藍牙4.0版本之后,藍牙的通訊,耗電,抗干擾都得到了顯著提升。同時藍牙成本也得到了降低。

然后才有了我們現在的各種穿戴設備例如手環,藍牙耳機,藍牙電子秤,藍牙音箱等等的爆發。

同時,其他工業或者外置設備也都開始大量支持藍牙通訊。因為能耗和成本降低了。

針對低功耗藍牙通訊,Android 4.3(API 18)開始引入了 BLE 庫。我們可以直接使用 Android SDK 中的藍牙 BLE 庫,而不用額外導入依賴庫。

以前開發藍牙通訊,還需要實現藍牙配對。需要主動跳轉到手機設置界面進行PIN碼配對,然后配對通過之后才能進行藍牙鏈接。

而使用BLE庫,我們可以直接通過藍牙設備的UUID進行連接(通過GATT服務),在當前應用內就能直接連接了。而不用通過系統設置。

市面上的各種手環的自動匹配鏈接,電子秤的自動連接等等都是通過GATT進行通訊和鏈接的。

2.1 術語

  • GATT:全稱為:Generic Attribute Profile,翻譯為:通用屬性配置文件。GATT 配置文件是一種通用規范,內容針對在 BLE 鏈路上發送和接收稱為“屬性ATT”的簡短數據片段。目前所有低功耗應用配置文件均以 GATT 為基礎。
  • ATT:全稱為:Attribute Protocol,翻譯為:屬性協議。它是 GATT 的構建基礎,二者的關系也被稱為 GATT/ATT。每個屬性均由通用唯一標識符 (UUID) 進行唯一標識,后者是用于對信息進行唯一標識的字符串 ID 的 128 位標準化格式。由 ATT 傳輸的屬性采用特征和服務格式。
  • 特征 Characteristic: 特征包含一個值和 0 至多個描述特征值的描述符。您可將特征理解為類型,后者與類類似。
  • 描述符:描述符是描述特征值的已定義屬性。例如,描述符可指定人類可讀的描述、特征值的可接受范圍或特定于特征值的度量單位。
  • Service — 服務是一系列特征。例如,您可能擁有名為“心率監測器”的服務,其中包括“心率測量”等特征。

以上術語的介紹來源于Android官網

2.2 通訊過程

假如我們有一個藍牙外置設備(Device),然后有一個支持藍牙的移動設備(Phone)。兩者之間的通訊方式步驟是:

  1. Device 開啟藍牙。(通常這些設備都是開機之后,就默認開啟藍牙了)
  2. Phone 開啟藍牙。
  3. Phone 發現 Device。
  4. Phone 與 Device 創建藍牙連接。
  5. Phone 創建 Gatt 客戶端,與 Device Gatt 服務端連接。
  6. Phone 通過 Gatt 服務功能獲取 Device 中的消息,并發送消息給 Device 設備。

整個過程就是這樣的。下面我也將按照這個通訊過程進行介紹。

3.開發

基于我的使用情況,從無到有的介紹,完整的藍牙開發配置過程。給大家一個參考

語言主要為 Java

3.1 權限

要在應用中使用藍牙功能,必須聲明 BLUETOOTH 藍牙權限。需要此權限才能執行任何藍牙通信,例如請求連接、接受連接和傳輸數據等。

同時,還需要位置權限。因為藍牙 LE 信標通常與位置相關聯。如果不開啟 ACCESS_FINE_LOCATION 權限。那么我們將會無法發現藍牙設備。

也就是執行藍牙掃描 API 無法得到任何結果(PS::Logcat 中的錯誤日志會告訴你,要開啟位置權限,否則無法掃描發現藍牙設備)。

<!-- 藍牙搜索配對 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<!-- 操縱藍牙的開啟-->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

<!-- 如果應用必須安裝在支持藍牙的設備上,可以將下面的required的值設置為true。-->
<uses-feature
android:name="android.hardware.bluetooth_le"
android:required="false" />

其中 android.permission.ACCESS_FINE_LOCATION? 是高版本API 28 權限。如果要支持更低版本,就需要申請<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />。

如果要執行藍牙掃描功能,我們需要申請:<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />權限

如果要執行藍牙鏈接,開關藍牙。需要申請:<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />權限

而上面兩個權限呢,是在 API 31 上才有效。而低版本就是申請:

<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />權限也就夠了。

權限配置完畢之后,就是代碼開發了。

不管是高版本,還是低版本。將權限都申請可以說最穩妥了。

3.2 檢測設備是否支持藍牙

通常情況下,手機是有藍牙的。而我們如果在其他 Android 系統的設備中,例如TV,平板,一體機等等。是否有藍牙還真不能完整保證。

如果不確定的情況下,那么可以通過以下代碼檢查 BLE 的可用性。

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
//不支持藍牙設備
finish();
} else {
//支持藍牙設備
}

藍牙是否開啟都不影響檢查結果。它檢查的是設備是否有藍牙功能,而不是藍牙是否啟動,下面會介紹如何判斷藍牙是否啟動

3.3 開啟藍牙

當我們設備也支持藍牙了,權限也配置了。下一步就是獲取 BluetoothAdapter 對象了。

final BluetoothManager bluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter();

我們后續控制藍牙的狀態,都是通過該方法實現的。

首先,檢測藍牙是否開啟??梢酝ㄟ^isEnabled()方法進行檢測:

if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
//開啟設備的藍牙鏈接
bluetoothAdapter.enable();//開啟藍牙
//動態判斷是否擁有位置權限ACCESS_COARSE_LOCATION 或ACCESS_FINE_LOCATION ,然后再執行藍牙掃描
} else {
//動態判斷是否擁有位置權限ACCESS_COARSE_LOCATION 或ACCESS_FINE_LOCATION,然后再執行藍牙掃描
}

我們其實可以直接使用bluetoothAdapter.enable()開啟藍牙。當藍牙沒有開啟時,我們可以直接開啟藍牙。

這個方法的結果,并不是實時返回的。我們如果要知道藍牙是否開啟,需要監聽藍牙狀態的廣播才行。下面會介紹廣播監聽。

PS:這個方法需要android.Manifest.permission.BLUETOOTH_CONNECT 權限才能使用。

官方是建議我們通過Intent讓系統設置進行開啟藍牙的。

if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}

但是現在startActivityForResult方法已經過時。我們可以使用Launcher來調用:

ActivityResultLauncher<Intent> launcher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
if (result.getResultCode() == RESULT_OK) {
//處理返回結果
}
});

上面的 launcher?需要在Activity? 的 onCreate 方法中初始化。然后在需要進行藍牙設置界面啟動的地方配置:

Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); //創建一個藍牙啟動的意圖
launcher.launch(enableBtIntent);//使用launcer啟動這個意圖就可以了。

我們如果使用bluetoothAdapter.enable();?時Android Studio出現代碼錯誤警告,可以在該代碼使用的方法中添加:@SuppressLint("MissingPermission")注解。

3.4 廣播監聽

其實這個廣播監聽,是否需要。根據大家實際情況來定。不一定需要。

首先,創建一個動態廣播對象:

public class BluetoothFoundReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//監聽藍牙狀態之后,發送消息
try {
if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) {
//開始掃描
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
//結束掃描
} else if (BluetoothDevice.ACTION_FOUND.equals(action)) {
//發現設備,每掃碼到一個設備,都會觸發一次
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//我們可以得到藍牙設備
} else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
//藍牙開關狀態

int statue = bluetoothAdapter.getState();
switch (statue) {
case BluetoothAdapter.STATE_OFF:
Log.e(TAG, "藍牙狀態:,藍牙關閉");
break;
case BluetoothAdapter.STATE_ON:
Log.e(TAG, "藍牙狀態:,藍牙打開");

break;
case BluetoothAdapter.STATE_TURNING_OFF:
Log.e(TAG, "藍牙狀態:,藍牙正在關閉");
break;
case BluetoothAdapter.STATE_TURNING_ON:
Log.e(TAG, "藍牙狀態:,藍牙正在打開");
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

然后進行廣播注冊:

bluetoothFoundReceiver = new BluetoothFoundReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);//連接藍牙,斷開藍牙
filter.addAction(BluetoothDevice.ACTION_FOUND);//找到設備的廣播
filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//搜索完成的廣播
filter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);//狀態改變 配對開始時,配對成功時
registerReceiver(bluetoothFoundReceiver, filter);

注冊完畢后,在onDestroy方法中需要注銷注冊:

@Override
protected void onDestroy() {
if (bluetoothFoundReceiver != null)
unregisterReceiver(bluetoothFoundReceiver); //停止監聽
super.onDestroy();
}

其實,我們只需要藍牙狀態的監聽就可以了BluetoothAdapter.ACTION_STATE_CHANGED 其他的設備查找,配對??梢圆挥?,因為觸發到廣播的設備查找效率太低,而且多次重復查找時,還會出現耗時變長。設備無法查找到的情況。

3.5 藍牙設備查找

官方文檔上推薦的查找方式是:

bluetoothAdapter.startLeScan(leScanCallback);  //查找
bluetoothAdapter.stopLeScan(leScanCallback); //停止查找

可是現在這個方法也過時了。替換方法是:

BluetoothLeScanner scanner = bluetoothAdapter.getBluetoothLeScanner();
//不進行權限驗證
ScanCallback callback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
BluetoothDevice device = result.getDevice();//得到設備
// Log.e(TAG, "發現設備" + device.getName());
}

@Override
public void onScanFailed(int errorCode) {
super.onScanFailed(errorCode);
Log.e(TAG, "搜索錯誤" + errorCode);
}
};
scanner.startScan(callback);

onScanResult方法是一個在子線程觸發的回調,我們不能在該方法中直接操作UI對象。

其次,掃描到一個藍牙設備就會觸發一次消息回調。我們可以得到一個BluetoothDevice對象。 也就是說這個方法中會觸發多次回調,

所以建議,在掃描到我們的藍牙設備之后,主動調用scanner.stopScan(callback);停止掃描。

PS:這種查找方式,不會觸發藍牙的遍歷廣播。我們如果開啟廣播進行監聽設備掃描情況。如果通過startScan方法,廣播中不會有回調。

上面是一個通用搜索模式,我們還可以配置自己的過濾條件。例如:

ScanFilter sn =new ScanFilter.Builder().setDeviceName("藍牙設備的名稱").setServiceUuid(ParcelUuid.fromString("我們的設備的Service UUID")).build();
List<ScanFilter> scanFilters=new ArrayList<>();
scanFilters.add(sn);
scanner.startScan(scanFilters, new ScanSettings.Builder().build(),callback);

其中ScanFilter?對象,我們可以配置我們想查找的藍牙設備的信息??梢允莝etDeviceName,setServiceUuid,setDeviceAddress,setServiceSolicitationUuid等。

ScanSettings對象是可以定義我們的掃描模式,通過配置該項可以提高掃描效率。

默認情況下,執行的是:SCAN_MODE_LOW_POWER在低功耗模式下執行藍牙LE掃描。 這是默認的掃描模式,因為它消耗最少的電量。

3.5.1 startDiscovery

如果上面的方法還不滿足我們的情況,可以使用:

if (bluetoothAdapter.isDiscovering()) {//是否在掃描
bluetoothAdapter.cancelDiscovery(); //停止掃描
}
//查找藍牙
bluetoothAdapter.startDiscovery();

我們可以直接使用bluetoothAdapter進行掃描。這個方法觸發之后是由系統進行藍牙掃描。就和我們在手機的設置界面中點擊藍牙掃描一樣。

上面的這個方法沒有回調,因為所有的藍牙設備的發現都將通過廣播事件進行傳遞。

需要通過我上面的廣播監聽介紹的內容。進行實時獲取到掃描到的設備。

使用上面的方法有幾個缺點:

1.效率慢,耗時很長。

2.重復掃描會失敗。不能說是失敗了,而是系統會將重復掃描的請求進行阻止,關鍵的問題在于這個阻止操作是手機廠商定制的。

PS:不管是BluetoothLeScanner? 還是bluetoothAdapter.startDiscovery() 去查找藍牙設備。都不建議一直重復掃描。否則會出現無法掃描到設備,沒有任何掃描結果等等情況。因為掃描是一個耗時耗電的操作。

3.6 鏈接Gatt

當我們掃描到了藍牙設備之后,就會獲取到BluetoothDevice?對象。然后我們通過BluetoothDevice?對象創建GATT服務進行后續的藍牙通訊。

BluetoothDevice device;// 當我們通過掃描得到device對象之后,進行Gatt服務創建
BluetoothGatt bluetoothGatt = device.connectGatt(this, false, gattCallback);

第一個傳參context沒有什么可以介紹的。

第二個傳參autoConnect:是一個boolean值對象,false代表直接連接到藍牙設備。true代表在藍牙設備可用時自動連接。

第三個參數BluetoothGattCallback 是Gatt服務的各種回調了。

我們通過gattCallback回調的內容,來得到與藍牙設備的鏈接狀態,數據通信內容等。

下面來詳細介紹下BluetoothGattCallback對象的幾個方法。

String SERVICE_UUID="00000-000000-000000-000000";//這個是我要鏈接的藍牙設備的ServiceUUID

BluetoothGattCallback gattCallback = new BluetoothGattCallback() {
//GATT的鏈接狀態回調
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
super.onConnectionStateChange(gatt, status, newState);
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices();
Log.v(TAG, "連接成功");
} else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
Log.e(TAG, "連接斷開");
} else if (newState == BluetoothProfile.STATE_CONNECTING) {
//TODO 在實際過程中,該方法并沒有調用
Log.e(TAG, "連接中....");
}
}
//獲取GATT服務發現后的回調
@Override
public void onServicesDiscovered(BluetoothGatt gatt, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
Log.e(TAG, "GATT_SUCCESS"); //服務發現
for (BluetoothGattService bluetoothGattService : gatt.getServices()) {
Log.e(TAG, "Service_UUID" + bluetoothGattService.getUuid()); // 我們可以遍歷到該藍牙設備的全部Service對象。然后通過比較Service的UUID,我們可以區分該服務是屬于什么業務的
if (SERVICE_UUID.equals(bluetoothGattService.getUuid().toString())) {

for (BluetoothGattCharacteristic characteristic : bluetoothGattService.getCharacteristics()) {
prepareBroadcastDataNotify(gatt, characteristic); //給滿足條件的屬性配置上消息通知
}
return;//結束循環操作
}
}
} else {
Log.e(TAG, "onServicesDiscovered received: " + status);
}
}

//藍牙設備發送消息后的自動監聽
@Override
public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
// readUUID 是我要鏈接的藍牙設備的消息讀UUID值,跟通知的特性的UUID比較。這樣可以避免其他消息的污染。
if (READ_UUID.equals(characteristic.getUuid().toString())) {
try {
String chara = new String(characteristic.getValue(), "UTF-8");
Log.e(TAG, "消息內容:" + chara);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
};

我們可以通過鏈接成功和鏈接斷開。來判斷我們當前與藍牙設備的通訊狀態。

當我們比對Service?的UUID成功之后, 我們就可以獲取Service的Characteristic對象。該對象也就是特征。通過注冊特征來實現消息的監聽和發送業務。

3.7 注冊消息監聽-setCharacteristicNotification

@SuppressLint("MissingPermission")
private void prepareBroadcastDataNotify(BluetoothGatt mBluetoothGatt, BluetoothGattCharacteristic characteristic) {
Log.e(TAG, "CharacteristicUUID:" + characteristic.getUuid().toString());
int charaProp = characteristic.getProperties();
//判斷屬性是否支持消息通知
if ((charaProp | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
BluetoothGattDescriptor descriptor =
characteristic.getDescriptor(UUID.fromString(UUIDManager.READ_DEDSCRIPTION_UUID));
if (descriptor != null) {
//注冊消息通知
mBluetoothGatt.setCharacteristicNotification(characteristic, true);
descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
mBluetoothGatt.writeDescriptor(descriptor);
}
}
}

在上面的示例中:READ_DEDSCRIPTION_UUID = "00002902-0000-1000-8000-00805f9b34fb" 是固定的,不管你鏈接什么樣的藍牙設備。

在注冊消息監聽,都是使用UUID值是00002902-0000-1000-8000-00805f9b34fb進行的。這個是Android系統保留的。用于動態監聽的。

你如果不想使用這個動態監聽。就需要自己寫線程主動去輪詢獲取到藍牙設備發送過來的消息了。

到這里,我們其實就能夠實現藍牙設備的實時監聽,并得到消息內容了。

3.8 寫數據到藍牙設備中

我們如果想將內容推送到藍牙設備中,在發現服務的時候onServicesDiscovered 遍歷特性中,確保是用于寫消息的特性對象后。選擇持有該特性,然后通過:

String data ="0x12";
BluetoothGattCharacteristic writeCharact = bluetoothGattService.
getCharacteristic(UUID.fromString(WRITE_UUID));
//查找UUID是寫的特性,并檢測是否擁有寫權限
if (writeCharact == null || writeCharact.getProperties() != BluetoothGattCharacteristic.PROPERTY_WRITE) {
return ;//該特性沒有寫的權限。所以無法傳入
}
// 當數據傳遞到藍牙之后
// 會回調BluetoothGattCallback里面的write方法
writeCharact.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
// 將需要傳遞的數據轉為16進制數
writeCharact.setValue(data);
bluetoothGatt.writeCharacteristic(writeCharact);

3.9 關閉連接

當藍牙通訊結束,或者界面關閉時。我們需要關閉GATT服務,減少資源占用。

if (bluetoothGatt != null) {
bluetoothGatt.close();
bluetoothGatt.disconnect();
bluetoothGatt = null;
}

也可以關閉BluetoothGattCallback 的回調監聽:

gattCallback.disConnectBlue();//關閉GATT服務回調監聽

4. 小結

到這里藍牙的鏈接和讀取就結束了。

我們通過bluetoothAdapter 查找到藍牙設備之后,再通過GATT服務進行藍牙設備與手機之間的配對。直接比對UUID,而不再需要PIN碼進行配對了。

(PS:有些安全性要求比較高的設備,還是會需要主動進行PIN碼配對。PIN配隊就只能通過系統設備界面中的藍牙功能項進行操作了。)

通過GATT服務連接成功后。就可以查詢該Server下的各種特性了,不同的特性對應了一個功能。有發消息的特性,也有用于收消息的特性。

同時一個藍牙設備對象,可能有多種服務功能。

如果不想自己寫線程變量輪詢設備發送過來的消息,就通過注冊消息監聽。讓BLE框架幫我們進行輪詢之后,再通知到我們。

責任編輯:武曉燕 來源: zinyan
相關推薦

2023-04-17 16:10:14

鴻蒙藍牙

2015-09-22 11:04:24

藍牙4.0開發

2022-01-25 16:54:14

BLE操作系統鴻蒙

2021-10-30 07:55:00

BLE 藍牙開發

2022-11-17 15:26:06

低功耗藍牙鴻蒙

2015-02-27 16:03:26

Android源碼Bluetooth_4BLE藍牙通信

2015-09-11 15:41:08

2011-06-08 12:42:08

Android 藍牙

2021-11-12 23:44:28

Windows 10Windows微軟

2023-09-19 15:58:13

Zigbee藍牙

2015-01-09 16:10:19

藍牙設備安全安全工具BlueMaho

2020-09-17 11:02:40

BLESA藍牙攻擊漏洞

2019-01-10 18:22:58

2010-01-08 15:35:30

Ubuntu連接

2013-12-09 09:37:11

藍牙4.1物聯網

2024-04-12 15:52:42

藍牙

2021-12-17 11:29:03

WiFi漏洞芯片

2020-05-20 12:52:03

漏洞攻擊藍牙

2021-09-05 05:59:00

BrakTooth漏洞藍牙設備

2021-09-07 05:36:59

藍牙漏洞惡意代碼
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲在线久久 | 天天草草草 | 久久久久久国产 | 国产日韩欧美在线观看 | 91亚洲视频在线 | 国产一级黄色网 | 91福利在线导航 | 国产视频三级 | 激情综合五月 | 国产精品无 | 国产精品久久 | 久久亚洲一区 | 亚洲精品一区在线观看 | 国产97在线 | 日韩 | 99国内精品久久久久久久 | 九九热在线视频观看这里只有精品 | 日本免费黄色 | 亚洲第一免费播放区 | 一区二区三区国产好的精 | 亚洲国产精品网站 | 久久久网 | 国产偷录叫床高潮录音 | 欧美涩涩网| 亚洲精品欧美 | 国产精品国产成人国产三级 | 成年人视频在线免费观看 | 久久在线 | av乱码| 97国产精品视频人人做人人爱 | 毛片免费在线 | 国产午夜精品久久久 | www.色午夜.com | 国产精品久久久久av | 91在线免费视频 | 亚洲成人一区 | 国产乱码精品1区2区3区 | 久久久久久久久久久一区二区 | 自拍 亚洲 欧美 老师 丝袜 | 国产欧美一区二区三区国产幕精品 | 久久精品久久久久久 | 黄色电影在线免费观看 |