Android實(shí)現(xiàn)藍(lán)牙(BlueTooth)設(shè)備檢測(cè)連接
無論是WIFI還是4G網(wǎng)絡(luò),建立網(wǎng)絡(luò)連接后都是訪問互聯(lián)網(wǎng)資源,并不能直接訪問局域網(wǎng)資源。比如兩個(gè)人在一起,A要把手機(jī)上的視頻傳給B,通常情況是打開手機(jī)QQ,通過QQ傳送文件給對(duì)方。不過上傳視頻很耗流量,如果現(xiàn)場(chǎng)沒有可用的WIFI,手機(jī)的數(shù)據(jù)流量又不足,那又該怎么辦呢?為了解決這種鄰近傳輸文件的問題,藍(lán)牙技術(shù)應(yīng)運(yùn)而生。藍(lán)牙技術(shù)是一種無線技術(shù)標(biāo)準(zhǔn),可實(shí)現(xiàn)設(shè)備之間的短距離數(shù)據(jù)交換。
Android為藍(lán)牙技術(shù)提供了4個(gè)工具類,分別是藍(lán)牙適配器BluetoothAdapter、藍(lán)牙設(shè)備BluetoothDevice、藍(lán)牙服務(wù)端套接字BluetoothServerSocket和藍(lán)牙客戶端套接字BluetoothSocket。
藍(lán)牙適配器BluetoothAdapter
BluetoothAdapter的作用其實(shí)跟其它的**Manger差不多,可以把它當(dāng)作藍(lán)牙管理器。下面是BluetoothAdapter的常用方法說明。
getDefaultAdapter:靜態(tài)方法,獲取默認(rèn)的藍(lán)牙適配器對(duì)象;
enable:打開藍(lán)牙功能;
disable:關(guān)閉藍(lán)牙功能;
isEnable:判斷藍(lán)牙功能是否打開;
startDiscovery:開始搜索周圍的藍(lán)牙設(shè)備;
cancelDiscovery:取消搜索操作;
isDiscovering:判斷當(dāng)前是否正在搜索設(shè)備;
getBondedDevices:獲取已綁定的設(shè)備列表;
setName:設(shè)置本機(jī)的藍(lán)牙名稱;
getName:獲取本機(jī)的藍(lán)牙名稱;
getAddress:獲取本機(jī)的藍(lán)牙地址;
getRemoteDevice:根據(jù)藍(lán)牙地址獲取遠(yuǎn)程的藍(lán)牙設(shè)備;
getState:獲取本地藍(lán)牙適配器的狀態(tài);
listenUsingRfcommWithServiceRecord:根據(jù)名稱和UUID創(chuàng)建并返回BluetoothServiceSocket;
listenUsingRfcommOn:根據(jù)渠道編號(hào)創(chuàng)建并返回BluetoothServiceSocket。
藍(lán)牙設(shè)備BluetoothDevice
BluetoothDevice用于指代某個(gè)藍(lán)牙設(shè)備,通常表示對(duì)方設(shè)備。BluetoothAdapter管理的是本機(jī)藍(lán)牙設(shè)備。下面是BluetoothDevice的常用方法說明。
- getName:獲得該設(shè)備的名稱;
- getAddress:獲得該設(shè)備的地址;
- getBondState:獲得該設(shè)備的綁定狀態(tài);
- createBond:創(chuàng)建匹配對(duì)象;
- createRfcommSocketToServiceRecord:根據(jù)UUID創(chuàng)建并返回一個(gè)BluetoothSocket。
藍(lán)牙服務(wù)器套接字BluetoothServiceSocket
BluetoothServiceSocket是服務(wù)端的Socket,用來接收客戶端的Socket連接請(qǐng)求。下面是常用的方法說明。
accept:監(jiān)聽外部的藍(lán)牙連接請(qǐng)求;
close:關(guān)閉服務(wù)端的藍(lán)牙監(jiān)聽。
藍(lán)牙客戶端套接字BluetoothSocket
BluetoothSocket是客戶端的Socket,用于與對(duì)方設(shè)備進(jìn)行數(shù)據(jù)通信。下面是常用的方法說明。
- connect:建立藍(lán)牙的socket連接;
- close:關(guān)閉藍(lán)牙的socket連接;
- getInputStream:獲取socket連接的輸入流對(duì)象;
- getOutputStream:獲取socket連接的輸出流對(duì)象;
- getRemoteDevice:獲取遠(yuǎn)程設(shè)備信息。
layout\activity_bluetooth.xml界面布局代碼如下:界面布局代碼如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="5dp"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <CheckBox android:id="@+id/ck_bluetooth" android:layout_width="wrap_content" android:layout_height="wrap_content" android:button="@null" android:checked="false" android:drawableLeft="@drawable/ck_status_selector" android:text="藍(lán)牙" android:textColor="#ff000000" android:textSize="17sp" /> <TextView android:id="@+id/tv_discovery" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="right|center" android:textColor="#ff000000" android:textSize="17sp" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="40dp" android:orientation="horizontal"> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="4" android:gravity="center" android:text="名稱" android:textColor="#ff000000" android:textSize="17sp" /> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="5" android:gravity="center" android:text="地址" android:textColor="#ff000000" android:textSize="17sp" /> <TextView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="2" android:gravity="center" android:text="狀態(tài)" android:textColor="#ff000000" android:textSize="17sp" /> </LinearLayout> <ListView android:id="@+id/lv_bluetooth" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
BluetoothActivity.java邏輯代碼如下:
package com.fukaimei.bluetoothtest; import java.io.IOException; import java.lang.reflect.Method; import java.util.ArrayList; import android.app.AlertDialog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothSocket; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.ListView; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.TextView; import android.widget.Toast; import com.fukaimei.bluetoothtest.adapter.BlueListAdapter; import com.fukaimei.bluetoothtest.bean.BlueDevice; import com.fukaimei.bluetoothtest.task.BlueAcceptTask; import com.fukaimei.bluetoothtest.task.BlueConnectTask; import com.fukaimei.bluetoothtest.task.BlueReceiveTask; import com.fukaimei.bluetoothtest.util.BluetoothUtil; import com.fukaimei.bluetoothtest.widget.InputDialogFragment; public class BluetoothActivity extends AppCompatActivity implements OnClickListener, OnItemClickListener, OnCheckedChangeListener, BlueConnectTask.BlueConnectListener, InputDialogFragment.InputCallbacks, BlueAcceptTask.BlueAcceptListener { private static final String TAG = "BluetoothActivity"; private CheckBox ck_bluetooth; private TextView tv_discovery; private ListView lv_bluetooth; private BluetoothAdapter mBluetooth; private ArrayList<BlueDevice> mDeviceList = new ArrayList<BlueDevice>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bluetooth); bluetoothPermissions(); ck_bluetooth = (CheckBox) findViewById(R.id.ck_bluetooth); tv_discovery = (TextView) findViewById(R.id.tv_discovery); lv_bluetooth = (ListView) findViewById(R.id.lv_bluetooth); if (BluetoothUtil.getBlueToothStatus(this) == true) { ck_bluetooth.setChecked(true); } ck_bluetooth.setOnCheckedChangeListener(this); tv_discovery.setOnClickListener(this); mBluetooth = BluetoothAdapter.getDefaultAdapter(); if (mBluetooth == null) { Toast.makeText(this, "本機(jī)未找到藍(lán)牙功能", Toast.LENGTH_SHORT).show(); finish(); } } // 定義獲取基于地理位置的動(dòng)態(tài)權(quán)限 private void bluetoothPermissions() { if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{ android.Manifest.permission.ACCESS_COARSE_LOCATION}, 1); } } /** * 重寫onRequestPermissionsResult方法 * 獲取動(dòng)態(tài)權(quán)限請(qǐng)求的結(jié)果,再開啟藍(lán)牙 */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (BluetoothUtil.getBlueToothStatus(this) == true) { ck_bluetooth.setChecked(true); } ck_bluetooth.setOnCheckedChangeListener(this); tv_discovery.setOnClickListener(this); mBluetooth = BluetoothAdapter.getDefaultAdapter(); if (mBluetooth == null) { Toast.makeText(this, "本機(jī)未找到藍(lán)牙功能", Toast.LENGTH_SHORT).show(); finish(); } } else { Toast.makeText(this, "用戶拒絕了權(quán)限", Toast.LENGTH_SHORT).show(); } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (buttonView.getId() == R.id.ck_bluetooth) { if (isChecked == true) { beginDiscovery(); Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); startActivityForResult(intent, 1); // 下面這行代碼為服務(wù)端需要,客戶端不需要 mHandler.postDelayed(mAccept, 1000); } else { cancelDiscovery(); BluetoothUtil.setBlueToothStatus(this, false); mDeviceList.clear(); BlueListAdapter adapter = new BlueListAdapter(this, mDeviceList); lv_bluetooth.setAdapter(adapter); } } } private Runnable mAccept = new Runnable() { @Override public void run() { if (mBluetooth.getState() == BluetoothAdapter.STATE_ON) { BlueAcceptTask acceptTask = new BlueAcceptTask(true); acceptTask.setBlueAcceptListener(BluetoothActivity.this); acceptTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else { mHandler.postDelayed(this, 1000); } } }; @Override public void onClick(View v) { if (v.getId() == R.id.tv_discovery) { beginDiscovery(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); if (requestCode == 1) { if (resultCode == RESULT_OK) { Toast.makeText(this, "允許本地藍(lán)牙被附近的其它藍(lán)牙設(shè)備發(fā)現(xiàn)", Toast.LENGTH_SHORT).show(); } else if (resultCode == RESULT_CANCELED) { Toast.makeText(this, "不允許藍(lán)牙被附近的其它藍(lán)牙設(shè)備發(fā)現(xiàn)", Toast.LENGTH_SHORT).show(); } } } private Runnable mRefresh = new Runnable() { @Override public void run() { beginDiscovery(); mHandler.postDelayed(this, 2000); } }; private void beginDiscovery() { if (mBluetooth.isDiscovering() != true) { mDeviceList.clear(); BlueListAdapter adapter = new BlueListAdapter(BluetoothActivity.this, mDeviceList); lv_bluetooth.setAdapter(adapter); tv_discovery.setText("正在搜索藍(lán)牙設(shè)備"); mBluetooth.startDiscovery(); } } private void cancelDiscovery() { mHandler.removeCallbacks(mRefresh); tv_discovery.setText("取消搜索藍(lán)牙設(shè)備"); if (mBluetooth.isDiscovering() == true) { mBluetooth.cancelDiscovery(); } } @Override protected void onStart() { super.onStart(); mHandler.postDelayed(mRefresh, 50); blueReceiver = new BluetoothReceiver(); //需要過濾多個(gè)動(dòng)作,則調(diào)用IntentFilter對(duì)象的addAction添加新動(dòng)作 IntentFilter foundFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND); foundFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); foundFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED); registerReceiver(blueReceiver, foundFilter); } @Override protected void onStop() { super.onStop(); cancelDiscovery(); unregisterReceiver(blueReceiver); } private BluetoothReceiver blueReceiver; private class BluetoothReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); Log.d(TAG, "onReceive action=" + action); // 獲得已經(jīng)搜索到的藍(lán)牙設(shè)備 if (action.equals(BluetoothDevice.ACTION_FOUND)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); BlueDevice item = new BlueDevice(device.getName(), device.getAddress(), device.getBondState() - 10); mDeviceList.add(item); BlueListAdapter adapter = new BlueListAdapter(BluetoothActivity.this, mDeviceList); lv_bluetooth.setAdapter(adapter); lv_bluetooth.setOnItemClickListener(BluetoothActivity.this); } else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) { mHandler.removeCallbacks(mRefresh); tv_discovery.setText("藍(lán)牙設(shè)備搜索完成"); } else if (action.equals(BluetoothDevice.ACTION_BOND_STATE_CHANGED)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device.getBondState() == BluetoothDevice.BOND_BONDING) { tv_discovery.setText("正在配對(duì)" + device.getName()); } else if (device.getBondState() == BluetoothDevice.BOND_BONDED) { tv_discovery.setText("完成配對(duì)" + device.getName()); mHandler.postDelayed(mRefresh, 50); } else if (device.getBondState() == BluetoothDevice.BOND_NONE) { tv_discovery.setText("取消配對(duì)" + device.getName()); } } } } @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { cancelDiscovery(); BlueDevice item = mDeviceList.get(position); BluetoothDevice device = mBluetooth.getRemoteDevice(item.address); try { if (device.getBondState() == BluetoothDevice.BOND_NONE) { Method createBondMethod = BluetoothDevice.class.getMethod("createBond"); Log.d(TAG, "開始配對(duì)"); Boolean result = (Boolean) createBondMethod.invoke(device); } else if (device.getBondState() == BluetoothDevice.BOND_BONDED && item.state != BlueListAdapter.CONNECTED) { tv_discovery.setText("開始連接"); BlueConnectTask connectTask = new BlueConnectTask(item.address); connectTask.setBlueConnectListener(this); connectTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, device); } else if (device.getBondState() == BluetoothDevice.BOND_BONDED && item.state == BlueListAdapter.CONNECTED) { tv_discovery.setText("正在發(fā)送消息"); InputDialogFragment dialog = InputDialogFragment.newInstance( "", 0, "請(qǐng)輸入要發(fā)送的消息"); String fragTag = getResources().getString(R.string.app_name); dialog.show(getFragmentManager(), fragTag); } } catch (Exception e) { e.printStackTrace(); tv_discovery.setText("配對(duì)異常:" + e.getMessage()); } } //向?qū)Ψ桨l(fā)送消息 @Override public void onInput(String title, String message, int type) { Log.d(TAG, "onInput message=" + message); Log.d(TAG, "mBlueSocket is " + (mBlueSocket == null ? "null" : "not null")); BluetoothUtil.writeOutputStream(mBlueSocket, message); } private BluetoothSocket mBlueSocket; //客戶端主動(dòng)連接 @Override public void onBlueConnect(String address, BluetoothSocket socket) { mBlueSocket = socket; tv_discovery.setText("連接成功"); refreshAddress(address); } //刷新已連接的狀態(tài) private void refreshAddress(String address) { for (int i = 0; i < mDeviceList.size(); i++) { BlueDevice item = mDeviceList.get(i); if (item.address.equals(address) == true) { item.state = BlueListAdapter.CONNECTED; mDeviceList.set(i, item); } } BlueListAdapter adapter = new BlueListAdapter(this, mDeviceList); lv_bluetooth.setAdapter(adapter); } //服務(wù)端偵聽到連接 @Override public void onBlueAccept(BluetoothSocket socket) { Log.d(TAG, "onBlueAccept socket is " + (socket == null ? "null" : "not null")); if (socket != null) { mBlueSocket = socket; BluetoothDevice device = mBlueSocket.getRemoteDevice(); refreshAddress(device.getAddress()); BlueReceiveTask receive = new BlueReceiveTask(mBlueSocket, mHandler); receive.start(); } } //收到對(duì)方發(fā)來的消息 private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (msg.what == 0) { byte[] readBuf = (byte[]) msg.obj; String readMessage = new String(readBuf, 0, msg.arg1); Log.d(TAG, "handleMessage readMessage=" + readMessage); AlertDialog.Builder builder = new AlertDialog.Builder(BluetoothActivity.this); builder.setTitle("我收到消息啦").setMessage(readMessage).setPositiveButton("確定", null); builder.create().show(); } } }; @Override protected void onDestroy() { super.onDestroy(); if (mBlueSocket != null) { try { mBlueSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
添加藍(lán)牙所需的相應(yīng)權(quán)限:
<!-- 藍(lán)牙 --> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH" /> <!--基于地理位置--> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Demo程序運(yùn)行效果界面截圖如下:
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android藍(lán)牙開發(fā)深入解析
- 詳解Android——藍(lán)牙技術(shù) 帶你實(shí)現(xiàn)終端間數(shù)據(jù)傳輸
- Android單片機(jī)與藍(lán)牙模塊通信實(shí)例代碼
- Android Bluetooth藍(lán)牙技術(shù)使用流程詳解
- 分享Android 藍(lán)牙4.0(ble)開發(fā)的解決方案
- Android 獲取藍(lán)牙Mac地址的正確方法
- Android手機(jī)通過藍(lán)牙連接佳博打印機(jī)的實(shí)例代碼
- android實(shí)現(xiàn)藍(lán)牙文件發(fā)送的實(shí)例代碼,支持多種機(jī)型
- 詳解Android 藍(lán)牙通信方式總結(jié)
- Android學(xué)習(xí)筆記之藍(lán)牙功能
相關(guān)文章
Android實(shí)現(xiàn)三段式滑動(dòng)效果
最近發(fā)現(xiàn)很多app都使用了三段式滑動(dòng),比如說高德的首頁和某寶等物流信息都是使用的三段式滑動(dòng)方式,谷歌其實(shí)給了我們很好的2段式滑動(dòng),就是BottomSheet,所以這次我也是在這個(gè)原理基礎(chǔ)上做了一個(gè)小小的修改來實(shí)現(xiàn)我們今天想要的效果。2021-06-06修改Android Studio 的 Logcat 緩沖區(qū)大小操作
這篇文章主要介紹了修改Android Studio 的 Logcat 緩沖區(qū)大小操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-04-04Kotlin面向?qū)ο笾R(shí)點(diǎn)講解
面向?qū)ο缶幊掏ㄟ^對(duì)事物的抽象,大大的簡(jiǎn)化了程序的開發(fā)難度。我們常用的編程語言:Java、C++、Python都屬于面向?qū)ο缶幊獭otlin與java類似,也是一種面向?qū)ο缶幊陶Z言。本文從面向?qū)ο笕齻€(gè)基本特征:封裝、繼承、多態(tài),來闡述一下Kotlin中的面向?qū)ο缶幊?/div> 2022-12-12Android應(yīng)用創(chuàng)建多個(gè)快捷方式
本文主要介紹Android 生成多個(gè)快捷方式,這里提供代碼實(shí)例,詳細(xì)講解生成多個(gè)快捷方式的實(shí)現(xiàn)方法,有需要的朋友可以參考下2016-07-07Android Studio實(shí)現(xiàn)標(biāo)題欄和狀態(tài)欄的隱藏
這篇文章主要介紹了Android Studio實(shí)現(xiàn)標(biāo)題欄和狀態(tài)欄的隱藏功能,在文中給大家補(bǔ)充介紹了android studio 去掉標(biāo)題欄狀態(tài)欄的完整代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-11-11Android模擬登錄評(píng)論CSDN實(shí)現(xiàn)代碼
本篇文章主要介紹了Android模擬登錄評(píng)論CSDN實(shí)現(xiàn)代碼,可以實(shí)現(xiàn)登陸發(fā)表評(píng)論到官方網(wǎng)站,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2016-11-11Android Studio查看Android 5.x源碼的步驟詳解
Google為Android開發(fā)者帶來Android Studio,用來取代Eclipse。從Android Studio出現(xiàn)起,整機(jī)開發(fā)和Android源碼閱讀和編輯一定能用上它。這篇文章小編就帶大家學(xué)習(xí)下如何使用Android Studio查看Android 5.x源碼,有需要的可以參考借鑒。2016-09-09Android仿貼吧內(nèi)容下的簡(jiǎn)單ListView嵌套GridView
這篇文章主要為大家詳細(xì)介紹了Android仿貼吧內(nèi)容下的簡(jiǎn)單ListView嵌套GridView,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-03-03Flutter本地存儲(chǔ)之基本的鍵值對(duì)存儲(chǔ)詳解
在原生的?Android?或?iOS?中,都提供了基本的鍵值對(duì)存儲(chǔ)方式,在?Flutter?中,提供了?shared_preferences?這個(gè)插件來實(shí)現(xiàn)本地鍵值對(duì)數(shù)據(jù)存儲(chǔ),本文就來和大家簡(jiǎn)單聊聊吧2023-03-03最新評(píng)論