Android Bluetooth 蓝牙通信(二)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/AmazingUU/article/details/54292584

前面一篇文章实现了最简单的蓝牙通信,本篇文章对其进行优化。

首先由于项目需求,将蓝牙的搜索和连接做成Dialog形式,并且在搜索过程中加入弹窗。其次,将蓝牙连接和数据发送改成Service,便于整个项目里使用,不局限于某个activity里。然后,由于之前Server端的线程是在onCreat里开启的,所以Client端只有第一次能连接成功,断开之后就无法连接了,优化为加了一个Button,点击Button开启线程。最后之前run方法里的while(true)没有设置跳出循环的条件,影响性能,优化为当Client端断开连接之前,发送”close”消息,Server端收到即跳出循环。其他的一些小的优化直接看代码。

效果图:(两个GIF是分开录的,时间有点不同步,请见谅)

BlueToothServerBlueToothServer























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注册

猜你喜欢

转载自blog.csdn.net/AmazingUU/article/details/54292584