Android bluetooth开发实践

内容

之前我写过一篇文章,Android Socket编程实践,讲解了如何通过ServerSocket和Socket实现局域网通信,但当时的通信环境是WIFI条件下。其实Android从2.0版本的sdk开始支持蓝牙框架,使设备可以通过蓝牙以无线方式与其他蓝牙设备进行数据交换。

工作流程

首先两个设备上都要有蓝牙设备或者专业一点叫蓝牙适配器,以手机和电脑为例我画了如下流程图。其次在手机上进行扫描,扫描周围蓝蓝牙设备,先找到手机附近的电脑,然后给它发出一个信号需要进行蓝牙的配对,再次返回一个信号说明手机和电脑已经配对成功了,最后配对成功后可以进行文件传输了。这是一个最基本的一个流程。
这里写图片描述

蓝牙API及常见操作

Android提供蓝牙API来执行下面这些不同的操作:

API解析

Android所有关于蓝牙开发的类都在android.bluetooth包下,如下图,只有8个类:

这里写图片描述
但是我们需要用到的就三个而已:BluetoothAdapter、BluetoothDevice、BluetoothServerSocket和BluetoothSocket,下面分别讲解。

BluetoothAdapter
顾名思义,蓝牙适配器,直到我们建立bluetoothSocket连接之前,都需要不断操作BluetoothAdapter里的很多方法,常用的有以下几个:

  • getDefaultAdapter()—静态方法,获取默认BluetoothAdapter,实际上,也只有这一种方法获取BluetoothAdapter
  • getName()—获取本地蓝牙名称
  • getAddress()—获取本地蓝牙地址
  • getState()—获取本地蓝牙适配器当前状态(感觉可能调试的时候更需要)
  • getRemoteDevice(String address)—根据蓝牙地址获取远程蓝牙设备
  • startDiscovery()—开始搜索,这是搜索的第一步
  • cancelDiscovery()—取消搜索,也就是说当我们正在搜索设备的时候调用这个方法将不再继续搜索
  • isDiscovering()—判断当前是否正在查找设备,是返回true
  • enable()—打开蓝牙,这个方法打开蓝牙不会弹出提示,更多的时候我们需要问下用户是否打开,以下这两行代码同样是打开蓝牙,不过会提示用户:
Intent enabler=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler,reCode);
  • disable()—关闭蓝牙
  • isEnabled()—判断蓝牙是否打开,已打开返回true,否则,返回false
  • listenUsingRfcommWithServiceRecord(String name,UUID uuid)—根据名称,UUID创建并返回BluetoothServerSocket,这是创建BluetoothSocket服务器端的第一步(安卓2.3系统及以下用)
  • listenUsingInsecureRfcommWithServiceRecord(String name,UUID uuid)—根据名称,UUID创建并返回BluetoothServerSocket(安卓2.3系统以上用)
  • getScanMode()—获取本地蓝牙扫描状态,枚举值:BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE、BluetoothAdapter.SCAN_MODE_CONNECTABLE、BluetoothAdapter.NONE
  • getBondedDevices()—获取已绑定的设备信息,返回值是Set<\BluetoothDevice>类型

BluetoothDevice
看名字就知道,这个类描述了一个蓝牙设备。

  • getState()—获取蓝牙状态。这里要说一下,只有在 BluetoothAdapter.STATE_ON状态下才可以监听
  • createRfcommSocketToServiceRecord(UUIDuuid)—根据UUID创建并返回一个BluetoothSocket(安卓2.3系统及以下用)
  • createInsecureRfcommSocketToServiceRecord(UUIDuuid)—根据UUID创建并返回一个BluetoothSocket(安卓2.3系统以上用)
    这个方法也是我们获取BluetoothDevice的目的:创建BluetoothSocket
  • getName()—获取设备蓝牙名称
  • getAddress()—获取设备蓝牙地址
  • getBondState()—获取绑定状态,枚举值:BluetoothDevice.BOND_BONDED、BluetoothDevice.BOND_BONDING、BluetoothDevice.BOND_NONE

BluetoothServerSocket
如果去除了Bluetooth相信大家一定再熟悉不过了,既然是Socket,方法就应该都差不多,这个类一种只有三个方法:

  • accept()—接收客户端连接
  • accept(int timeout)—接收客户端连接,并指定连接超时时间 注意: (1)执行这两个方法的时候,直到接收到了客户端的连接请求(或是过期之后),不然都会阻塞线程,所以应该放在新线程里运行!
(2)这两个方法都返回一个BluetoothSocket,最后的连接也是服务器端与客户端的两个BluetoothSocket的连接。
  • close()—关闭ServerSocket

BluetoothSocket
跟BluetoothServerSocket相对,是客户端交互的Socket,一共5个方法:

  • connect()—连接
  • close()—关闭
  • getInputStream()—获取输入流
  • getOutputStream()—获取输出流
  • getRemoteDevice()—获取远程设备,这里指的是获取bluetoothSocket指定连接的那个远程蓝牙设备

扫描其他蓝牙设备

mBtAdapter.startDiscovery()

获取扫描设备列表

定义BroadcastReceiver,关于BroadcastReceiver不多讲了,不是今天的讨论内容,代码如下

 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device.getBondState() != BluetoothDevice.BOND_BONDED) { mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { setProgressBarIndeterminateVisibility(false); setTitle(R.string.select_device); if (mNewDevicesArrayAdapter.getCount() == 0) { String noDevices = getResources().getText(R.string.none_found).toString(); mNewDevicesArrayAdapter.add(noDevices);
                }
            }
        }
    };

这样,每当查找到新设备或是搜索完成,相应的操作都在上段代码的两个if里执行了,不过前提是你要先注册BroadcastReceiver,具体代码如下

/** Register for broadcasts when a device is discovered*/
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, filter); filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);

注:你能扫描到其他设备,前提是其他设备当前处于可被扫描状态,那么如何让自己处于可被扫描状态呢?

if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); /**300S内可被扫描*/ discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
      startActivity(discoverableIntent);
}

连接到其他设备

我们知道Socket通信是通过socket实现两端的连接。蓝牙也一样,是通过BluetoothSocket建立两端的连接,服务器端(BluetoothServerSocket)和客户端(BluetoothSocket)需要指定同样的UUID,才能建立连接,因为建立连接的方法会阻塞线程,所以服务器端和客户端都应该启动新的线程。
1)服务器端:

/**UUID格式一般是"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"可到http://www.uuidgenerator.com 申请*/
BluetoothServerSocket serverSocket = mAdapter.listenUsingInsecureRfcommWithServiceRecord(NAME_INSECURE, MY_UUID_INSECURE);
.....
socket = mmServerSocket.accept();

2)客户端:

/**还记得我们刚才在BroadcastReceiver获取了BLuetoothDevice么?*/
BluetoothSocket clienSocket=device.createInsecureRfcommSocketToServiceRecord(MY_UUID_INSECURE);
clienSocket.connect();

之后双方就可以使用两端的BluetoothSocket通过流进行通信。

开发实战

首先,要操作蓝牙,先要在AndroidManifest.xml里加入权限:

<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permissionandroid:name="android.permission.BLUETOOTH" />

主页面MainActivity代码:

/** * This is the main Activity that displays the current chat session. */
public class MainActivity extends Activity { private static final String TAG = "BluetoothChat"; private static final boolean D = true; public static final int MESSAGE_STATE_CHANGE = 1; public static final int MESSAGE_READ = 2; public static final int MESSAGE_WRITE = 3; public static final int MESSAGE_DEVICE_NAME = 4; public static final int MESSAGE_TOAST = 5; public static final String DEVICE_NAME = "device_name"; public static final String TOAST = "toast"; private static final int REQUEST_CONNECT_DEVICE_SECURE = 1; private static final int REQUEST_CONNECT_DEVICE_INSECURE = 2; private static final int REQUEST_ENABLE_BT = 3; private ListView mConversationView; private EditText mOutEditText; private Button mSendButton; private String mConnectedDeviceName = null; private ArrayAdapter<String> mConversationArrayAdapter; private StringBuffer mOutStringBuffer; private BluetoothAdapter mBluetoothAdapter = null; private BluetoothChatService mChatService = null; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (D) Log.e(TAG, "+++ ON CREATE +++"); setContentView(R.layout.main); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); if (mBluetoothAdapter == null) { Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show(); finish(); return; } } @Override public void onStart() { super.onStart(); if (D) Log.e(TAG, "++ ON START ++"); if (!mBluetoothAdapter.isEnabled()) { Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); startActivityForResult(enableIntent, REQUEST_ENABLE_BT); } else { if (mChatService == null) setupChat(); } } @Override public synchronized void onResume() { super.onResume(); if (D) Log.e(TAG, "+ ON RESUME +"); if (mChatService != null) { if (mChatService.getState() == BluetoothChatService.STATE_NONE) { mChatService.start(); } } } private void setupChat() { Log.d(TAG, "setupChat()"); mConversationArrayAdapter = new ArrayAdapter<String>(this, R.layout.message); mConversationView = (ListView) findViewById(R.id.in); mConversationView.setAdapter(mConversationArrayAdapter); mOutEditText = (EditText) findViewById(R.id.edit_text_out); mOutEditText.setOnEditorActionListener(mWriteListener); mSendButton = (Button) findViewById(R.id.button_send); mSendButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { TextView view = (TextView) findViewById(R.id.edit_text_out); String message = view.getText().toString(); sendMessage(message); } }); mChatService = new BluetoothChatService(this, mHandler); mOutStringBuffer = new StringBuffer(""); } @Override public synchronized void onPause() { super.onPause(); if (D) Log.e(TAG, "- ON PAUSE -"); } @Override public void onStop() { super.onStop(); if (D) Log.e(TAG, "-- ON STOP --"); } @Override public void onDestroy() { super.onDestroy(); if (mChatService != null) mChatService.stop(); if (D) Log.e(TAG, "--- ON DESTROY ---"); } private void ensureDiscoverable() { if (D) Log.d(TAG, "ensure discoverable"); if (mBluetoothAdapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) { Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); startActivity(discoverableIntent); } } /** * Sends a message. * * @param message A string of text to send. */ private void sendMessage(String message) { if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) { Toast.makeText(this, R.string.not_connected, Toast.LENGTH_SHORT).show(); return; } if (message.length() > 0) { byte[] send = message.getBytes(); mChatService.write(send); mOutStringBuffer.setLength(0); mOutEditText.setText(mOutStringBuffer); } } private TextView.OnEditorActionListener mWriteListener = new TextView.OnEditorActionListener() { public boolean onEditorAction(TextView view, int actionId, KeyEvent event) { if (actionId == EditorInfo.IME_NULL && event.getAction() == KeyEvent.ACTION_UP) { String message = view.getText().toString(); sendMessage(message); } if (D) Log.i(TAG, "END onEditorAction"); return true; } }; private final void setStatus(int resId) { final ActionBar actionBar = getActionBar(); actionBar.setSubtitle(resId); } private final void setStatus(CharSequence subTitle) { final ActionBar actionBar = getActionBar(); actionBar.setSubtitle(subTitle); } private final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_STATE_CHANGE: if (D) Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1); switch (msg.arg1) { case BluetoothChatService.STATE_CONNECTED: setStatus(getString(R.string.title_connected_to, mConnectedDeviceName)); mConversationArrayAdapter.clear(); break; case BluetoothChatService.STATE_CONNECTING: setStatus(R.string.title_connecting); break; case BluetoothChatService.STATE_LISTEN: case BluetoothChatService.STATE_NONE: setStatus(R.string.title_not_connected); break; } break; case MESSAGE_WRITE: byte[] writeBuf = (byte[]) msg.obj; String writeMessage = new String(writeBuf); mConversationArrayAdapter.add("Me: " + writeMessage); break; case MESSAGE_READ: byte[] readBuf = (byte[]) msg.obj; String readMessage = new String(readBuf, 0, msg.arg1); mConversationArrayAdapter.add(mConnectedDeviceName + ": " + readMessage); break; case MESSAGE_DEVICE_NAME: mConnectedDeviceName = msg.getData().getString(DEVICE_NAME); Toast.makeText(getApplicationContext(), "Connected to " + mConnectedDeviceName, Toast.LENGTH_SHORT).show(); break; case MESSAGE_TOAST: Toast.makeText(getApplicationContext(), msg.getData().getString(TOAST), Toast.LENGTH_SHORT).show(); break; } } }; public void onActivityResult(int requestCode, int resultCode, Intent data) { if (D) Log.d(TAG, "onActivityResult " + resultCode); switch (requestCode) { case REQUEST_CONNECT_DEVICE_SECURE: if (resultCode == Activity.RESULT_OK) { connectDevice(data, true); } break; case REQUEST_CONNECT_DEVICE_INSECURE: if (resultCode == Activity.RESULT_OK) { connectDevice(data, false); } break; case REQUEST_ENABLE_BT: if (resultCode == Activity.RESULT_OK) { setupChat(); } else { Log.d(TAG, "BT not enabled"); Toast.makeText(this, R.string.bt_not_enabled_leaving, Toast.LENGTH_SHORT).show(); finish(); } } } private void connectDevice(Intent data, boolean secure) { String address = data.getExtras() .getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS); BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address); mChatService.connect(device, secure); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.option_menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { Intent serverIntent = null; switch (item.getItemId()) { case R.id.secure_connect_scan: serverIntent = new Intent(this, DeviceListActivity.class); startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE_SECURE); return true; case R.id.insecure_connect_scan: serverIntent = new Intent(this, DeviceListActivity.class); startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE_INSECURE); return true; case R.id.discoverable: ensureDiscoverable(); return true; } return false;
    }
}

搜索其他设备界面DeviceListActivity代码:

/** * This Activity appears as a dialog. It lists any paired devices and * devices detected in the area after discovery. When a device is chosen * by the user, the MAC address of the device is sent back to the parent * Activity in the result Intent. */
public class DeviceListActivity extends Activity { private static final String TAG = "DeviceListActivity"; private static final boolean D = true; public static String EXTRA_DEVICE_ADDRESS = "device_address"; private BluetoothAdapter mBtAdapter; private ArrayAdapter<String> mPairedDevicesArrayAdapter; private ArrayAdapter<String> mNewDevicesArrayAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.device_list); setResult(Activity.RESULT_CANCELED); Button scanButton = (Button) findViewById(R.id.button_scan); scanButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { doDiscovery(); v.setVisibility(View.GONE); } }); mPairedDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name); mNewDevicesArrayAdapter = new ArrayAdapter<String>(this, R.layout.device_name); ListView pairedListView = (ListView) findViewById(R.id.paired_devices); pairedListView.setAdapter(mPairedDevicesArrayAdapter); pairedListView.setOnItemClickListener(mDeviceClickListener); ListView newDevicesListView = (ListView) findViewById(R.id.new_devices); newDevicesListView.setAdapter(mNewDevicesArrayAdapter); newDevicesListView.setOnItemClickListener(mDeviceClickListener); IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND); this.registerReceiver(mReceiver, filter); filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); this.registerReceiver(mReceiver, filter); mBtAdapter = BluetoothAdapter.getDefaultAdapter(); Set<BluetoothDevice> pairedDevices = mBtAdapter.getBondedDevices(); if (pairedDevices.size() > 0) { findViewById(R.id.title_paired_devices).setVisibility(View.VISIBLE); for (BluetoothDevice device : pairedDevices) { mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } } else { String noDevices = getResources().getText(R.string.none_paired).toString(); mPairedDevicesArrayAdapter.add(noDevices); } } @Override protected void onDestroy() { super.onDestroy(); if (mBtAdapter != null) { mBtAdapter.cancelDiscovery(); } this.unregisterReceiver(mReceiver); } /** * Start device discover with the BluetoothAdapter */ private void doDiscovery() { if (D) Log.d(TAG, "doDiscovery()"); setProgressBarIndeterminateVisibility(true); setTitle(R.string.scanning); findViewById(R.id.title_new_devices).setVisibility(View.VISIBLE); if (mBtAdapter.isDiscovering()) { mBtAdapter.cancelDiscovery(); } mBtAdapter.startDiscovery(); } private OnItemClickListener mDeviceClickListener = new OnItemClickListener() { public void onItemClick(AdapterView<?> av, View v, int arg2, long arg3) { mBtAdapter.cancelDiscovery(); String info = ((TextView) v).getText().toString(); String address = info.substring(info.length() - 17); Intent intent = new Intent(); intent.putExtra(EXTRA_DEVICE_ADDRESS, address); setResult(Activity.RESULT_OK, intent); finish(); } }; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (BluetoothDevice.ACTION_FOUND.equals(action)) { BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); if (device.getBondState() != BluetoothDevice.BOND_BONDED) { mNewDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress()); } } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { setProgressBarIndeterminateVisibility(false); setTitle(R.string.select_device); if (mNewDevicesArrayAdapter.getCount() == 0) {
                    String noDevices = getResources().getText(R.string.none_found).toString();
                    mNewDevicesArrayAdapter.add(noDevices);
                }
            }
        }
    };
}

最后是双方建立连接和通信的服务:

这/** * This class does all the work for setting up and managing Bluetooth * connections with other devices. It has a thread that listens for * incoming connections, a thread for connecting with a device, and a * thread for performing data transmissions when connected. */
public class BluetoothChatService { private static final String TAG = "BluetoothChatService"; private static final boolean D = true; private static final String NAME_SECURE = "BluetoothChatSecure"; private static final String NAME_INSECURE = "BluetoothChatInsecure"; private static final UUID MY_UUID_SECURE = UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66"); private static final UUID MY_UUID_INSECURE = UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66"); private final BluetoothAdapter mAdapter; private final Handler mHandler; private AcceptThread mSecureAcceptThread; private AcceptThread mInsecureAcceptThread; private ConnectThread mConnectThread; private ConnectedThread mConnectedThread; private int mState; public static final int STATE_NONE = 0; public static final int STATE_LISTEN = 1; public static final int STATE_CONNECTING = 2; public static final int STATE_CONNECTED = 3; /** * Constructor. Prepares a new MainActivity session. * @param context The UI Activity Context * @param handler A Handler to send messages back to the UI Activity */ public BluetoothChatService(Context context, Handler handler) { mAdapter = BluetoothAdapter.getDefaultAdapter(); mState = STATE_NONE; mHandler = handler; } /** * Set the current state of the chat connection * @param state An integer defining the current connection state */ private synchronized void setState(int state) { if (D) Log.d(TAG, "setState() " + mState + " -> " + state); mState = state; mHandler.obtainMessage(MainActivity.MESSAGE_STATE_CHANGE, state, -1).sendToTarget(); } /** * Return the current connection state. */ public synchronized int getState() { return mState; } /** * Start the chat service. Specifically start AcceptThread to begin a * session in listening (server) mode. Called by the Activity onResume() */ public synchronized void start() { if (D) Log.d(TAG, "start"); if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } setState(STATE_LISTEN); if (mSecureAcceptThread == null) { mSecureAcceptThread = new AcceptThread(true); mSecureAcceptThread.start(); } if (mInsecureAcceptThread == null) { mInsecureAcceptThread = new AcceptThread(false); mInsecureAcceptThread.start(); } } /** * Start the ConnectThread to initiate a connection to a remote device. * @param device The BluetoothDevice to connect * @param secure Socket Security type - Secure (true) , Insecure (false) */ public synchronized void connect(BluetoothDevice device, boolean secure) { if (D) Log.d(TAG, "connect to: " + device); if (mState == STATE_CONNECTING) { if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} } if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} mConnectThread = new ConnectThread(device, secure); mConnectThread.start(); setState(STATE_CONNECTING); } /** * Start the ConnectedThread to begin managing a Bluetooth connection * @param socket The BluetoothSocket on which the connection was made * @param device The BluetoothDevice that has been connected */ public synchronized void connected(BluetoothSocket socket, BluetoothDevice device, final String socketType) { if (D) Log.d(TAG, "connected, Socket Type:" + socketType); if (mConnectThread != null) {mConnectThread.cancel(); mConnectThread = null;} if (mConnectedThread != null) {mConnectedThread.cancel(); mConnectedThread = null;} if (mSecureAcceptThread != null) { mSecureAcceptThread.cancel(); mSecureAcceptThread = null; } if (mInsecureAcceptThread != null) { mInsecureAcceptThread.cancel(); mInsecureAcceptThread = null; } mConnectedThread = new ConnectedThread(socket, socketType); mConnectedThread.start(); Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_DEVICE_NAME); Bundle bundle = new Bundle(); bundle.putString(MainActivity.DEVICE_NAME, device.getName()); msg.setData(bundle); mHandler.sendMessage(msg); setState(STATE_CONNECTED); } /** * Stop all threads */ public synchronized void stop() { if (D) Log.d(TAG, "stop"); if (mConnectThread != null) { mConnectThread.cancel(); mConnectThread = null; } if (mConnectedThread != null) { mConnectedThread.cancel(); mConnectedThread = null; } if (mSecureAcceptThread != null) { mSecureAcceptThread.cancel(); mSecureAcceptThread = null; } if (mInsecureAcceptThread != null) { mInsecureAcceptThread.cancel(); mInsecureAcceptThread = null; } setState(STATE_NONE); } /** * Write to the ConnectedThread in an unsynchronized manner * @param out The bytes to write * @see ConnectedThread#write(byte[]) */ public void write(byte[] out) { ConnectedThread r; synchronized (this) { if (mState != STATE_CONNECTED) return; r = mConnectedThread; } r.write(out); } /** * Indicate that the connection attempt failed and notify the UI Activity. */ private void connectionFailed() { Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString(MainActivity.TOAST, "Unable to connect device"); msg.setData(bundle); mHandler.sendMessage(msg); BluetoothChatService.this.start(); } /** * Indicate that the connection was lost and notify the UI Activity. */ private void connectionLost() { Message msg = mHandler.obtainMessage(MainActivity.MESSAGE_TOAST); Bundle bundle = new Bundle(); bundle.putString(MainActivity.TOAST, "Device connection was lost"); msg.setData(bundle); mHandler.sendMessage(msg); BluetoothChatService.this.start(); } /** * This thread runs while listening for incoming connections. It behaves * like a server-side client. It runs until a connection is accepted * (or until cancelled). */ private class AcceptThread extends Thread { private final BluetoothServerSocket mmServerSocket; private String mSocketType; public AcceptThread(boolean secure) { BluetoothServerSocket tmp = null; mSocketType = secure ? "Secure":"Insecure"; try { if (secure) { tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME_SECURE, MY_UUID_SECURE); } else { tmp = mAdapter.listenUsingInsecureRfcommWithServiceRecord( NAME_INSECURE, MY_UUID_INSECURE); } } catch (IOException e) { Log.e(TAG, "Socket Type: " + mSocketType + "listen() failed", e); } mmServerSocket = tmp; } public void run() { if (D) Log.d(TAG, "Socket Type: " + mSocketType + "BEGIN mAcceptThread" + this); setName("AcceptThread" + mSocketType); BluetoothSocket socket = null; while (mState != STATE_CONNECTED) { try { socket = mmServerSocket.accept(); } catch (IOException e) { Log.e(TAG, "Socket Type: " + mSocketType + "accept() failed", e); break; } if (socket != null) { synchronized (BluetoothChatService.this) { switch (mState) { case STATE_LISTEN: case STATE_CONNECTING: connected(socket, socket.getRemoteDevice(), mSocketType); break; case STATE_NONE: case STATE_CONNECTED: try { socket.close(); } catch (IOException e) { Log.e(TAG, "Could not close unwanted socket", e); } break; } } } } if (D) Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType); } public void cancel() { if (D) Log.d(TAG, "Socket Type" + mSocketType + "cancel " + this); try { mmServerSocket.close(); } catch (IOException e) { Log.e(TAG, "Socket Type" + mSocketType + "close() of server failed", e); } } } /** * This thread runs while attempting to make an outgoing connection * with a device. It runs straight through; the connection either * succeeds or fails. */ private class ConnectThread extends Thread { private final BluetoothSocket mmSocket; private final BluetoothDevice mmDevice; private String mSocketType; public ConnectThread(BluetoothDevice device, boolean secure) { mmDevice = device; BluetoothSocket tmp = null; mSocketType = secure ? "Secure" : "Insecure"; try { if (secure) { tmp = device.createRfcommSocketToServiceRecord( MY_UUID_SECURE); } else { tmp = device.createInsecureRfcommSocketToServiceRecord( MY_UUID_INSECURE); } } catch (IOException e) { Log.e(TAG, "Socket Type: " + mSocketType + "create() failed", e); } mmSocket = tmp; } public void run() { Log.i(TAG, "BEGIN mConnectThread SocketType:" + mSocketType); setName("ConnectThread" + mSocketType); mAdapter.cancelDiscovery(); try { mmSocket.connect(); } catch (IOException e) { try { mmSocket.close(); } catch (IOException e2) { Log.e(TAG, "unable to close() " + mSocketType + " socket during connection failure", e2); } connectionFailed(); return; } synchronized (BluetoothChatService.this) { mConnectThread = null; } connected(mmSocket, mmDevice, mSocketType); } public void cancel() { try { mmSocket.close(); } catch (IOException e) { Log.e(TAG, "close() of connect " + mSocketType + " socket failed", e); } } } /** * This thread runs during a connection with a remote device. * It handles all incoming and outgoing transmissions. */ private class ConnectedThread extends Thread { private final BluetoothSocket mmSocket; private final InputStream mmInStream; private final OutputStream mmOutStream; public ConnectedThread(BluetoothSocket socket, String socketType) { Log.d(TAG, "create ConnectedThread: " + socketType); mmSocket = socket; InputStream tmpIn = null; OutputStream tmpOut = null; try { tmpIn = socket.getInputStream(); tmpOut = socket.getOutputStream(); } catch (IOException e) { Log.e(TAG, "temp sockets not created", e); } mmInStream = tmpIn; mmOutStream = tmpOut; } public void run() { Log.i(TAG, "BEGIN mConnectedThread"); byte[] buffer = new byte[1024]; int bytes; while (true) { try { bytes = mmInStream.read(buffer); mHandler.obtainMessage(MainActivity.MESSAGE_READ, bytes, -1, buffer).sendToTarget(); } catch (IOException e) { Log.e(TAG, "disconnected", e); connectionLost(); BluetoothChatService.this.start(); break; } } } /** * Write to the connected OutStream. * @param buffer The bytes to write */ public void write(byte[] buffer) { try { mmOutStream.write(buffer); mHandler.obtainMessage(MainActivity.MESSAGE_WRITE, -1, -1, buffer).sendToTarget(); } catch (IOException e) { Log.e(TAG, "Exception during write", e); } } public void cancel() { try { mmSocket.close(); } catch (IOException e) { Log.e(TAG, "close() of connect socket failed", e);
            }
        }
    }
}
总结
本文介绍了Android蓝牙编程的基本流程和API使用。首先,两个设备需具备蓝牙适配器,手机通过扫描找到附近设备并进行配对,配对成功后可进行文件传输。Android提供了蓝牙API,主要类包括BluetoothAdapter、BluetoothDevice、BluetoothServerSocket和BluetoothSocket。BluetoothAdapter用于管理蓝牙状态和设备发现,BluetoothDevice表示蓝牙设备,BluetoothServerSocket用于接收连接请求,BluetoothSocket用于客户端与服务器的交互。文章详细讲解了如何扫描设备、获取设备列表、注册BroadcastReceiver以处理设备发现事件,以及如何连接到其他设备。通过这些API,开发者可以实现蓝牙数据传输功能。