安卓 蓝牙聊天小程序
一、简述
记--简单的蓝牙聊天小程序。使用的是传统蓝牙开发。(某些手机由于Android版本原因需要添加新的权限)
两台设备开启蓝牙,一台设备设置蓝牙可见性,另一台设备进行连接,然后互相收发信息。
开发环境:win7-32bit, ADT,jdk1.7
测试手机:Android版本4.4.2
例子打包:链接: https://pan.baidu.com/s/1WXKD_Wan4tc9O86-1H4EQg 提取码: g1ac
二、效果
三、工程结构
四、源文件
MainActivity.java文件
package com.liang.bluetooth;
/*
* 首先打开蓝牙并且打开蓝牙可见性是可以正常通信的
* 问题1:静默蓝牙可见性问题
* 问题2:询问式开启蓝牙问题
* 问题3:二次连接处理
* 问题4:资源释放、退出处理
* 问题5:主线程不会等待AlertDialog返回,show出来后继续执行。 解决:阻塞主线程,等待AlertDialog返回
* */
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.util.UUID;
import com.liang.bluetooth.DeviceListActivity;
import com.liang.bluetooth.R;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
//import android.view.Menu; //如使用菜单加入此三包
//import android.view.MenuInflater;
//import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
@SuppressLint({ "NewApi", "HandlerLeak" }) public class MainActivity extends Activity {
private final static int REQUEST_CONNECT_DEVICE = 1; //用来表示 查询设备的请求码
private final static int REQUEST_BT_ENABLE_CODE = 2; //用来表示 开启蓝牙的请求码
private final static String MY_UUID = "00001101-0000-1000-8000-00805F9B34FB"; //SPP服务UUID号
private TextView tv_dev = null; //用来显示本机蓝牙设备名称
private TextView tv_reDev = null; //用来显示连接的蓝牙设备名称
private TextView tv_rmsg = null; //用来显示接收到的信息
private ScrollView sv = null; //消息滚动条句柄,目的滚动到底部,显示最新接收的消息
private EditText edt_smsg = null; //发送信息输入文本框
private Button btn_cnnt = null; //"连接"按钮
private Button btn_send_file = null; //"发送文件"按钮 (此功能还没有添加)
private Toast mToast = null; //提示框
private BluetoothAdapter mAdapter = null; //获取本地蓝牙适配器
BluetoothDevice mRemoteDev = null; //用来表示其他蓝牙设备
BluetoothSocket mSocket = null; //蓝牙通信socket
private BluetoothServerSocket mServerSocket = null;//服务socket
AcceptThread accept_thread = null; //服务线程 等待别的蓝牙设备请求连接
ConnectThread connect_thread = null;//客户端连接线程 主动连接其它蓝牙设备
PrintWriter writer = null;//发送消息给其它蓝牙设备
private int cs_flag = 0; //用来标识本机是作为1服务器的还是2客户端,在发送消息的时候要用到
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main); //设置主界面为 main.xml
//1 初始化控件
init();
//2 请求打开蓝牙操作
if( mAdapter.isEnabled() )//已经打开蓝牙
{
setDiscoverableTimeout(300, 2);//设置蓝牙可见性
// 开启服务线程
accept_thread = new AcceptThread();
accept_thread.start();
}
else //还没开启蓝牙则开启
{
openBlueTooth(MainActivity.this, 0, 2);//询问开启蓝牙
}
}
/*
* 初始化
* */
public void init()
{
mAdapter = BluetoothAdapter.getDefaultAdapter();//获取蓝牙适配器
//判断设备是否支持蓝牙
if(mAdapter == null)
{
//告诉用户设备不支持蓝牙,然后退出
showAlertDialog("退出应用","抱歉!该设备不支持蓝牙。 ", "", "确定", 0);
}
//初始化控件
tv_dev = (TextView) findViewById(R.id.tv_dev); //得到本机蓝牙设备名称显示句柄
tv_dev.setText("本机蓝牙名称:"+mAdapter.getName()+"("+mAdapter.getAddress()+")");//显示本机蓝牙设备名称
tv_reDev = (TextView) findViewById(R.id.tv_reDev);//得到其他蓝牙设备名称显示句柄
tv_rmsg = (TextView) findViewById(R.id.in); //得到数据显示句柄
sv = (ScrollView)findViewById(R.id.sv_list); //得到翻页句柄(有滚动条)
edt_smsg = (EditText)findViewById(R.id.edt_smsg); //得到输入框句柄
btn_cnnt = (Button)findViewById(R.id.btn_cnnt);//得到"连接"按钮
btn_send_file = (Button)findViewById(R.id.btn_send_file);//得到"发送文件"按钮
}
/*
* 消息处理队列,对于accept、connect子线程一般不能直接更改UI,
* 所以子线程通过发送Handler消息,让Handler更改UI,(Handler类似独立线程)
*/
private Handler mHandler = new Handler(){
public void handleMessage(Message msg){
super.handleMessage(msg);
switch(msg.what)
{
case 1://弹出Toast提示框
showToast((String)msg.obj);
break;
case 2://确认连接
showAlertDialog("确认连接", "连接"+(String)msg.obj, "取消", "确定", 1);
break;
case 3://更改 所连接的蓝牙设备的信息
tv_reDev.setText((String)msg.obj);
break;
case 4://更改"连接"按钮的显示文本
btn_cnnt.setText((String)msg.obj);
break;
case 5://关闭蓝牙可见性
setDiscoverableTimeout(1, 3);
break;
case 9://显示接收到的消息
default:
tv_rmsg.append((String)msg.obj); //显示接收到的消息
sv.scrollTo(0,tv_rmsg.getMeasuredHeight()); //滚动到最新消息处
break;
}
}
};
/*
* 发送handler消息
* @param content 消息内容
* @param what 消息类型
*/
private void sendHandlerMsg(String content, int what)
{
Message msg = mHandler.obtainMessage();
msg.what = what;
msg.obj = content;
mHandler.sendMessage(msg);
}
/*
* "发送"按钮单击事件
*/
public void onSendButtonClicked(View v)
{
if(!mAdapter.isEnabled())//如果还没有开启蓝牙服务
{
showToast("蓝牙服务不可用!!");
return ;
}
if(cs_flag == 0)//未连接蓝牙设备!
{
showToast("未连接蓝牙设备!");
return ;
}
//获取要发送的信息
String smsg = edt_smsg.getText().toString();
if(smsg.equals(""))//如果消息为空则不发送
{
return ;
}
edt_smsg.setText("");//清空信息输入框
//发送消息
if(cs_flag == 1)//本机作为服务器
{
accept_thread.write(smsg);
}
else if(cs_flag == 2)//本机作为客户端
{
connect_thread.write(smsg);
}
}
/*
* 接收活动结果,响应startActivityForResult()
* @param requestCode:请求码(自定义一个 整数,代表要请求什么操作)
* @param resultCode:结果码(自定义一个 整数,代表请求的处理结果,表示成功与否。。。)
* @param data:页面返回的数据
*/
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
switch(requestCode)
{
case REQUEST_BT_ENABLE_CODE://请求开启蓝牙界面返回 (系统自带的请求打开蓝牙界面)
if (resultCode == RESULT_OK)
{
//用户允许打开蓝牙(蓝牙开启需要一定的时间)
showToast("蓝牙已开启");
//开启服务线程
accept_thread = new AcceptThread();
accept_thread.start();
} else if (resultCode == RESULT_CANCELED) {
//用户没有允许打开蓝牙,退出应用
showAlertDialog("退出应用", "抱歉!应用需要开启蓝牙!", "" ,"确认", 0) ;
}
break;
case REQUEST_CONNECT_DEVICE://搜索周边蓝牙设备界面结果返回
// 响应返回结果
if (resultCode == Activity.RESULT_OK) //连接成功
{
//1 获取要连接设备的名称、MAC地址
//String dev_name = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_NAME);
String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
//2 得到要连接的蓝牙设备句柄
mRemoteDev = mAdapter.getRemoteDevice(address);
if(mRemoteDev == null)
{
tv_rmsg.append("获取蓝牙设备句柄失败\n");
return;
}
//3 开启连接线程
if(connect_thread != null)//取消之前的
{
connect_thread.close();//结束线程
connect_thread = null;
}
connect_thread = new ConnectThread(mRemoteDev);
connect_thread.start();
}
break;
default:break;
}
}
/*
* "连接"按钮响应函数
*/
public void onConnectButtonClicked(View v)
{
if(!mAdapter.isEnabled())
{ //如果蓝牙服务不可用则提示,可能是还没有开启
showToast("蓝牙服务不可用!");
return;
}
if(mSocket == null)//如未连接设备则打开DeviceListActivity进行搜索周边蓝牙设备,并选择连接
{
Intent serverIntent = new Intent(this, DeviceListActivity.class); //跳转程序设置
startActivityForResult(serverIntent, REQUEST_CONNECT_DEVICE); //设置返回宏定义
}
else //已经连接上就断开连接
{
//弹出确认框
showAlertDialog("断开连接", "您确认断开吗?", "取消" ,"确认", 3);
}
}
/*
* 断开连接
* */
public void disconnect()
{
try
{//断开连接
if(cs_flag == 1 && accept_thread != null)//本机作为服务端
{
//发送"__DISCONNECT__",告诉客户端要断开操作
accept_thread.write("__DISCONNECT__");
accept_thread.disconnect();//断开当前连接
}
else if(cs_flag == 2 && connect_thread != null )//本机作为客户端
{
//发送"__DISCONNECT__",告诉服务端要断开操作
connect_thread.write("__DISCONNECT__");
connect_thread.close();//关闭连接线程,即断开与服务端的连接
accept_thread = null;
}
cs_flag = 0;//标识为未连接蓝牙设备
btn_cnnt.setText("连接");//将"断开"按钮的文本改为连接
tv_reDev.setText("未连接蓝牙设备");
}catch(Exception e){}
}
/*
* 关闭socket,结束线程
*/
public void reSource()
{
try
{
//关闭服务线程
if( accept_thread != null)
{
accept_thread.close();//关闭线程
accept_thread = null;
}
//关闭connect线程
if( connect_thread != null)
{
connect_thread.close();//关闭线程
connect_thread = null;
}
//关闭socket
if(mServerSocket != null)
{
try {
mServerSocket.close();
mServerSocket = null;
} catch (IOException e) {}
}
if(mSocket != null)
{
try {
mSocket.close();
mSocket = null;
} catch (IOException e) {}
}
cs_flag = 0;//表示为未连接蓝牙设备
btn_cnnt.setText("连接");
tv_reDev.setText("未连接蓝牙设备");
}
catch(Exception e){}
}
/*
* 关闭程序调用处理部分
*/
public void onDestroy(){
super.onDestroy();
reSource();
}
/*
* "发送文件"按钮响应函数
*/
public void onSendFileButtonClicked(View v)
{
showAlertDialog("发送文件", "此功能有待完善!", "取消" ,"确认", 1);
}
//"退出"按钮响应函数
public void onQuitButtonClicked(View v){
//弹出一个对话框,确认是否退出
showAlertDialog("退出应用", "您确认退出吗?", "取消" ,"确认", 0);
}
//弹出确认对话框
public void showAlertDialog(String title, String content, String negative, String positive, final int action)
{
//创建一个对话框
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
dialog.setTitle(title); //对话框标题
dialog.setMessage(content);//设置对话框内容提示
if(!negative.isEmpty() && negative != "" )
{
//添加"取消按钮",并且单击时响应
dialog.setNegativeButton(negative,new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which)
{
if(action == 2)
{
//用户没有允许开启蓝牙,就退出应用,因为应用需要开启蓝牙才正常工作
showAlertDialog("退出应用", "抱歉!应用需要开启蓝牙!", "" ,"确认", 0) ;
}
}
});
}
//添加一个确定按钮,并且单击时响应
dialog.setPositiveButton(positive, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which)
{
switch(action)
{
case 0://退出应用操作
reSource();//释放资源
finish();//关闭本页面
break;
case 1://"确认"
break;
case 2://开启蓝牙
mAdapter.enable();//开启蓝牙
while(!mAdapter.isEnabled());//等待蓝牙开启完毕,需要一定时间
setDiscoverableTimeout(300, 2);//设置蓝牙可见性
// 开启服务线程
accept_thread = new AcceptThread();
accept_thread.start();
break;
case 3://断开连接
disconnect();
break;
default:
break;
}
}
});
dialog.show();
}
//弹出Toast提示框
private void showToast(String text) {
if( mToast == null) {
mToast = Toast.makeText(this, text, Toast.LENGTH_SHORT);
}
else {
mToast.setText(text);
}
mToast.show();
}
/**
* 打开蓝牙
* @param activity
* @param requestCode
* @param mode 开启蓝牙的方式:0询问式开启,1直接打开
*/
public void openBlueTooth(Activity activity, int requestCode, int mode) {
if(mode == 0)//弹出系统自带确认框询问式开启
{
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
activity.startActivityForResult(intent, requestCode);
}
else if(mode == 2)//弹出自定义的确认框
{
//弹出一个对话框,确认是否开启蓝牙
showAlertDialog("开启蓝牙", "确认开启蓝牙?", "取消" ,"确认", 2) ;
}
else //静默开启(不弹出提示框)
{
mAdapter.enable();//这种方式是直接尝试打开蓝牙,对用户不友好
// 开启服务线程 (可能需要等待蓝牙开启完毕,开启蓝牙需要时间)
accept_thread = new AcceptThread();
accept_thread.start();
}
//静默设置蓝牙可见性,时间为300秒
setDiscoverableTimeout(300, 2);
}
/*
* 通过PrintWriter发送消息给其他蓝牙设备
* @param btOs 蓝牙输出流
* @param msg 要发送的消息文本
*/
public void sendMsg(OutputStream btOs, String msg)
{
if (btOs != null)
{
try {
if (writer == null) {
writer = new PrintWriter(new OutputStreamWriter(btOs, "UTF-8"), true);
}
writer.println(msg);//发送给其它蓝牙设备
if(msg == "__DISCONNECT__")
{
msg = "断开连接";
}
tv_rmsg.append("我:"+msg+"\n");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
sendHandlerMsg("writer错误:" + e.getMessage(), 100);
}
}
}
/* 设置蓝牙可见性
* BluetoothAdapter 里面的setDiscoverableTimeout和setScanMode起到了关键性左右,
* BluetoothAdapter源码将这2个方法隐藏了。利用反射访问
* @param timeout 可见时间(秒)最多300秒
* @param mode 1为询问式设置,2为静默设置 3关闭蓝牙可见性
*/
public void setDiscoverableTimeout(int timeout, int mode)
{
if(mode == 1)//弹出确认框询问式设置
{
Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, timeout);
startActivity(discoverableIntent);
}
else //静默式,不弹出确认框。
{
try {
//利用反射使用 setDiscoverableTimeout和setScanMode
Method setDiscoverableTimeout = BluetoothAdapter.class.getMethod("setDiscoverableTimeout", int.class);
setDiscoverableTimeout.setAccessible(true);
Method setScanMode =BluetoothAdapter.class.getMethod("setScanMode", int.class,int.class);
setScanMode.setAccessible(true);
if(mode == 2)//静默式 timeout不起作用,会一直保持可见性
{
setDiscoverableTimeout.invoke(mAdapter, timeout);
setScanMode.invoke(mAdapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE,timeout);
//mHandler.sendEmptyMessageDelayed(5, 300000);//300秒后发送5类信号让Handler关闭蓝牙可见性
}
else if(mode == 3)//实现关闭蓝牙可见性
{
setDiscoverableTimeout.invoke(mAdapter, 1);
setScanMode.invoke(mAdapter, BluetoothAdapter.SCAN_MODE_CONNECTABLE,1);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//Accept服务端线程
class AcceptThread extends Thread
{
private InputStream btIs; //用来获取其他蓝牙设备发来的消息
private OutputStream btOs; //用来发送消息给其他蓝牙设备
private boolean thread_run;//线程运行的标志
private boolean cnnt_state;//连接状态
public AcceptThread() {
thread_run = true;
cnnt_state = true;
}
@Override
public void run()
{
String devInfo = null;
try
{
//1 获取套接字
mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord("TEST", UUID.fromString(MY_UUID));
if (mServerSocket == null)
{
sendHandlerMsg("accept获取mServerSocket失败\n", 100);
//这里应该添加"获取失败"处理, 否则后续操作可能出现异常
return;
}
while(thread_run)//一般程序开始运行,服务就启动,并一直监听客户端的连接
{
//2 监听连接请求 -- 连接一个设备 (如果需要连接多个设备,就需要一直mServerSocket.accept(),每accept一个客户端就就交给一个新的socket去通信-)
if(mSocket != null)//关闭之前的
{
try {
mSocket.close();
mSocket = null;
} catch (IOException e) {}
}
try
{
mSocket = mServerSocket.accept();//阻塞等待客户端连接
}
catch(Exception ex)
{
continue;
}
devInfo = mSocket.getRemoteDevice().getName()+"("+mSocket.getRemoteDevice().getAddress()+")";
//弹出提示框
sendHandlerMsg("连接 "+mSocket.getRemoteDevice().getName()+" 成功!", 1);
//展示已经连接的设备名称
sendHandlerMsg("已连接:"+devInfo, 3);
sendHandlerMsg("-----已连接:"+devInfo+" -----\n", 9);
cs_flag = 1;//标识本机作为服务器
sendHandlerMsg("断开", 4);//将"连接"按钮文本更改为"断开"
try
{
//3 获取输入输出流
btIs = mSocket.getInputStream();
btOs = mSocket.getOutputStream();
//4 通讯-接收消息
BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8"));
String content = null;
cnnt_state = true;
while (cnnt_state)
{
content = reader.readLine();
if(content.startsWith("__DISCONNECT__") )//客户端发来断开连接的请求
{
sendHandlerMsg("收到消息:" + "断开连接" +"\n", 9);//将消息显示到TextView
break;
}
else if(content != "" && !content.isEmpty())
{
sendHandlerMsg("收到消息:" + content +"\n", 9);//将消息显示到TextView
content = "";
}
}
}
catch(Exception e){}
sendHandlerMsg("断开连接", 1);
sendHandlerMsg("----已断开连接----" +"\n", 9);//将消息显示到TextView
cs_flag = 0;//标识未连接蓝牙设备
sendHandlerMsg("未连接蓝牙设备", 3);
sendHandlerMsg("连接", 4);
}
} catch (IOException e) {
e.printStackTrace();
sendHandlerMsg("accept错误:" + e.getMessage(), 100);
}
finally
{
reSource();//释放资源
finish();//退出应用
}
}
public void write(String msg)
{
sendMsg(btOs, msg);
}
//关闭线程
public void close()
{
cnnt_state = false;
thread_run = false;
try {
mSocket.close();
mServerSocket.close();
} catch (IOException e) {}
}
//断开连接
public void disconnect()
{
cnnt_state = false;
try {
mSocket.close();
} catch (IOException e) {}
}
}
//Connect客户端线程
class ConnectThread extends Thread
{
private BluetoothDevice mDevice;//要连接的蓝牙设备
private InputStream btIs; //用来获取其他蓝牙设备发来的消息
private OutputStream btOs; //用来发送消息给其他蓝牙设备
private boolean thread_run; //用来控制循环(如果是true则线程一直在运行)
public ConnectThread(BluetoothDevice device) {
mDevice = device;//被点击选中的蓝牙设备
thread_run = true;
}
@Override
public void run() {
if (mDevice != null) {
try {
try
{
if(mSocket != null)
{
mSocket.close();
mSocket = null;
}
}
catch(Exception e)
{}
//1 获取套接字
mSocket = mDevice.createInsecureRfcommSocketToServiceRecord(UUID.fromString(MY_UUID));
if (mSocket == null)
{
sendHandlerMsg("cnnt获取mSocket失败\n", 100);
return;
}
try
{
//2 发起连接请求
mSocket.connect();
//弹出提示框
sendHandlerMsg("连接 " + mDevice.getName() + " 成功!", 1);
//在TextView上显示已经连接上的蓝牙设备名称
sendHandlerMsg("已连接:"+mDevice.getName()+"("+mDevice.getAddress()+")", 3);
sendHandlerMsg("-----已连接:"+mDevice.getName()+"("+mDevice.getAddress()+") -----\n", 9);
cs_flag = 2;//标识本机作为客户端
sendHandlerMsg("断开", 4);//将"连接"按钮文本改为断开
//3 获取输入输出流
btIs = mSocket.getInputStream();
btOs = mSocket.getOutputStream();
//4 通讯-接收消息
BufferedReader reader = new BufferedReader(new InputStreamReader(btIs, "UTF-8"));
String content = null;
thread_run = true;
while (thread_run)
{
content = reader.readLine();
if(content.startsWith("__DISCONNECT__") )//收到服务器端的断开信息
{
sendHandlerMsg("收到消息:" + "断开连接" + "\n", 9);
break;
}
else if(content !="" && !content.isEmpty())
{
sendHandlerMsg("收到消息:" + content + "\n", 9);
content = "";
}
}
}catch(Exception e){}
cs_flag = 0;
sendHandlerMsg("断开连接", 1);
sendHandlerMsg("未连接蓝牙设备", 3);
sendHandlerMsg("----已断开连接----" +"\n", 9);//将消息显示到TextView
sendHandlerMsg("连接", 4);//将"断开"按钮文本改为"连接"
} catch (IOException e) {
e.printStackTrace();
sendHandlerMsg("cnnt错误:" + e.getMessage(), 100);
}
finally
{
if(mSocket != null)
{
try {
mSocket.close();
mSocket = null;
} catch (IOException e) {}
}
}
}
}
//发送信息
public void write(String msg)
{
sendMsg(btOs, msg);
}
//结束线程
public void close()
{
thread_run = false;
try {
mSocket.close();
} catch (IOException e) {}
}
}
}
DeviceListActivity.java文件
package com.liang.bluetooth;
import com.liang.bluetooth.R;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.View.OnClickListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;
//蓝牙设备列表
@SuppressLint("NewApi")
public class DeviceListActivity extends Activity {
// 返回时数据标签
public static String EXTRA_DEVICE_NAME = "设备名称";
public static String EXTRA_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);
// 注册接收查找到设备action接收器
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
this.registerReceiver(mReceiver, filter);
// 注册查找结束action接收器
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
this.registerReceiver(mReceiver, filter);
// 得到本地蓝牙适配器
mBtAdapter = BluetoothAdapter.getDefaultAdapter();
}
//销毁
@Override
protected void onDestroy() {
super.onDestroy();
// 关闭扫描服务
if (mBtAdapter != null) {
mBtAdapter.cancelDiscovery();
}
// 注销action接收器
this.unregisterReceiver(mReceiver);
}
//"取消"按钮响应函数
public void OnCancel(View v){
finish();//关闭本页面
}
/**
* 开始服务和设备查找
*/
private void doDiscovery() {
// 显示进度滚动圈
setProgressBarIndeterminateVisibility(true);
//设置窗口标题
setTitle("查找设备中...");
//弹出提示框吗,提示用户
Toast.makeText(DeviceListActivity.this, "查找设备中...", Toast.LENGTH_SHORT).show();
// 显示其它设备(未配对设备)列表
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();
// 得到设备名称与MAC地址
String info = ((TextView) v).getText().toString();
String dev_name = info.substring(0, info.length() - 18);//设备名称
String address = info.substring(info.length() - 17);//MAC地址
// 设置返回数据
Intent intent = new Intent();
intent.putExtra(EXTRA_DEVICE_NAME, dev_name);
intent.putExtra(EXTRA_DEVICE_ADDRESS, address);
// 设置返回值并结束程序
setResult(Activity.RESULT_OK, intent);
finish();
}
};
// 查找到设备和搜索完成action监听器
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
// 查找到设备action
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 //添加到已配对设备列表
{
mPairedDevicesArrayAdapter.add(device.getName() + "\n" + device.getAddress());
}
}
else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) // 搜索完成action
{
setProgressBarIndeterminateVisibility(false);//滚动圈消失
setTitle("选择要连接的设备");
if (mNewDevicesArrayAdapter.getCount() == 0) {
String noDevices = "没有找到新设备";
mNewDevicesArrayAdapter.add(noDevices);
}
}
}
};
}
布局文件
main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/tv_dev"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/tv_dev" />
<TextView
android:id="@+id/tv_reDev"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tv_reDev" />
<ScrollView
android:id="@+id/sv_list"
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1.06"
android:scrollbars="vertical" >
<TextView android:id="@+id/in"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</ScrollView>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<EditText
android:id="@+id/edt_smsg"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_weight="1"
android:inputType="text" >
</EditText>
<Button
android:id="@+id/btn_send"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="onSendButtonClicked"
android:text="@string/btn_send" >
</Button>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<Button
android:id="@+id/btn_cnnt"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:onClick="onConnectButtonClicked"
android:text="@string/btn_cnnt" >
</Button>
<Button
android:id="@+id/btn_send_file"
android:layout_width="152dp"
android:layout_height="wrap_content"
android:onClick="onSendFileButtonClicked"
android:text="@string/btn_send_file" >
</Button>
<Button
android:id="@+id/Button06"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="onQuitButtonClicked"
android:text="@string/btn_exit" >
</Button>
</LinearLayout>
</LinearLayout>
device_list.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ListView android:id="@+id/paired_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stackFromBottom="true"
android:layout_weight="1"
/>
<TextView android:id="@+id/title_new_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/tv_disconnect"
android:visibility="gone"
android:background="#666"
android:textColor="#fff"
android:paddingLeft="5dp"
/>
<ListView android:id="@+id/new_devices"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:stackFromBottom="true"
android:layout_weight="2"
/>
<Button
android:id="@+id/button_scan"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/btn_search" />
<Button
android:id="@+id/button_cancel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="OnCancel"
android:text="@string/btn_cancel" />
</LinearLayout>
device_name.xml文件
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="18sp"
android:padding="5dp"
/>
五、总结
1、程序大致流程图
设备双方开启蓝牙,设备A设置蓝牙可见性,其它设备可以搜索到,开启监听线程,可以监听并接受其它蓝牙设备的连接请求。设备B搜索附近蓝牙,搜索到设备A的蓝牙,发起连接请求,设备A接收之后就可以互相收发消息。
2、蓝牙开发相关事项
蓝牙开发重要对象:本地蓝牙适配器
BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();//获取本地蓝牙适配器
操作 | 代码 | 备注 |
蓝牙权限 |
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.BLUETOOTH" /> |
某些Andriod版本需要其他权限 |
是否支持蓝牙 |
BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter();//获取蓝牙适配器 |
如果mAdapter 为null就说明设备不支持蓝牙 |
蓝牙是否已经开启 | mAdapter.isEnabled(); | 返回true说明已经打开 |
打开蓝牙 |
方式1:弹出系统自带的请求对话框。 Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); mAdapter.enable();//这种方式是直接尝试打开蓝牙,对用户不友好 |
蓝牙打开需要一定的时间 |
关闭蓝牙 | mAdapter.disable(); | |
搜索蓝牙 | mBtAdapter.startDiscovery(); | 搜索周边的蓝牙设备(搜索会持续一段时间,手动结束搜索mBtAdapter.cancelDiscovery();) 搜索到一个蓝牙设备系统会发出广播BluetoothDevice.ACTION_FOUND 可以自定义广播接受者来接收广播。 |
监听连接 | BluetoothServerSocket mServerSocket = mAdapter.listenUsingRfcommWithServiceRecord("TEST", UUID.fromString(MY_UUID)); mSocket = mServerSocket.accept();//阻塞等待客户端连接 |
(监听线程、等待别的蓝牙设备来链接) MY_UUID = "00001101-0000-1000-8000-00805F9B34FB"; //SPP服务UUID号 |
发起连接 | BluetoothSocket mSocket = mDevice.createInsecureRfcommSocketToServiceRecord(UUID.fromString(MY_UUID)); //发起连接请求 |
用来请求连接某个蓝牙设备 mDevice:是要连接的蓝牙设备对象。 |
设置蓝牙可见性 | 方式1:弹出确认框 (timeout:可见时间最大300秒) Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE); 方式2:不弹出提示框
setDiscoverableTimeout.invoke(mAdapter, timeout); |
一般默认不可见,设置为可见,其它蓝牙设备才能搜索到。 |
收发消息 | //3 获取输入输出流 InputStream btIs = mSocket.getInputStream();//用来接收消息 OutputStream btOs = mSocket.getOutputStream();//用来发送消息 |
3、待完善
a) 例子只简单演示了一对一通信,其实可以一对多。
b) 蓝牙文件传输(文件检索,文件编码, 续传)。
c) 例子中如果断开链接又进行链接的情况处理不当,可能是对socket等资源的释放存在问题。
d) 程序没有使用"广播"方式来处理蓝牙的链接与断开(推荐)。
e) 程序不够"健壮性",不够"人性化", 代码不够精简。