Android系統(tǒng)中的藍(lán)牙連接程序編寫(xiě)實(shí)例教程
Bluetooth結(jié)構(gòu)
1、JAVA層
frameworks/base/core/java/android/bluetooth/
包含了bluetooth的JAVA類(lèi)。
2、JNI層
frameworks/base/core/jni/android_bluetooth_開(kāi)頭的文件
定義了bluez通過(guò)JNI到上層的接口。
frameworks/base/core/jni/android_server_bluetoothservice.cpp
調(diào)用硬件適配層的接口system/bluetooth/bluedroid/bluetooth.c
3、bluez庫(kù)
external/bluez/
這是bluez用戶(hù)空間的庫(kù),開(kāi)源的bluetooth代碼,包括很多協(xié)議,生成libbluetooth.so。
4、硬件適配層
system/bluetooth/bluedroid/bluetooth.c
包含了對(duì)硬件操作的接口
system/bluetooth/data/*
一些配置文件,復(fù)制到/etc/bluetooth/。
還有其他一些測(cè)試代碼和工具。
簡(jiǎn)略介紹Bluetooth開(kāi)發(fā)使用到的類(lèi)
1、BluetoothAdapter,藍(lán)牙適配器,可判斷藍(lán)牙設(shè)備是否可用等功能。
常用方法列舉如下:
cancelDiscovery() ,取消搜索過(guò)程,在進(jìn)行藍(lán)牙設(shè)備搜索時(shí),如果調(diào)用該方法會(huì)停止搜索。(搜索過(guò)程會(huì)持續(xù)12秒)
disable()關(guān)閉藍(lán)牙,也就是我們常說(shuō)的禁用藍(lán)牙。
enable()打開(kāi)藍(lán)牙,這個(gè)方法打開(kāi)藍(lán)牙但不會(huì)彈出提示,正常流程操作下,我們會(huì)讓系統(tǒng)提示用戶(hù)是否打開(kāi)藍(lán)牙設(shè)備。如下兩行代碼可輕松搞定。
Intent enabler=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler,reCode);//同startActivity(enabler);(在主Activity啟動(dòng)一個(gè)二級(jí)Activity,reCode一般等于3,一定記得要在AndroidManifest.xml里面添加藍(lán)牙權(quán)限)
getAddress()獲取本地藍(lán)牙地址
getDefaultAdapter()獲取默認(rèn)BluetoothAdapter,實(shí)際上,也只有這一種方法獲取BluetoothAdapter
getName()獲取本地藍(lán)牙名稱(chēng)
getRemoteDevice(String address)根據(jù)藍(lán)牙地址獲取遠(yuǎn)程藍(lán)牙設(shè)備
getState()獲取本地藍(lán)牙適配器當(dāng)前狀態(tài)(感覺(jué)可能調(diào)試的時(shí)候更需要)
isDiscovering()判斷當(dāng)前是否正在查找設(shè)備,是返回true
isEnabled()判斷藍(lán)牙是否打開(kāi),已打開(kāi)返回true,否則,返回false
listenUsingRfcommWithServiceRecord(String name,UUID uuid)根據(jù)名稱(chēng),UUID創(chuàng)建并返回
BluetoothServerSocket,這是創(chuàng)建BluetoothSocket服務(wù)器端的第一步
startDiscovery()開(kāi)始搜索,這是搜索的第一步
2.BluetoothDevice看名字就知道,這個(gè)類(lèi)描述了一個(gè)藍(lán)牙設(shè)備
createRfcommSocketToServiceRecord(UUIDuuid)根據(jù)UUID創(chuàng)建并返回一個(gè)BluetoothSocket
這個(gè)方法也是我們獲取BluetoothDevice的目的——?jiǎng)?chuàng)建BluetoothSocket
這個(gè)類(lèi)其他的方法,如getAddress(),getName(),同BluetoothAdapter
這個(gè)類(lèi)有幾個(gè)隱藏方法,涉及到藍(lán)牙的自動(dòng)配對(duì),setPin,createBond,cancelPairingUserInput,等方法(需要通過(guò)java的反射,調(diào)用這幾個(gè)隱藏方法)
3.BluetoothServerSocket如果去除了Bluetooth相信大家一定再熟悉不過(guò)了,既然是Socket,方法就應(yīng)該都差不多,
這個(gè)類(lèi)一種只有三個(gè)方法
兩個(gè)重載的accept(),accept(inttimeout)兩者的區(qū)別在于后面的方法指定了過(guò)時(shí)時(shí)間,需要注意的是,執(zhí)行這兩個(gè)方法的時(shí)候,直到接收到了客戶(hù)端的請(qǐng)求(或是過(guò)期之后),都會(huì)阻塞線(xiàn)程,應(yīng)該放在新線(xiàn)程里運(yùn)行!
還有一點(diǎn)需要注意的是,這兩個(gè)方法都返回一個(gè)BluetoothSocket,最后的連接也是服務(wù)器端與客戶(hù)端的兩個(gè)BluetoothSocket的連接
close()關(guān)閉!
4.BluetoothSocket,跟BluetoothServerSocket相對(duì),是客戶(hù)端
一共5個(gè)方法,不出意外,都會(huì)用到
close(),關(guān)閉
connect()連接
getInptuStream()獲取輸入流
getOutputStream()獲取輸出流
getRemoteDevice()獲取遠(yuǎn)程設(shè)備,這里指的是獲取bluetoothSocket指定連接的那個(gè)遠(yuǎn)程藍(lán)牙設(shè)備
二、藍(lán)牙設(shè)備的發(fā)現(xiàn)、查找。
1.基于安全性考慮,設(shè)置開(kāi)啟可被搜索后,Android系統(tǒng)會(huì)默認(rèn)給出120秒的時(shí)間,其他設(shè)備能在這120秒內(nèi)搜索到它。
Intent enable = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
startActivityForResult(enalbe,REQUEST_DISCOVERABLE);
2.搜索藍(lán)牙設(shè)備
BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter();
_bluetooth.startDiscovery();
3.關(guān)閉藍(lán)牙設(shè)備
BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter();
_bluetooth.disable();
4.創(chuàng)建藍(lán)牙客戶(hù)端
BluetoothSocket socket = device.createRfcommSocketToServiceRecord(UUID.fromString(UUID號(hào)));
socket.connect();
4.創(chuàng)建藍(lán)牙服務(wù)端
BluetoothServerSocket _serverSocket = _bluetooth.listenUsingRfcommWithServiceRecord(服務(wù)端名稱(chēng),UUID.fromeString(UUID號(hào)));
BluetoothSocket socket = _serverSocket.accept();
InputStream inputStream = socket.getInputStream();
TCP/IP通信相關(guān)代碼實(shí)現(xiàn)
注:tcp_ip模塊需要在系統(tǒng)setting模塊中,完成藍(lán)牙的配對(duì),只有配對(duì)成功的,才能進(jìn)行socket通信(具體如何配對(duì)和如何自動(dòng)配對(duì),將在bluetoot3或者4中進(jìn)行講解)
AndridManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.thecaseforbluetooth"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".BluetoothActivity"
android:label="@string/title_activity_bluetooth" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/LinearLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnSearch" android:text="查找設(shè)備" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnExit" android:text="退出應(yīng)用" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnDis" android:text="設(shè)置可被發(fā)現(xiàn)" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnserver" android:text="啟動(dòng)服務(wù)端" /> <ToggleButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/tbtnSwitch" android:text="開(kāi)/關(guān) 藍(lán)牙設(shè)備" /> <ListView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/lvDevices" /> </LinearLayout>
BluetoothActivity.java
package com.example.thecaseforbluetooth;
import java.util.ArrayList;
import java.util.Set;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.ToggleButton;
public class BluetoothActivity extends Activity {
public Button searchBtn;//搜索藍(lán)牙設(shè)備
public Button exitBtn;//退出應(yīng)用
public Button discoverBtn;//設(shè)置可被發(fā)現(xiàn)
public ToggleButton openBtn;//開(kāi)關(guān)藍(lán)牙設(shè)備
public Button serverbtn;
public ListView listView;//藍(lán)牙設(shè)備清單
public ArrayAdapter<String> adapter;
public ArrayList<String> list =new ArrayList<String>();
private BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
Set<BluetoothDevice> bondDevices ;
public Context context ;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
searchBtn = (Button)findViewById(R.id.btnSearch);
exitBtn = (Button)findViewById(R.id.btnExit);
discoverBtn = (Button)findViewById(R.id.btnDis);
openBtn = (ToggleButton)findViewById(R.id.tbtnSwitch);
serverbtn = (Button)findViewById(R.id.btnserver);
listView = (ListView)findViewById(R.id.lvDevices);
context = getApplicationContext();
adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,list);
listView.setAdapter(adapter);
openBtn.setChecked(false);
//注冊(cè)廣播接收信號(hào)
IntentFilter intent = new IntentFilter();
intent.addAction(BluetoothDevice.ACTION_FOUND);// 用BroadcastReceiver來(lái)取得搜索結(jié)果
intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); //每當(dāng)掃描模式變化的時(shí)候,應(yīng)用程序可以為通過(guò)ACTION_SCAN_MODE_CHANGED值來(lái)監(jiān)聽(tīng)全局的消息通知。比如,當(dāng)設(shè)備停止被搜尋以后,該消息可以被系統(tǒng)通知給應(yīng)用程序。
intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); //每當(dāng)藍(lán)牙模塊被打開(kāi)或者關(guān)閉,應(yīng)用程序可以為通過(guò)ACTION_STATE_CHANGED值來(lái)監(jiān)聽(tīng)全局的消息通知。
registerReceiver(searchReceiver, intent);
//顯示已配對(duì)設(shè)備以及搜索未配對(duì)設(shè)備
searchBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(bluetoothAdapter.isDiscovering()){
bluetoothAdapter.cancelDiscovery();
}
list.clear();
bondDevices = bluetoothAdapter.getBondedDevices();
for(BluetoothDevice device : bondDevices) {
String str = " 已配對(duì)完成 " + device.getName() +" "
+ device.getAddress();
list.add(str);
adapter.notifyDataSetChanged();
}
bluetoothAdapter.startDiscovery();
}
});
//退出應(yīng)用
exitBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
BluetoothActivity.this.finish();
}
});
//設(shè)置藍(lán)牙設(shè)備可發(fā)現(xiàn)
discoverBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent discoverIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverIntent);
}
});
//開(kāi)關(guān)藍(lán)牙設(shè)備
openBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(openBtn.isChecked() == true){
bluetoothAdapter.disable();
}
else if(openBtn.isChecked() == false){
bluetoothAdapter.enable();
}
}
});
//作為服務(wù)端開(kāi)啟
serverbtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
ServerThread serverThread = new ServerThread(bluetoothAdapter, context);
Toast.makeText(context, "server 端啟動(dòng)", 5000).show();
serverThread.start();
}
});
listView.setOnItemClickListener(new ItemClickListener());
}
@Override
public void onStart() {
super.onStart();
// If BT is not on, request that it be enabled.
if(bluetoothAdapter == null){
Toast.makeText(context, "藍(lán)牙設(shè)備不可用", 5000).show();
}
if (!bluetoothAdapter.isEnabled()) {
Intent enableIntent = new Intent(
BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, 3);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
private final BroadcastReceiver searchReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
String action = intent.getAction();
BluetoothDevice device = null;
if(BluetoothDevice.ACTION_FOUND.equals(action)){
device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device.getBondState() == BluetoothDevice.BOND_NONE) {
Toast.makeText(context, device.getName()+"", 5000).show();
String str = " 未配對(duì)完成 " + device.getName() +" "
+ device.getAddress();
if (list.indexOf(str) == -1)// 防止重復(fù)添加
list.add(str);
}
adapter.notifyDataSetChanged();
}
}
};
public class ItemClickListener implements OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
// TODO Auto-generated method stub
if(bluetoothAdapter.isDiscovering())
bluetoothAdapter.cancelDiscovery();
String str = list.get(arg2);
String address = str.substring(str.length() - 17);
BluetoothDevice btDev = bluetoothAdapter.getRemoteDevice(address);
ClientThread clientThread = new ClientThread(btDev, context);
clientThread.start();
}}
}
Bluetoothprotocol.java
package com.example.thecaseforbluetooth;
public interface Bluetoothprotocol {
public static final String PROTOCOL_SCHEME_L2CAP = "btl2cap";
public static final String PROTOCOL_SCHEME_RFCOMM = "btspp";
public static final String PROTOCOL_SCHEME_BT_OBEX = "btgoep";
public static final String PROTOCOL_SCHEME_TCP_OBEX = "tcpobex";
}
ServerThread.java
package com.example.thecaseforbluetooth;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;
public class ServerThread extends Thread {
public BluetoothServerSocket mserverSocket;
public BluetoothAdapter bluetoothAdapter;
public BluetoothSocket socket;
public Context context;
public ServerThread(BluetoothAdapter bluetoothAdapter,Context context) {
this.bluetoothAdapter = bluetoothAdapter;
this.context = context;
}
public void run() {
try {
/* 創(chuàng)建一個(gè)藍(lán)牙服務(wù)器
* 參數(shù)分別:服務(wù)器名稱(chēng)、UUID */
mserverSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord(Bluetoothprotocol.PROTOCOL_SCHEME_RFCOMM,
UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
// /* 接受客戶(hù)端的連接請(qǐng)求 */
socket = mserverSocket.accept();
//下面代碼作者偷懶,讀寫(xiě)另外起一個(gè)線(xiàn)程最好
//接收數(shù)據(jù)
byte[] buffer = new byte[1024];
int bytes;
InputStream mmInStream = null;
try {
mmInStream = socket.getInputStream();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println("zhoulc server");
while(true){
if( (bytes = mmInStream.read(buffer)) > 0 )
{
byte[] buf_data = new byte[bytes];
for(int i=0; i<bytes; i++)
{
buf_data[i] = buffer[i];
}
String s = new String(buf_data);
System.out.println(s+"zhoulc server is in");
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
ClientThread.java
package com.example.thecaseforbluetooth;
import java.io.IOException;
import java.io.OutputStream;
import java.util.UUID;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.widget.Toast;
public class ClientThread extends Thread {
public BluetoothSocket socket;
public BluetoothDevice device;
public Context context;
public ClientThread(BluetoothDevice device,Context context){
this.device = device;
this.context = context;
}
public void run() {
try {
//創(chuàng)建一個(gè)Socket連接:只需要服務(wù)器在注冊(cè)時(shí)的UUID號(hào)
socket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
//連接
socket.connect();
//下面代碼作者偷懶,讀寫(xiě)另外起一個(gè)線(xiàn)程最好
//發(fā)送數(shù)據(jù)
if (socket == null)
{
Toast.makeText(context, "鏈接失敗", 5000).show();
return;
}
System.out.println("zhoulc client");
while(true){
try {
System.out.println("zhoulc client is in");
String msg = "hello everybody I am client";
OutputStream os = socket.getOutputStream();
os.write(msg.getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
藍(lán)牙設(shè)備之間自動(dòng)配對(duì)
1、藍(lán)牙設(shè)備之間自動(dòng)配對(duì),需要兩個(gè)設(shè)備都安裝進(jìn)行配對(duì)的apk(網(wǎng)上好多自動(dòng)配對(duì)的帖子都沒(méi)有說(shuō)明情況)
2、在自動(dòng)匹配的時(shí)候想通過(guò)反射調(diào)用BluetoothDevice的setPin、createBond、cancelPairingUserInput實(shí)現(xiàn)設(shè)置密鑰、配對(duì)請(qǐng)求創(chuàng)建、取消密鑰信息輸入等。
1)createBond()創(chuàng)建,最終會(huì)調(diào)到源碼的BluetoothService的createBond(String address)方法,通過(guò)對(duì)源碼淺顯的了解,createBond主要是寫(xiě)入匹配密鑰(BluetoothService的writeDockPin())以及進(jìn)入jni注冊(cè)回調(diào)函數(shù)onCreatePairedDeviceResult觀察匹配結(jié)果
比如: // Pins did not match, or remote device did not respond to pin
// request in time
// We rejected pairing, or the remote side rejected pairing. This
// happens if either side presses 'cancel' at the pairing dialog.
// Not sure if this happens
// Other device is not responding at all
// already bonded
等,在jni中創(chuàng)建了進(jìn)行匹配的device("CreatePairedDevice"),這時(shí)bluetooth會(huì)發(fā)送一個(gè)ACTION_PAIRING_REQUEST的廣播,只有當(dāng)前會(huì)出現(xiàn)密鑰框的藍(lán)牙設(shè)備收到。寫(xiě)完密鑰之后,發(fā)送廣播給另外一個(gè)藍(lán)牙設(shè)備接收,然后打開(kāi)密鑰輸入框進(jìn)行匹配。
2)setPin()設(shè)置密鑰,通過(guò)查看setting源碼,發(fā)現(xiàn)在確認(rèn)輸入密鑰之后會(huì)調(diào)用setPin()(如果點(diǎn)取消,就會(huì)調(diào)用cancelPairingUserInput,取消密鑰框),setPin具體通過(guò)D-BUS做了什么沒(méi)有去深究,但是在調(diào)用setPin的時(shí)候會(huì)remove掉一個(gè)map里面的鍵值對(duì)(address:int),也就是我們?cè)谡{(diào)用setPin之后如果再去調(diào)用onCreatePairedDeviceResult,則該方法一定返回false,并且出現(xiàn)下面的打印提示:cancelUserInputNative(B8:FF:FE:55:EF:D6) called but no native data available, ignoring. Maybe the PasskeyAgent Request was already cancelled by the remote or by bluez.(因?yàn)樵摲椒ㄒ矔?huì)remove掉一個(gè)鍵值對(duì))
3)cancelPairingUserInput()取消用戶(hù)輸入密鑰框,個(gè)人覺(jué)得一般情況下不要和setPin(setPasskey、setPairingConfirmation、setRemoteOutOfBandData)一起用,這幾個(gè)方法都會(huì)remove掉map里面的key:value(也就是互斥的)。
3、藍(lán)牙耳機(jī)、手柄等一些無(wú)法手動(dòng)配置的設(shè)備是如何完成自動(dòng)配對(duì)的。
在源碼里面有一個(gè)自動(dòng)配對(duì)的方法,也就是把pin值自動(dòng)設(shè)為“0000”
/*package*/ synchronized boolean attemptAutoPair(String address) {
if (!mBondState.hasAutoPairingFailed(address) &&
!mBondState.isAutoPairingBlacklisted(address)) {
mBondState.attempt(address);
setPin(address, BluetoothDevice.convertPinToBytes("0000"));
return true;
}
return false;
}
該方法是在底層回調(diào)到j(luò)ava層的onRequestPinCode方法時(shí)被調(diào)用,首先 Check if its a dock(正常輸入的密鑰,走正常配對(duì)方式,雙方輸入匹配值),然后再 try 0000 once if the device looks dumb(涉及到Device.AUDIO_VIDEO相關(guān)部分如:耳機(jī),免提等進(jìn)入自動(dòng)匹配模式)進(jìn)行自動(dòng)配對(duì)。
言歸正傳,雖然個(gè)人覺(jué)得自動(dòng)配對(duì)需要雙方乃至多方藍(lán)牙設(shè)備都需要裝上實(shí)現(xiàn)自動(dòng)配對(duì)的apk,已經(jīng)失去了自動(dòng)配對(duì)的意義,但有可能還是會(huì)派上用場(chǎng)。下面我們看看現(xiàn)實(shí)情況的自動(dòng)配對(duì)是什么樣的吧。
由于BluetoothDevice配對(duì)的方法都是hide的,所以我們需要通過(guò)反射調(diào)用被隱藏的方法,現(xiàn)在基本都是通用的工具類(lèi)型了,網(wǎng)上模式基本一樣。
ClsUtils.java
package cn.bluetooth;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.util.Log;
public class ClsUtils
{
public static BluetoothDevice remoteDevice=null;
/**
* 與設(shè)備配對(duì) 參考源碼:platform/packages/apps/Settings.git
* /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
*/
@SuppressWarnings("unchecked")
static public boolean createBond(@SuppressWarnings("rawtypes") Class btClass, BluetoothDevice btDevice)
throws Exception
{
Method createBondMethod = btClass.getMethod("createBond");
Boolean returnValue = (Boolean) createBondMethod.invoke(btDevice);
return returnValue.booleanValue();
}
/**
* 與設(shè)備解除配對(duì) 參考源碼:platform/packages/apps/Settings.git
* /Settings/src/com/android/settings/bluetooth/CachedBluetoothDevice.java
*/
@SuppressWarnings("unchecked")
static public boolean removeBond(Class btClass, BluetoothDevice btDevice)
throws Exception
{
Method removeBondMethod = btClass.getMethod("removeBond");
Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice);
return returnValue.booleanValue();
}
@SuppressWarnings("unchecked")
static public boolean setPin(Class btClass, BluetoothDevice btDevice,
String str) throws Exception
{
try
{
Method removeBondMethod = btClass.getDeclaredMethod("setPin",
new Class[]
{byte[].class});
Boolean returnValue = (Boolean) removeBondMethod.invoke(btDevice,
new Object[]
{str.getBytes()});
Log.d("returnValue", "setPin is success " +btDevice.getAddress()+ returnValue.booleanValue());
}
catch (SecurityException e)
{
// throw new RuntimeException(e.getMessage());
e.printStackTrace();
}
catch (IllegalArgumentException e)
{
// throw new RuntimeException(e.getMessage());
e.printStackTrace();
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}
// 取消用戶(hù)輸入
@SuppressWarnings("unchecked")
static public boolean cancelPairingUserInput(Class btClass,
BluetoothDevice device)
throws Exception
{
Method createBondMethod = btClass.getMethod("cancelPairingUserInput");
// cancelBondProcess()
Boolean returnValue = (Boolean) createBondMethod.invoke(device);
Log.d("returnValue", "cancelPairingUserInput is success " + returnValue.booleanValue());
return returnValue.booleanValue();
}
// 取消配對(duì)
@SuppressWarnings("unchecked")
static public boolean cancelBondProcess(Class btClass,
BluetoothDevice device)
throws Exception
{
Method createBondMethod = btClass.getMethod("cancelBondProcess");
Boolean returnValue = (Boolean) createBondMethod.invoke(device);
return returnValue.booleanValue();
}
/**
*
* @param clsShow
*/
@SuppressWarnings("unchecked")
static public void printAllInform(Class clsShow)
{
try
{
// 取得所有方法
Method[] hideMethod = clsShow.getMethods();
int i = 0;
for (; i < hideMethod.length; i++)
{
//Log.e("method name", hideMethod.getName() + ";and the i is:"
// + i);
}
// 取得所有常量
Field[] allFields = clsShow.getFields();
for (i = 0; i < allFields.length; i++)
{
//Log.e("Field name", allFields.getName());
}
}
catch (SecurityException e)
{
// throw new RuntimeException(e.getMessage());
e.printStackTrace();
}
catch (IllegalArgumentException e)
{
// throw new RuntimeException(e.getMessage());
e.printStackTrace();
}
catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Bluetooth1.java 主activity,所有界面操作實(shí)現(xiàn)地方
package cn.bluetooth;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
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.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.Toast;
import android.widget.ToggleButton;
public class Bluetooth1 extends Activity {
/** Called when the activity is first created. */
Button btnSearch, btnDis, btnExit;
ToggleButton tbtnSwitch;
ListView lvBTDevices;
ArrayAdapter<String> adtDevices;
List<String> lstDevices = new ArrayList<String>();
BluetoothAdapter btAdapt;
public static BluetoothSocket btSocket;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Button 設(shè)置
btnSearch = (Button) this.findViewById(R.id.btnSearch);
btnSearch.setOnClickListener(new ClickEvent());
btnExit = (Button) this.findViewById(R.id.btnExit);
btnExit.setOnClickListener(new ClickEvent());
btnDis = (Button) this.findViewById(R.id.btnDis);
btnDis.setOnClickListener(new ClickEvent());
// ToogleButton設(shè)置
tbtnSwitch = (ToggleButton) this.findViewById(R.id.tbtnSwitch);
tbtnSwitch.setOnClickListener(new ClickEvent());
// ListView及其數(shù)據(jù)源 適配器
lvBTDevices = (ListView) this.findViewById(R.id.lvDevices);
adtDevices = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, lstDevices);
lvBTDevices.setAdapter(adtDevices);
lvBTDevices.setOnItemClickListener(new ItemClickEvent());
btAdapt = BluetoothAdapter.getDefaultAdapter();// 初始化本機(jī)藍(lán)牙功能
// ========================================================
// modified by wiley
/*
* if (btAdapt.getState() == BluetoothAdapter.STATE_OFF)// 讀取藍(lán)牙狀態(tài)并顯示
* tbtnSwitch.setChecked(false); else if (btAdapt.getState() ==
* BluetoothAdapter.STATE_ON) tbtnSwitch.setChecked(true);
*/
if (btAdapt.isEnabled()) {
tbtnSwitch.setChecked(false);
} else {
tbtnSwitch.setChecked(true);
}
// ============================================================
// 注冊(cè)Receiver來(lái)獲取藍(lán)牙設(shè)備相關(guān)的結(jié)果
IntentFilter intent = new IntentFilter();
intent.addAction(BluetoothDevice.ACTION_FOUND);// 用BroadcastReceiver來(lái)取得搜索結(jié)果
intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(searchDevices, intent);
}
private final BroadcastReceiver searchDevices = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Bundle b = intent.getExtras();
Object[] lstName = b.keySet().toArray();
// 顯示所有收到的消息及其細(xì)節(jié)
for (int i = 0; i < lstName.length; i++) {
String keyName = lstName.toString();
Log.e(keyName, String.valueOf(b.get(keyName)));
}
BluetoothDevice device = null;
// 搜索設(shè)備時(shí),取得設(shè)備的MAC地址
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device.getBondState() == BluetoothDevice.BOND_NONE) {
String str = " 未配對(duì)|" + device.getName() + "|"
+ device.getAddress();
if (lstDevices.indexOf(str) == -1)// 防止重復(fù)添加
lstDevices.add(str); // 獲取設(shè)備名稱(chēng)和mac地址
adtDevices.notifyDataSetChanged();
}
}else if(BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)){
device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
switch (device.getBondState()) {
case BluetoothDevice.BOND_BONDING:
Log.d("BlueToothTestActivity", "正在配對(duì)......");
break;
case BluetoothDevice.BOND_BONDED:
Log.d("BlueToothTestActivity", "完成配對(duì)");
//connect(device);//連接設(shè)備
break;
case BluetoothDevice.BOND_NONE:
Log.d("BlueToothTestActivity", "取消配對(duì)");
default:
break;
}
}
}
};
@Override
protected void onDestroy() {
this.unregisterReceiver(searchDevices);
super.onDestroy();
android.os.Process.killProcess(android.os.Process.myPid());
}
class ItemClickEvent implements AdapterView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
if(btAdapt.isDiscovering())btAdapt.cancelDiscovery();
String str = lstDevices.get(arg2);
String[] values = str.split("\\|");
String address = values[2];
Log.e("address", values[2]);
BluetoothDevice btDev = btAdapt.getRemoteDevice(address);
try {
Boolean returnValue = false;
if (btDev.getBondState() == BluetoothDevice.BOND_NONE) {
Toast.makeText(Bluetooth1.this, "遠(yuǎn)程設(shè)備發(fā)送藍(lán)牙配對(duì)請(qǐng)求", 5000).show();
//這里只需要createBond就行了
ClsUtils.createBond(btDev.getClass(), btDev);
}else if(btDev.getBondState() == BluetoothDevice.BOND_BONDED){
Toast.makeText(Bluetooth1.this, btDev.getBondState()+" ....正在連接..", 1000).show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class ClickEvent implements View.OnClickListener {
@Override
public void onClick(View v) {
if (v == btnSearch)// 搜索藍(lán)牙設(shè)備,在BroadcastReceiver顯示結(jié)果
{
if (btAdapt.getState() == BluetoothAdapter.STATE_OFF) {// 如果藍(lán)牙還沒(méi)開(kāi)啟
Toast.makeText(Bluetooth1.this, "請(qǐng)先打開(kāi)藍(lán)牙", 1000)
.show();
return;
}
if (btAdapt.isDiscovering())
btAdapt.cancelDiscovery();
lstDevices.clear();
Object[] lstDevice = btAdapt.getBondedDevices().toArray();
for (int i = 0; i < lstDevice.length; i++) {
BluetoothDevice device = (BluetoothDevice) lstDevice[i];
String str = " 已配對(duì)|" + device.getName() + "|"
+ device.getAddress();
lstDevices.add(str); // 獲取設(shè)備名稱(chēng)和mac地址
adtDevices.notifyDataSetChanged();
}
setTitle("本機(jī):" + btAdapt.getAddress());
btAdapt.startDiscovery();
} else if (v == tbtnSwitch) {// 本機(jī)藍(lán)牙啟動(dòng)/關(guān)閉
if (tbtnSwitch.isChecked() == false)
btAdapt.enable();
else if (tbtnSwitch.isChecked() == true)
btAdapt.disable();
} else if (v == btnDis)// 本機(jī)可以被搜索
{
Intent discoverableIntent = new Intent(
BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(
BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);
} else if (v == btnExit) {
try {
if (btSocket != null)
btSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
Bluetooth1.this.finish();
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
PairingRequest.java (重要部分,自動(dòng)配對(duì)主要是這個(gè)部分完成,activity只是創(chuàng)建了一個(gè)配對(duì)請(qǐng)求)
package cn.bluetooth;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class PairingRequest extends BroadcastReceiver {
String strPsw = "0000";
final String ACTION_PAIRING_REQUEST = "android.bluetooth.device.action.PAIRING_REQUEST";
static BluetoothDevice remoteDevice = null;
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(ACTION_PAIRING_REQUEST)) {
BluetoothDevice device = intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
try {
ClsUtils.setPin(device.getClass(), device, strPsw); // 手機(jī)和藍(lán)牙采集器配對(duì)
// ClsUtils.cancelPairingUserInput(device.getClass(),
// device); //一般調(diào)用不成功,前言里面講解過(guò)了
Toast.makeText(context, "配對(duì)信息" + device.getName(), 5000)
.show();
} catch (Exception e) {
// TODO Auto-generated catch block
Toast.makeText(context, "請(qǐng)求連接錯(cuò)誤...", 1000).show();
}
}
// */
// pair(device.getAddress(),strPsw);
}
}
}
AndroidManifest.xml 啟動(dòng)activity,接收廣播
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cn.bluetooth"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="15" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".Bluetooth1"
android:label="@string/title_activity_bluetooth1" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".PairingRequest">
<intent-filter>
<action android:name="android.bluetooth.device.action.PAIRING_REQUEST" />
</intent-filter>
</receiver>
</application>
</manifest>
main.xml 布局
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/LinearLayout1" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnSearch" android:text="btnSearch" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnExit" android:text="btnExit" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/btnDis" android:text="btnDis" /> <ToggleButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/tbtnSwitch" android:text="tbtnSwitch" /> <ListView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/lvDevices" /> </LinearLayout>
- Android藍(lán)牙開(kāi)發(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)開(kāi)發(fā)的解決方案
- android實(shí)現(xiàn)藍(lán)牙文件發(fā)送的實(shí)例代碼,支持多種機(jī)型
- Android手機(jī)通過(guò)藍(lán)牙連接佳博打印機(jī)的實(shí)例代碼
- Android 獲取藍(lán)牙Mac地址的正確方法
- Android藍(lán)牙通信聊天實(shí)現(xiàn)發(fā)送和接受功能
- Android實(shí)現(xiàn)藍(lán)牙(BlueTooth)設(shè)備檢測(cè)連接
相關(guān)文章
Android?Studio實(shí)現(xiàn)帶三角函數(shù)對(duì)數(shù)運(yùn)算功能的高級(jí)計(jì)算器
這篇文章主要為大家詳細(xì)介紹了Android?Studio實(shí)現(xiàn)帶三角函數(shù)對(duì)數(shù)運(yùn)算功能的高級(jí)計(jì)算器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
Android手勢(shì)滑動(dòng)實(shí)現(xiàn)ImageView縮放圖片大小
這篇文章主要為大家詳細(xì)介紹了Android手勢(shì)滑動(dòng)實(shí)現(xiàn)ImageView縮放圖片大小的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-02-02
Android編程實(shí)現(xiàn)ActionBar的home圖標(biāo)動(dòng)畫(huà)切換效果
這篇文章主要介紹了Android編程實(shí)現(xiàn)ActionBar的home圖標(biāo)動(dòng)畫(huà)切換效果,涉及Android布局、樣式、Activity及菜單相關(guān)操作技巧,需要的朋友可以參考下2017-01-01
android的got表HOOK實(shí)現(xiàn)代碼
對(duì)于android的so文件的hook根據(jù)ELF文件特性分為:Got表hook、Sym表hook和inline hook等。今天通過(guò)本文給大家介紹android HOOK實(shí)現(xiàn)got表的實(shí)例代碼,需要的朋友參考下吧2021-08-08
利用Jetpack Compose實(shí)現(xiàn)經(jīng)典俄羅斯方塊游戲
你的童年是否有俄羅斯方塊呢,本文就來(lái)介紹如何通過(guò)Jetpack Compose實(shí)現(xiàn)一個(gè)俄羅斯方塊!感興趣的小伙伴快跟隨小編一起動(dòng)手嘗試一下吧2022-05-05
Android防止按鈕過(guò)快點(diǎn)擊造成多次事件的解決方法
這篇文章主要介紹了Android防止按鈕過(guò)快點(diǎn)擊造成多次事件的解決方法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07
Android編程實(shí)現(xiàn)圖片背景漸變切換與圖層疊加效果
這篇文章主要介紹了Android編程實(shí)現(xiàn)圖片背景漸變切換與圖層疊加效果,涉及Android圖形特效的相關(guān)操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2017-01-01
Android開(kāi)發(fā)實(shí)戰(zhàn)鬧鐘項(xiàng)目
這篇文章主要介紹了Android開(kāi)發(fā)實(shí)戰(zhàn)鬧鐘項(xiàng)目,根據(jù)我們手機(jī)鬧鐘設(shè)計(jì)的一個(gè)鬧鐘APP,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09
Kotlin Lambda表達(dá)式實(shí)踐使用介紹
lambda 本質(zhì)上是可以傳遞給函數(shù)的一小段代碼,Kotlin 與 Java 中的 Lambda 有一定的區(qū)別,除了對(duì) lambda 的全面支持外,還有內(nèi)聯(lián)函數(shù)等簡(jiǎn)潔高效的特性。下面我們來(lái)仔細(xì)看一下2022-12-12

