网上有很多关于蓝牙聊天工具的示例代码,对比参考并加入了自己的理解和创新,自己也做了一个蓝牙聊天工具,总体感觉还可以,下面进行分析一下。
首先定义了 一个Activity主界面,进行聊天信息的输出和发送 。先将聊天界面的XML布局文件贴出来:
activity_bluetooth_chat.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_height="match_parent" android:layout_width="match_parent" android:orientation="vertical" xmlns:android="http://schemas.android.com/apk/res/android">
<ListView android:id="@+id/chat_window_LV" android:layout_width="match_parent" android:layout_height="match_parent" android:transcriptMode="alwaysScroll" android:stackFromBottom="true" android:layout_weight="2">
</ListView>
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_gravity="bottom">
<Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="p" android:onClick="takePhotoClicked"/>
<EditText android:id="@+id/input_ET" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="1" android:layout_gravity="bottom"/>
<Button android:text="send" android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="onSendBtnClicked"/>
</LinearLayout>
</LinearLayout>
包括一个ListView和一个LineLayout布局(一个EditText和一个Button),该ListView用来显示聊天信息的,由一个ArrayAdapter类进行管理,每当收到或者发送 一条信息时,调用add(Object obj)方法可以同步显示出来,另外属性android:transcriptMode=”alwaysScroll”表示当内容 逐渐增多时,会出现滚动条进行帮助显示。 android:stackFromBottom=”true”表示item是从底部开始添加的。而android:layout_gravity=”bottom”>属性表示 该部件的位置,此处是在parent的底部。
聊天记录的ListView里每个item的layout为:
talk_note.xml
<?xml version="1.0" encoding="utf-8"?>
<TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="20dp" xmlns:android="http://schemas.android.com/apk/res/android"></TextView>
下面来看主Activity,先给出用到的数据成员:
public class BluetoothChat extends AppCompatActivity {
private static final String TAG = "BluetoothChat";
public static boolean mBoundListenConnection = false;
private static final String path = "/DCIM/camera/";
/* @brief: the flag indicate that the window is in behind */
public static boolean WINDOW_BEHIND_FLAG = true;
/* @brief: the handler flag used to decide the action will be taken */
public static final int NEW_DEVICE_READY_TO_CONNECT = 0;
/* * @Brief: * the intent request code */
private static final int REQUEST_CODE_ENABLE_BLUETOOTH = 1001;
private static final int REQUEST_CODE_SCAN_DEVICES = 1002;
public static final int REQUEST_CODE_CAMERA_ACTION = 1003;
private static final int REQUEST_CODE_PHOTO_list = 1004;
private BluetoothAdapter mBluetoothChatAdapter = null;
private static BluetoothChatDevice mBluetoothChatDevice;
private ArrayAdapter<String> messageArrayAdapter;
private EditText inputArea;
public BlueChatHandler mBluetoothChatHandler = new BlueChatHandler(this);
private Messenger bluetoothChatMessenger = new Messenger(mBluetoothChatHandler);
private Messenger listenMessenger;
给出onCreate方法中做的工作 :
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_bluetooth_chat);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
inputArea = (EditText)findViewById(R.id.input_ET);
ListView message = (ListView)findViewById(R.id.chat_window_LV);
messageArrayAdapter = new ArrayAdapter<String>(this,R.layout.talk_note);
message.setAdapter(messageArrayAdapter);
mBluetoothChatHandler.post(mRunnable);
mBluetoothChatDevice =
new BluetoothChatDevice(this,mBluetoothChatHandler);
mBluetoothChatAdapter = BluetoothAdapter.getDefaultAdapter();
if(mBluetoothChatAdapter==null) {
finish();
return;
}
if (!mBluetoothChatAdapter.isEnabled()) {
Intent i = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(i, REQUEST_CODE_ENABLE_BLUETOOTH);
}else {
if (!mBoundListenConnection) {
Intent listenForConnection = new Intent(this, ListenForConnectionService.class);
bindService(listenForConnection, mServiceConnection, BIND_AUTO_CREATE);
}
}
// Eula.show(this);
}
首先初始化聊天记录 的ListView message和输入部件EditText inputArea。并将message与一个ArrayAdapter messageArrayAdapter关联 起来,进而可以实时同步 聊天信息。初始化Ui thread的handler,并将其 post到一个Runnable对象中,使程序先执行此run()方法:
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
Eula.show(BluetoothChat.this);
}
};
此处不是本文的重点不做介绍。其实就是一个用户协议,accept后下次打开该 应用时,就不会出现AlertDialog窗口。
然后定义了一个BluetoothChatDevice的实例mBluetoothChatDevice,该类是自己写的一个类,将UI thread的Handler和该Context通过其构造 函数传递给该类。
接着调用mBluetoothChatAdapter = BluetoothAdapter.getDefaultAdapter();获得该设备的蓝牙 接口,并判断是否支持蓝牙,如果不 支持 蓝牙则 返回值mBluetoothChatAdapter ==null。
接着通过调用蓝牙接口的isEnabled()方法判断蓝牙是否已经打开。如果已经打开则启动一个service来监听是否有蓝牙设备接入;如果没有打开则执行打开蓝牙activity,并在打开后启动监听蓝牙设备接入服务。
public static boolean WINDOW_BEHIND_FLAG = true;为indicate当前applicaton是否visible。如果visible则直接将聊天 信息同步到ListView message中,如果Invisible,则会出现Toast提示信息。
public static boolean mBoundListenConnection = false;为是否bind了监听设备接入的服务,由于是在onCreate方法中启动服务的。所以在onDestory方法中unbind服务。
要想bind一个service必须 具有一个ServiceConnection,给出此数据成员的代码:
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
listenMessenger = new Messenger(service);
Message msg = Message.obtain(null,ListenForConnectionService.START_LISTEN_HANDLER_MESS,bluetoothChatMessenger);
mBoundListenConnection = true;
try {
listenMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mBoundListenConnection = false;
try {
listenMessenger.send(Message.obtain(null, ListenForConnectionService.STOP_LISTEN_HANDLER_MESS));
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
在方法public void onServiceConnected(ComponentName name, IBinder service) 中得到一个Messenger对象,此对象 由Ibinder service初始化,是监听蓝牙接入的Handler,因此进行listenMessenger.send(msg)时,会在监听service中收到消息,并启动监听thread,并且将该包含UI thread的handler的Messenger发给 绑定的服务中,这样在此监听 服务中 就可以向UI thread传递消息了。
看看这段断码后看onActivityResult方法
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.scan_for_blueDevice:
Intent i = new Intent(this,BluetoothDeviceList.class);
startActivityForResult(i, REQUEST_CODE_SCAN_DEVICES);
break;
case R.id.visibleBluetooth:
Intent scanIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
scanIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,300);
Log.d(TAG,"type"+scanIntent.getType());
startActivity(scanIntent);
break;
default:
break;
}
return super.onOptionsItemSelected(item);
}
此段完成了启动来源可发现的activity,以及启动 蓝牙搜索设备的activity。
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_CODE_ENABLE_BLUETOOTH:
if(resultCode==RESULT_CANCELED) {
Toast.makeText(this,"the bluetooth device is not enable",Toast.LENGTH_LONG).show();
finish();
return;
}
if(!mBoundListenConnection) {
Intent listenForConnection = new Intent(this, ListenForConnectionService.class);
bindService(listenForConnection, mServiceConnection, BIND_AUTO_CREATE);
}
break;
case REQUEST_CODE_SCAN_DEVICES:
String address = data.getExtras().getString(BluetoothDeviceList.SELECTED_BLUETOOTH_DEVICE_ADDR);
BluetoothDevice bluetoothDevice = mBluetoothChatAdapter.getRemoteDevice(address);
mBluetoothChatDevice.connect(bluetoothDevice);
break;
default:
super.onActivityResult(requestCode, resultCode, data);
break;
}
}
给出UI thead的Handler:
private class BlueChatHandler extends Handler {
private Context context;
public BlueChatHandler(Context c) {
context = c;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case NEW_DEVICE_READY_TO_CONNECT:
BluetoothSocket socket = (BluetoothSocket)msg.obj;
mBluetoothChatDevice.chat(socket);
break;
case BluetoothChatDevice.SEND_MESSAGE:
byte[] bytes = (byte[])msg.obj;
String tmp = new String(bytes);
messageArrayAdapter.add(mBluetoothChatAdapter.getName()+": "+tmp);
break;
case BluetoothChatDevice.RECEIVE_MESSAGE:
// Bitmap bitmap;
byte[] mbytes = (byte[])msg.obj;
/* if (mbytes.length != 0) { bitmap = BitmapFactory.decodeByteArray(mbytes, 0, mbytes.length); } else { return; }*/
String mtmp = new String(mbytes,0,msg.arg1);
messageArrayAdapter.add(BluetoothChatDevice.connectedDeviceName+": "+mtmp);
// messageArrayAdapter.add(bitmap);
if(WINDOW_BEHIND_FLAG)
Toast.makeText(BluetoothChat.this,BluetoothChatDevice.connectedDeviceName+": " +
" "+mtmp,Toast.LENGTH_LONG).show();
break;
case BluetoothChatDevice.TOAST_MESSAGE:
switch (msg.arg1) {
case BluetoothChatDevice.TOAST_MESSAGE_UNCONNECT:
Toast.makeText(context, msg.obj.toString() + " unconnected",
Toast.LENGTH_SHORT).show();
break;
case BluetoothChatDevice.TOAST_MESSAGE_CONNECT:
Toast.makeText(context,msg.obj.toString()+" connect",
Toast.LENGTH_SHORT).show();
break;
default:
break;
}
break;
default:
super.handleMessage(msg);
break;
}
}
给出BluetoothChatdevice类:
/** * Created by almo.liu on 2016/4/20. */
public class BluetoothChatDevice implements Serializable {
private static final String TAG = "BluetoothChatDevice";
public static final int IN_CHATTING_FLAG = 0;
public static final int IN_CONNECTING_FLAG = 1;
public static final int IN_CONNECTED_FLAG = 2;
public static final int NO_EVENT_HAPPEN = -1;
public static int STATE = NO_EVENT_HAPPEN;
public static final int RECEIVE_MESSAGE = 11;
public static final int SEND_MESSAGE = 12;
public static final int TOAST_MESSAGE = 13;
public static final int TOAST_MESSAGE_CONNECT = 14;
public static final int TOAST_MESSAGE_UNCONNECT = 15;
private Context mContext;
private Handler mBluetoothChatDeviceHandler;
public static String connectedDeviceName = null;
private ConnectingThread mConnectingThread;
private ChatThread mChatThread;
public BluetoothChatDevice(Context context,Handler handler) {
mContext = context;
mBluetoothChatDeviceHandler = handler;
}
public synchronized void chat(BluetoothSocket bluetoothSocket) {
if(mChatThread!=null)
mChatThread.cancel();
mChatThread = new ChatThread(bluetoothSocket);
mChatThread.start();
}
public synchronized void connect(BluetoothDevice bluetoothDevice) {
if(mConnectingThread!=null)
mConnectingThread.cancel();
mConnectingThread = new ConnectingThread(bluetoothDevice);
mConnectingThread.start();
}
public void send(byte[] bytes) {
if(mChatThread!=null)
mChatThread.write(bytes);
else
Toast.makeText(mContext,"please connected a device!",Toast.LENGTH_SHORT).show();
}
private void connectionLost() {
mBluetoothChatDeviceHandler.obtainMessage(TOAST_MESSAGE,TOAST_MESSAGE_UNCONNECT,
1,connectedDeviceName).sendToTarget();
connectedDeviceName = null;
}
private void successConnect() {
mBluetoothChatDeviceHandler.obtainMessage(TOAST_MESSAGE,TOAST_MESSAGE_CONNECT,
0,connectedDeviceName).sendToTarget();
}
private class ConnectingThread extends Thread {
BluetoothSocket socket;
public ConnectingThread(BluetoothDevice bluetoothDevice) {
BluetoothSocket bluetoothSocket = null;
STATE = IN_CONNECTING_FLAG;
try {
bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(
ListenForConnectionService.MY_UUID_INSECURE);
} catch (IOException e) {
STATE = NO_EVENT_HAPPEN;
}
socket = bluetoothSocket;
}
@Override
public void run() {
try {
socket.connect();
} catch (IOException e) {
STATE = NO_EVENT_HAPPEN;
mConnectingThread = null;
return;
}
STATE = IN_CONNECTED_FLAG;
mConnectingThread = null;
chat(socket);
}
public synchronized void cancel() {
try {
socket.close();
mConnectingThread = null;
STATE = NO_EVENT_HAPPEN;
} catch (IOException e) {
e.printStackTrace();
}
}
}
private class ChatThread extends Thread {
private BluetoothSocket socket;
private InputStream inputStream;
private OutputStream outputStream;
public ChatThread(BluetoothSocket b) {
InputStream tmp_input = null;
OutputStream tmp_output = null;
STATE = IN_CHATTING_FLAG;
socket = b;
try {
tmp_input= socket.getInputStream();
tmp_output = socket.getOutputStream();
} catch (IOException e) {
Log.d(TAG,"exception in create out and in stream");
STATE = NO_EVENT_HAPPEN;
}
connectedDeviceName = socket.getRemoteDevice().getName();
successConnect();
outputStream = tmp_output;
inputStream = tmp_input;
}
public synchronized void write(byte[] bytes) {
if(mChatThread==null)
return;
try {
outputStream.write(bytes);
} catch (IOException e) {
return;
}
mBluetoothChatDeviceHandler.obtainMessage(SEND_MESSAGE,bytes).sendToTarget();
}
@Override
public void run() {
byte[] buffer = new byte[1024];
int num = 0;
while(true) {
try {
num = inputStream.read(buffer);
Log.d(TAG,new String(buffer));
} catch (IOException e) {
Log.d(TAG, "exception in read: " + num);
mChatThread = null;
break;
}
mBluetoothChatDeviceHandler.obtainMessage(RECEIVE_MESSAGE,num,0,buffer).sendToTarget();
}
connectionLost();
STATE = NO_EVENT_HAPPEN;
mChatThread = null;
}
public synchronized void cancel() {
try {
STATE = NO_EVENT_HAPPEN;
socket.close();
mChatThread = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
给出监听设备接入的service:
public class ListenForConnectionService extends Service {
private static final String TAG = "ListenFor";
public static final UUID MY_UUID_SECURE =
UUID.fromString("fa87c0d0-afac-11de-8a39-0800200c9a66");
public static final UUID MY_UUID_INSECURE =
UUID.fromString("8ce255c0-200a-11e0-ac64-0800200c9a66");
private static boolean loop_listen = false;
private BluetoothAdapter mListenAdapter ;
private BluetoothServerSocket mListenBluetoothServerSocket;
public static final int START_LISTEN_HANDLER_MESS = 1;
public static final int STOP_LISTEN_HANDLER_MESS = 2;
private Messenger blueChatMessenger;
private Handler listenHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case START_LISTEN_HANDLER_MESS:
blueChatMessenger = (Messenger)msg.obj;
listen();
break;
case STOP_LISTEN_HANDLER_MESS:
cancelListen();
break;
default:
super.handleMessage(msg);
break;
}
}
};
Messenger listenMessenger = new Messenger(listenHandler);
@Override
public void onCreate() {
super.onCreate();
/* mListenAdapter = BluetoothAdapter.getDefaultAdapter(); BluetoothServerSocket tmp; try { tmp = mListenAdapter.listenUsingInsecureRfcommWithServiceRecord("test",MY_UUID_INSECURE); } catch (IOException e) { return; } mListenBluetoothServerSocket = tmp; */
}
@Override
public void onDestroy() {
super.onDestroy();
loop_listen = false;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return listenMessenger.getBinder();
}
@Override
public void onRebind(Intent intent) {
super.onRebind(intent);
}
private Runnable listenRunnable = new Runnable() {
@Override
public void run() {
BluetoothSocket socket = null;
while(loop_listen) {
if(mListenBluetoothServerSocket==null) {
mListenAdapter = BluetoothAdapter.getDefaultAdapter();
BluetoothServerSocket tmp;
Log.d(TAG,"null++++++++++++++++++");
try {
tmp = mListenAdapter.listenUsingInsecureRfcommWithServiceRecord("test",MY_UUID_INSECURE);
} catch (IOException e) {
return;
}
mListenBluetoothServerSocket = tmp;
continue;
}
try {
socket = mListenBluetoothServerSocket.accept();
} catch (IOException e) {
loop_listen = false;
break;
}
if(socket!=null) {
Message msg = Message.obtain(null,BluetoothChat.NEW_DEVICE_READY_TO_CONNECT,socket);
try {
blueChatMessenger.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
};
/* the method called to contact with the service */
public void listen() {
loop_listen = true;
Thread listenThread = new Thread(listenRunnable);
listenThread.start();
}
public void cancelListen() {
loop_listen = false;
}
}