您的位置:首页 > 移动开发 > Android开发

Android蓝牙聊天程序的扩展开发(基于Google Sample,类QQ设计)

2016-03-26 23:22 656 查看
由于最近实习的公司要求一定的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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: