版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/AmazingUU/article/details/54292584
前面一篇文章实现了最简单的蓝牙通信,本篇文章对其进行优化。
首先由于项目需求,将蓝牙的搜索和连接做成Dialog形式,并且在搜索过程中加入弹窗。其次,将蓝牙连接和数据发送改成Service,便于整个项目里使用,不局限于某个activity里。然后,由于之前Server端的线程是在onCreat里开启的,所以Client端只有第一次能连接成功,断开之后就无法连接了,优化为加了一个Button,点击Button开启线程。最后之前run方法里的while(true)没有设置跳出循环的条件,影响性能,优化为当Client端断开连接之前,发送”close”消息,Server端收到即跳出循环。其他的一些小的优化直接看代码。
效果图:(两个GIF是分开录的,时间有点不同步,请见谅)
Client端代码:
NewActivity(实际上就是MainActivity,我改了下名字)
public class NewActivity extends ActionBarActivity {
private List<String> lstDevices = new ArrayList<String>();
private ArrayAdapter<String> adtDevices;
private ToggleButton tbtnSwitch;
private Button btnSearch, btn_showDialog, btn_stopService, btn_send;
private EditText edit;
private BluetoothAdapter btAdapt;
private String address;
private ListView lvBTDevices;
private ProgressDialog pDialog = null;//“请稍等”Dialog
private boolean isFinish = false;//搜索结束标志
//蓝牙设置Dialog
private AlertDialog.Builder dialogBuilder;
private AlertDialog dialog;
private int serviceBindStatus = 2;//service绑定状态
private MyService blueToothService;//蓝牙服务
private ServiceConnection connection = new ServiceConnection() {//蓝牙服务连接
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
blueToothService = ((MyService.BlueToothBinder) service).getService();
blueToothService.connect(address, btAdapt);
if (blueToothService.getConnectionStatus() == 1) {
Toast.makeText(NewActivity.this, "connect succeeded", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(NewActivity.this, "connect fail", Toast.LENGTH_LONG).show();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
edit = (EditText) findViewById(R.id.edit);
btn_showDialog = (Button) findViewById(R.id.btn_showDialog);
btn_showDialog.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showDialog();//显示蓝牙设置Dialog
}
});
btn_stopService = (Button) findViewById(R.id.btn_stopService);
btn_stopService.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//解绑只能在服务绑定之后才能进行,否则会引起程序崩溃,这里要进行判断
if (serviceBindStatus == 1) {
unbindService(connection);
serviceBindStatus = 2;
Log.d("tag", "unbind");
Toast.makeText(NewActivity.this, "Service stop", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(NewActivity.this, "Service dose not start", Toast.LENGTH_SHORT).show();
}
}
});
btn_send = (Button) findViewById(R.id.btn_send);
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
blueToothService.send(edit.getText().toString());
if (blueToothService.getSendStatus() == 1) {
Toast.makeText(NewActivity.this, "send succeed", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(NewActivity.this, "send fail", Toast.LENGTH_SHORT).show();
}
}
});
}
private void showDialog() {
dialogBuilder = new AlertDialog.Builder(NewActivity.this);
View dialogView = LayoutInflater.from(NewActivity.this)
.inflate(R.layout.activity_new, null);//蓝牙设置Dialog布局
dialogBuilder.setTitle("蓝牙设置");
dialogBuilder.setView(dialogView);
dialog = dialogBuilder.show();
//调整dialog大小
Window window = dialog.getWindow();
WindowManager.LayoutParams lp = window.getAttributes();
lp.gravity = Gravity.CENTER;
lp.width = 750;//宽高可设置具体大小
lp.height = 900;
dialog.getWindow().setAttributes(lp);
tbtnSwitch = (ToggleButton) dialogView.findViewById(R.id.tbtnSwitch);
btnSearch = (Button) dialogView.findViewById(R.id.btnSearch);
tbtnSwitch.setOnClickListener(new ClickEvent());
btnSearch.setOnClickListener(new ClickEvent());
// ListView及其数据源 适配器
lvBTDevices = (ListView) dialogView.findViewById(R.id.lvDevices);
adtDevices = new ArrayAdapter<String>(NewActivity.this,
android.R.layout.simple_list_item_1, lstDevices);
lvBTDevices.setAdapter(adtDevices);
lvBTDevices.setOnItemClickListener(new ItemClickEvent());//设置监听器获取数据
btAdapt = BluetoothAdapter.getDefaultAdapter();// 初始化本机蓝牙功能
if (btAdapt.getState() == BluetoothAdapter.STATE_OFF)// 读取蓝牙状态并显示于双状态按钮
tbtnSwitch.setChecked(false);
else if (btAdapt.getState() == BluetoothAdapter.STATE_ON)
tbtnSwitch.setChecked(true);
// 注册Receiver来获取蓝牙设备相关的结果,onReceive()里取得搜索所得的蓝牙设备信息
IntentFilter intent = new IntentFilter();
intent.addAction(BluetoothDevice.ACTION_FOUND);// 用BroadcastReceiver来取得搜索结果
intent.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
intent.addAction(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
intent.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
registerReceiver(searchDevices, intent);
}
private BroadcastReceiver searchDevices = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
Bundle b = intent.getExtras();
Object[] lstName = b.keySet().toArray();
// 显示所有收到的消息及其细节
for (int i = 0; i < lstName.length; i++) {
String keyName = lstName[i].toString();
Log.e(keyName, String.valueOf(b.get(keyName)));
}
//搜索设备时,取得设备的MAC地址
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
String str = device.getName() + "|" + device.getAddress();
if (lstDevices.indexOf(str) == -1)// 防止重复添加,
lstDevices.add(str); // 获取设备名称和mac地址
adtDevices.notifyDataSetChanged();//通知Activity刷新数据
isFinish = true;
}
}
};
class ItemClickEvent implements AdapterView.OnItemClickListener {
@Override
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2,
long arg3) {
btAdapt.cancelDiscovery();
String str = lstDevices.get(arg2);
String[] values = str.split("\\|");
address = values[1];
Log.d("address", values[1]);
Intent i = new Intent(NewActivity.this, MyService.class);
//绑定服务同样只能绑定一次,如果已绑定首先要解绑
if (serviceBindStatus == 1) {
unbindService(connection);
serviceBindStatus = 2;
Log.d("tag", "unbind");
}
bindService(i, connection, BIND_AUTO_CREATE);
serviceBindStatus = 1;
Log.d("tag", "bind");
dialog.dismiss();//取消蓝牙设置Dialog
lstDevices.clear();//清空搜索到的设备列表
}
}
//按钮监听器,打开本机蓝牙的设置
class ClickEvent implements View.OnClickListener {
@Override
public void onClick(View v) {
if (v == btnSearch)// 搜索蓝牙设备,在BroadcastReceiver显示结果
{
if (btAdapt.getState() == BluetoothAdapter.STATE_OFF) {// 如果蓝牙还没开启
Toast.makeText(NewActivity.this, "请先打开蓝牙", Toast.LENGTH_LONG).show();
return;
}
setTitle("本机蓝牙地址:" + btAdapt.getAddress());
lstDevices.clear();
btAdapt.startDiscovery();
showProgressDialog();
} else if (v == tbtnSwitch) {// 本机蓝牙启动/关闭
if (tbtnSwitch.isChecked() == false)
btAdapt.enable();
else if (tbtnSwitch.isChecked() == true)
btAdapt.disable();
}
}
}
//“请稍等”Dialog
private void showProgressDialog() {
pDialog = new ProgressDialog(NewActivity.this);
pDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
pDialog.setProgress(100);
pDialog.setMessage("请稍等...");
pDialog.setIndeterminate(false);
pDialog.show();
WindowManager.LayoutParams lp = pDialog.getWindow().getAttributes();
lp.gravity = Gravity.CENTER;
Window win = pDialog.getWindow();
win.setAttributes(lp);
new Thread(new Runnable() {
@Override
public void run() {
long startTime = System.currentTimeMillis();
int progress = 0;
//搜索结束或者搜索时间超过10秒跳出循环
while (!isFinish && System.currentTimeMillis() - startTime <= 10000) {
try {
progress += 10;
pDialog.setProgress(progress);
Thread.sleep(100);
} catch (InterruptedException e) {
pDialog.dismiss();
}
}
pDialog.dismiss();
//如果超过10秒,通知handler来Toast
if (System.currentTimeMillis() - startTime >= 10000) {
Message msg = new Message();
handler.sendMessage(msg);
}
isFinish = false;//标志重新置为false,等待下一次搜索
}
}).start();
}
public Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Toast.makeText(NewActivity.this, "搜索时间过长,请重试", Toast.LENGTH_SHORT).show();
super.handleMessage(msg);
}
};
}
MyService:
public class MyService extends Service {
static final String SPP_UUID = "00001101-0000-1000-8000-00805F9B34FB";
private BlueToothBinder binder=new BlueToothBinder();//自定义BlueToothBinder类
private BluetoothSocket btSocket;
private int connectionStatus,sendStatus;//蓝牙连接状态和发送状态
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public void connect(String address,BluetoothAdapter btAdapt){
UUID uuid = UUID.fromString(SPP_UUID);
BluetoothDevice btDev = btAdapt.getRemoteDevice(address);
try {
btSocket = btDev
.createRfcommSocketToServiceRecord(uuid);
try {
// 连接建立之前的先配对
if (btDev.getBondState() == BluetoothDevice.BOND_NONE) {
Method creMethod = BluetoothDevice.class
.getMethod("createBond");
Log.e("TAG", "开始配对");
creMethod.invoke(btDev);
}
} catch (Exception e) {
e.printStackTrace();
}
btSocket.connect();
Log.d("tag","connect succeeded");
connectionStatus=1;
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Log.d("tag","connect fail");
connectionStatus=2;
}
}
public int getConnectionStatus(){
return connectionStatus;
}
public int getSendStatus() {
return sendStatus;
}
public void send(String str){
OutputStream os = null;
try {
os = btSocket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
if (os != null) {
try {
os.write(str.getBytes("UTF-8"));
Log.d("tag","send succeed");
sendStatus=1;
} catch (IOException e) {
sendStatus=2;
e.printStackTrace();
}
}
}
@Override
public void onDestroy() {
super.onDestroy();
try {
send("close");//连接关闭之前手动发送close,通知Server端连接关闭
btSocket.close();//socket要记得关闭
Log.d("tag","socket close");
} catch (IOException e) {
e.printStackTrace();
}
}
class BlueToothBinder extends Binder{
//利用Binder得到Service实例
public MyService getService(){
return MyService.this;
}
}
}
蓝牙设置的布局文件就是前一篇文章的布局文件稍加修改,主活动的布局文件就是Button、EditText很简单,都不贴了。
Server端:
public class MainActivity extends ActionBarActivity {
private final UUID MY_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
private BluetoothAdapter bluetoothAdapter;
private final String NAME = "BlueTooth_Socket";
private AcceptThread acceptThread;
private Button btn_start, btn_searched;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
// 初始版本是在onCreate里就新建线程,这样的问题是只有第一次连接成功,断开之后就无法连接了
// 因为serverSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID)
// 是写在AcceptThread()的构造方法里的,断开之后就无法监听了
// acceptThread = new AcceptThread();
// acceptThread.start();
btn_searched = (Button) findViewById(R.id.searched);
btn_searched.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent discoverableIntent = new Intent(
BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent.putExtra(
BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(discoverableIntent);//本机蓝牙的内部设置
}
});
btn_start = (Button) findViewById(R.id.start);
//点击一次开一次线程,所以每次连接断开后,要再次点击之后才能开始监听
btn_start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
acceptThread = new AcceptThread();
acceptThread.start();
Toast.makeText(MainActivity.this, "开启监听", Toast.LENGTH_SHORT).show();
}
});
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Toast.makeText(MainActivity.this, String.valueOf(msg.obj), Toast.LENGTH_SHORT).show();
}
};
private class AcceptThread extends Thread {
private BluetoothServerSocket serverSocket;
private BluetoothSocket socket;
private InputStream is;
//构造函数里进行蓝牙监听
public AcceptThread() {
try {
serverSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
} catch (Exception e) {
}
}
@Override
public void run() {
try {
socket = serverSocket.accept();
//socket.connect();
Message message = new Message();
message.what = 1;
message.obj = "connect succeed";
handler.sendMessage(message);
Log.d("tag", "connected");
//如果不想在accept其他的连接,则调用BluetoothServerSocket的close()方法释放资源
// (调用该方法后,之前获得的BluetoothSocket实例并没有close。但由于RFCOMM一个时
// 刻只允许在一条channel中有一个连接,则一般在accept一个连接后,
// 便close掉BluetoothServerSocket)。这里如果不close,serverSocket被占用
//新的线程里的serverSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
//无法使用
serverSocket.close();
is = socket.getInputStream();
//原本跳出循环打算用socket.isConnected(),但是因为没有用connect(),
// 返回一直是false,行不通。只能在关闭前手动发送一个消息
while (true) {
Thread.sleep(1);//减少CPU占用率
byte[] buffer = new byte[128];
int count = is.read(buffer);
Message msg = new Message();
msg.what = 2;
msg.obj = new String(buffer, 0, count, "UTF-8");
//如果接受的消息为close,表示Client端已关闭连接,跳出循环
if (String.valueOf(msg.obj).equals("close")) {
Message m = new Message();
m.what = 3;
m.obj = "disconnect";
handler.sendMessage(m);
Log.d("tag", "break");
break;
}
handler.sendMessage(msg);//sendMessage写在这里防止close消息发送给handler
}
} catch (Exception e) {
}
}
}
}
操作步骤:
1、Server端点击本机可被搜索,然后点击开启监听
2、Client端点击蓝牙设置,搜索设备,点击搜索到的设备,Client端和Server端会同时Toast “connect succeed”
3、连接成功后,发送消息,Client端Toast “send succeed”,Server端Toast消息内容
4、点击停止服务,Client端Toast “Service stop”,Server端Toast “disconnect”
5、若要重新连接,重复上述步骤
注:别忘了蓝牙权限和Service注册