首先看看程序的效果:
在整个开发过程中涉及的几个关键步骤
1)判断蓝牙设备是否可用
2)若蓝牙设备可用,判断是否开启
是:则不操作
否:开启蓝牙设备
3)让设备可见(在一定的时间范围内)
4)查看已经连接过的设备
5)扫描附近的设备
6)连接设备
7)建立socket连接,读写消息
8)退出程序时结束扫描和连接
程序架构:
ChatActivity:UI的变化 接收(发送)来自BluetoothChatService的消息,接收 DeviceListActivity的消息
DeviceListActivity:find device 选择device,返回device的Mac address
BluetoothChatService:
接口:start() stop() Construction(Context,Handler) write(byte [])
connect(BluetoothDevice , boolean)
处理的工作:
监听连接 连接 read write
各线程含义:
ConnectThread:主动发起蓝牙连接线程(事件触发)
ConnectedThread:蓝牙连接完成后读写消息(蓝牙连接成功后启动)
AcceptThread:监听来自其他设备的蓝牙连接,若蓝牙连接成功,启动ConnectedThread读写(默认启动)
监听蓝牙连接线程(相当于Socket编程中的Server):
private class AcceptThread extends Thread {
// The local server socket
private final BluetoothServerSocket mmServerSocket;
private String mSocketType;
public AcceptThread(boolean secure) {
BluetoothServerSocket tmp = null;
mSocketType = secure ? "Secure" : "Insecure";
// Create a new listening server socket
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() {
Log.d(TAG, "Socket Type: " + mSocketType +
"BEGIN mAcceptThread" + this);
setName("AcceptThread" + mSocketType);
BluetoothSocket socket = null;
// Listen to the server socket if we're not connected
while (mState != STATE_CONNECTED) {
try {
// This is a blocking call and will only return on a
// successful connection or an exception
socket = mmServerSocket.accept();
} catch (IOException e) {
Log.e(TAG, "Socket Type: " + mSocketType + "accept() failed", e);
break;
}
// If a connection was accepted
if (socket != null) {
synchronized (BluetoothChatService.this) {
switch (mState) {
case STATE_LISTEN:
case STATE_CONNECTING:
// Situation normal. Start the connected thread.
connected(socket, socket.getRemoteDevice(),
mSocketType);
break;
case STATE_NONE:
case STATE_CONNECTED:
// Either not ready or already connected. Terminate new socket.
try {
socket.close();
} catch (IOException e) {
Log.e(TAG, "Could not close unwanted socket", e);
}
break;
}
}
}
}
Log.i(TAG, "END mAcceptThread, socket Type: " + mSocketType);
}
public void cancel() {
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);
}
}
}
主动连接其他蓝牙设备的线程:
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";
// Get a BluetoothSocket for a connection with the
// given BluetoothDevice
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();
// 创建一个 BluetoothSocket 连接
try {
// This is a blocking call and will only return on a
// successful connection or an exception
mmSocket.connect();
} catch (IOException e) {
// Close the socket
try {
mmSocket.close();
} catch (IOException e2) {
Log.e(TAG, "unable to close() " + mSocketType +
" socket during connection failure", e2);
}
connectionFailed();
return;
}
// 已经完成蓝牙连接,重置ConnectThread
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);
}
}
}
蓝牙连接成功后管理读写的线程:
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;
// Get the BluetoothSocket input and output streams
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 {
// 读InputStream
bytes = mmInStream.read(buffer);
// 发送读取的消息到UI Activity
mHandler.obtainMessage(Constants.MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
Log.e(TAG, "disconnected", e);
connectionLost();
// Start the service over to restart listening mode
BluetoothChatService.this.start();
break;
}
}
}
/**
* Write to the connected OutStream.
*
* @param buffer The bytes to write
*/
public void write(byte[] buffer) {
try {
mmOutStream.write(buffer);
// Share the sent message back to the UI Activity
mHandler.obtainMessage(Constants.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);
}
}
}
主Activity(ChatActivity)
package com.example.mybluetoothchat;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.example.mybluetoothchat.R;
import com.example.mybluetoothchat.ChatMsgViewAdapter;
import com.example.mybluetoothchat.ChatMsgEntity;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
public class ChatActivity extends ActionBarActivity{
private Button mBtnSend;
private EditText mEditTextContent;
private ChatMsgViewAdapter mAdapter;
private ListView mListView;
private List<ChatMsgEntity> mDataArrays = new ArrayList<ChatMsgEntity>();
private ActionBar actionBar;
// Intent request codes
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 String mConnectedDeviceName = null;
/**
* Array adapter for the conversation thread
*/
private ArrayAdapter<String> mConversationArrayAdapter;
/**
* String buffer for outgoing messages
*/
private StringBuffer mOutStringBuffer;
/**
* Local Bluetooth adapter
*/
private BluetoothAdapter mBluetoothAdapter = null;
/**
* Member object for the chat services
*/
private BluetoothChatService mChatService = null;
/**
* The Handler that gets information back from the BluetoothChatService
*/
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case Constants.MESSAGE_STATE_CHANGE:
switch (msg.arg1) {
case BluetoothChatService.STATE_CONNECTED:
setStatus(1,mConnectedDeviceName,"连接到 " + mConnectedDeviceName);
break;
case BluetoothChatService.STATE_CONNECTING:
setStatus("连接中。。。");
break;
case BluetoothChatService.STATE_LISTEN:
case BluetoothChatService.STATE_NONE:
setStatus("无连接");
break;
}
break;
case Constants.MESSAGE_WRITE:
byte[] writeBuf = (byte[]) msg.obj;
// construct a string from the buffer
String writeMessage = new String(writeBuf);
//mConversationArrayAdapter.add("Me: " + writeMessage);
send(writeMessage);
break;
case Constants.MESSAGE_READ:
byte[] readBuf = (byte[]) msg.obj;
// construct a string from the valid bytes in the buffer
String readMessage = new String(readBuf, 0, msg.arg1);
receive(readMessage);
break;
case Constants.MESSAGE_DEVICE_NAME:
// save the connected device's name
mConnectedDeviceName = msg.getData().getString(Constants.DEVICE_NAME);
if (null != this) {
Toast.makeText(ChatActivity.this, "Connected to "
+ mConnectedDeviceName, Toast.LENGTH_SHORT).show();
}
break;
case Constants.MESSAGE_TOAST:
if (null != this) {
Toast.makeText(ChatActivity.this, msg.getData().getString(Constants.TOAST),
Toast.LENGTH_SHORT).show();
}
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//requestWindowFeature(Window.FEATURE_NO_TITLE);//
actionBar=getSupportActionBar();
actionBar.setTitle("蓝牙聊天");
setContentView(R.layout.activity_chat);
initView();
initData();
//获得BluetoothAdapter
mBluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
//判断有没有蓝牙设备
if(mBluetoothAdapter==null){
Log.e("错误", "设备没有蓝牙模块");
finish();
}
}
@Override
protected void onStart() {
super.onStart();
/** 打开蓝牙设备*/
if (!mBluetoothAdapter.isEnabled()){
Intent enableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableIntent, REQUEST_ENABLE_BT);
}
else if (mChatService==null){
setupChat();
}
}
@Override
public void onDestroy() {
super.onDestroy();
if (mChatService != null) {
mChatService.stop();
}
}
@Override
public void onResume() {
super.onResume();
// Performing this check in onResume() covers the case in which BT was
// not enabled during onStart(), so we were paused to enable it...
// onResume() will be called when ACTION_REQUEST_ENABLE activity returns.
if (mChatService != null) {
// Only if the state is STATE_NONE, do we know that we haven't started already
if (mChatService.getState() == BluetoothChatService.STATE_NONE) {
// Start the Bluetooth chat services
mChatService.start();
}
}
}
/**
* Set up the UI and background operations for chat.
*/
private void setupChat() {
mBtnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
/** 发送消息的相关处理*/
sendMessage();
}
});
// Initialize the BluetoothChatService to perform bluetooth connections
mChatService = new BluetoothChatService(this, mHandler);
// Initialize the buffer for outgoing messages
mOutStringBuffer = new StringBuffer("");
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CONNECT_DEVICE_SECURE:
// When DeviceListActivity returns with a device to connect
if (resultCode == Activity.RESULT_OK) {
connectDevice(data, true);
}
break;
case REQUEST_ENABLE_BT:
// When the request to enable Bluetooth returns
if (resultCode == Activity.RESULT_OK) {
// Bluetooth is now enabled, so set up a chat session
setupChat();
} else {
// User did not enable Bluetooth or an error occurred
Toast.makeText(this, "蓝牙开启失败!",
Toast.LENGTH_SHORT).show();
finish();
}
}
}
/**
*
* 连接设备
*
* */
private void connectDevice(Intent data, boolean secure) {
// Get the device MAC address
String address = data.getExtras()
.getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
// Get the BluetoothDevice object
BluetoothDevice device = mBluetoothAdapter.getRemoteDevice(address);
// Attempt to connect to the device
mChatService.connect(device, secure);
}
/**
* 让本设备可见
*/
private void ensureDiscoverable() {
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);
}
}
/**
* Updates the status on the action bar.
*
* @param resId a string resource ID
*/
private void setStatus(int resId) {
if (null == this) {
return;
}
final ActionBar actionBar = getSupportActionBar();
if (null == actionBar) {
return;
}
actionBar.setSubtitle(resId);
}
private void setStatus(int ok,CharSequence Title ,CharSequence subTitle){
final ActionBar actionBar = getSupportActionBar();
actionBar.setTitle(Title);
actionBar.setSubtitle(subTitle);
}
/**
* Updates the status on the action bar.
*
* @param subTitle status
*/
private void setStatus(CharSequence subTitle) {
if (null == this) {
return;
}
final ActionBar actionBar = getSupportActionBar();
if (null == actionBar) {
return;
}
actionBar.setSubtitle(subTitle);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_chat, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
//noinspection SimplifiableIfStatement
if (id == R.id.action_connect) {
Intent serverIntent = new Intent(this, DeviceListActivity.class);
startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE_SECURE);
return true;
}
if (id == R.id.action_show_bluetooth){
ensureDiscoverable();
return true;
}
return super.onOptionsItemSelected(item);
}
private void initView() {
mListView = (ListView) findViewById(R.id.chat_list_view);
mBtnSend = (Button) findViewById(R.id.btn_send);
mEditTextContent = (EditText) findViewById(R.id.et_sendmessage);
}
private final static int COUNT = 8;
//初始化要显示的数据
private void initData() {
mAdapter = new ChatMsgViewAdapter(this, mDataArrays);
mListView.setAdapter(mAdapter);
}
private void sendMessage() {
if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {
Toast.makeText(this, "未连接蓝牙设备!", Toast.LENGTH_SHORT).show();
mEditTextContent.setText("");
return;
}
String contString = mEditTextContent.getText().toString();
if (contString.length() > 0){
// Get the message bytes and tell the BluetoothChatService to write
byte[] send = contString.getBytes();
mChatService.write(send);
// Reset out string buffer to zero and clear the edit text field
mOutStringBuffer.setLength(0);
mEditTextContent.setText(mOutStringBuffer);
}
}
private void send(String msg)
{
// Check that we're actually connected before trying anything
/*if (mChatService.getState() != BluetoothChatService.STATE_CONNECTED) {
Toast.makeText(this, "未连接蓝牙设备!", Toast.LENGTH_SHORT).show();
mEditTextContent.setText("");
return;
}
String contString = mEditTextContent.getText().toString();
if (contString.length() > 0)
mEditTextContent.setText("");
{*/
ChatMsgEntity entity = new ChatMsgEntity();
entity.setDate(getDate());
entity.setName("我");
entity.setMsgType(false);
entity.setText(msg);
mDataArrays.add(entity);
mAdapter.notifyDataSetChanged();
mListView.setSelection(mListView.getCount() - 1);
}
private void receive(String msg)
{
ChatMsgEntity entity = new ChatMsgEntity();
entity.setDate(getDate());
entity.setName(mConnectedDeviceName);
entity.setMsgType(true);
entity.setText(msg);
mDataArrays.add(entity);
mAdapter.notifyDataSetChanged();
mListView.setSelection(mListView.getCount() - 1);
}
private String getDate() {
Calendar c = Calendar.getInstance();
String year = String.valueOf(c.get(Calendar.YEAR));
String month = String.valueOf(c.get(Calendar.MONTH));
String day = String.valueOf(c.get(Calendar.DAY_OF_MONTH) + 1);
String hour = String.valueOf(c.get(Calendar.HOUR_OF_DAY));
String mins = String.valueOf(c.get(Calendar.MINUTE));
StringBuffer sbBuffer = new StringBuffer();
sbBuffer.append(year + "-" + month + "-" + day + " " + hour + ":" + mins);
return sbBuffer.toString();
}
}
聊天消息实体类:
package com.example.mybluetoothchat;
public class ChatMsgEntity {
private static final String TAG = ChatMsgEntity.class.getSimpleName();
private String name;
private String date;
private String text;
private boolean msgType = true;
public boolean getMsgType() {
return msgType;
}
public void setMsgType(boolean msgType) {
this.msgType = msgType;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDate() {
return date;
}
public void setDate(String date) {
this.date = date;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public ChatMsgEntity() {
}
public ChatMsgEntity(String name, String date, String text, boolean msgType) {
this.name = name;
this.date = date;
this.text = text;
this.msgType = msgType;
}
}
消息显示ListView的适配器:
package com.example.mybluetoothchat;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;
import com.example.mybluetoothchat.R;
import com.example.mybluetoothchat.ChatMsgEntity;
import java.util.List;
public class ChatMsgViewAdapter extends BaseAdapter{
public static interface IMsgViewType
{
int IMVT_COM_MSG = 0;
int IMVT_TO_MSG = 1;
}
private static final String TAG = ChatMsgViewAdapter.class.getSimpleName();
private List<ChatMsgEntity> data;
private Context context;
private LayoutInflater mInflater;
public ChatMsgViewAdapter(Context context, List<ChatMsgEntity> data) {
this.context = context;
this.data = data;
mInflater = LayoutInflater.from(context);
}
public int getCount() {
return data.size();
}
public Object getItem(int position) {
return data.get(position);
}
public long getItemId(int position) {
return position;
}
public int getItemViewType(int position) {
// TODO Auto-generated method stub
ChatMsgEntity entity = data.get(position);
if (entity.getMsgType())
{
return IMsgViewType.IMVT_COM_MSG;
}else{
return IMsgViewType.IMVT_TO_MSG;
}
}
public int getViewTypeCount() {
// TODO Auto-generated method stub
return 2;
}
public View getView(int position, View convertView, ViewGroup parent) {
ChatMsgEntity entity = data.get(position);
boolean isComMsg = entity.getMsgType();
ViewHolder viewHolder = null;
if (convertView == null)
{
if (isComMsg)
{
convertView = mInflater.inflate(R.layout.chatting_item_msg_text_left, null);
}else{
convertView = mInflater.inflate(R.layout.chatting_item_msg_text_right, null);
}
viewHolder = new ViewHolder();
viewHolder.tvSendTime = (TextView) convertView.findViewById(R.id.tv_sendtime);
viewHolder.tvUserName = (TextView) convertView.findViewById(R.id.tv_username);
viewHolder.tvContent = (TextView) convertView.findViewById(R.id.tv_chatcontent);
viewHolder.isComMsg = isComMsg;
convertView.setTag(viewHolder);
}else{
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.tvSendTime.setText(entity.getDate());
viewHolder.tvUserName.setText(entity.getName());
viewHolder.tvContent.setText(entity.getText());
return convertView;
}
static class ViewHolder {
public TextView tvSendTime;
public TextView tvUserName;
public TextView tvContent;
public boolean isComMsg = true;
}
}
完整项目见源代码:http://download.csdn.net/detail/u012885690/8964019