Android Bluetooth 蓝牙通信(二)
来源:互联网 发布:mac装win10多少钱 编辑:程序博客网 时间:2024/06/06 10:07
前面一篇文章实现了最简单的蓝牙通信,本篇文章对其进行优化。
首先由于项目需求,将蓝牙的搜索和连接做成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注册
- Android Bluetooth 蓝牙通信(二)
- Android BlueTooth蓝牙通信
- android 蓝牙 通信 bluetooth
- android 蓝牙 通信 bluetooth
- android -- 蓝牙 bluetooth (二) 打开蓝牙
- android -- 蓝牙 bluetooth (二) 打开蓝牙 .
- android -- 蓝牙 bluetooth (二) 打开蓝牙
- android -- 蓝牙 bluetooth (二) 打开蓝牙
- android -- 蓝牙 bluetooth (二) 打开蓝牙
- android -- 蓝牙 bluetooth (二) 打开蓝牙
- android -- 蓝牙 bluetooth (二) 打开蓝牙
- android -- 蓝牙 bluetooth (二) 打开蓝牙
- android -- 蓝牙 bluetooth (二) 打开蓝牙
- android -- 蓝牙 bluetooth (二) 打开蓝牙
- android -- 蓝牙 bluetooth (二) 打开蓝牙
- android -- 蓝牙 bluetooth (二) 打开蓝牙
- android -- 蓝牙 bluetooth (二) 打开蓝牙
- Android Bluetooth 蓝牙通信(一)
- 推荐系统:技术、评估及高效算法 第11章
- 4.NLTK之编写结构化程序
- linux中w命令使用
- 个人记录-LeetCode 77. Combinations
- Android利用Achartengine实现实时曲线图
- Android Bluetooth 蓝牙通信(二)
- Thread类和Runnable接口的区别
- Spring声明式事务原理分析
- 最全资料整理, 教你如何用HEXO搭建十分cool的属于自己的网站
- 调试botguard
- 使用七牛云存储markdown用的图片
- 434. Number of Segments in a String
- Centos7上Hadoop的安装和配置
- Vmware 的三种网络模式详解