由于最近实习的公司要求一定的Android 蓝牙技术支持,故花了一天的时间钻研Google 的蓝牙聊天APP源码,然后又花了一下午对该Sample的UI进行了进一步,写成这个博文提供给大家学习,源码后面有下载链接,不要分O(∩_∩)O~
首先看看程序的效果:
在整个开发过程中涉及的几个关键步骤
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