android 手机与单片机之间的蓝牙通信

时间:2022-07-17 18:57:38

刚好碰到这蓝牙通信方面的项目,上网也看过其他人在蓝牙这方面写的博客,但大多都不全,给一些初触蓝牙的开发者造成一定的麻烦,自己当初也费了不少劲。所以把自己参考网上的一些资料用Android studio写的代码完全放出来,分享一下。菜鸟初写博客,若有不恰之处,请指出,必改正。下面我会把自己的思路和代码一一呈现。(PS:由于后期做了些逻辑操作,代码可能有点臃肿,请勿怪。还好完整的代码是会有的,里面有多出来的一两个类没有用的,但懒得重新打包了)

第一篇博客写完,感觉好菜。资源下载:http://download.csdn.net/detail/u013168302/9146907

1.添加权限

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

2. 成员变量跟onCreate()方法,为了方便阅读,把这些摆出来

public class PipelineActivity extends Activity {
    public static BluetoothSocket btSocket;
    private BluetoothAdapter bluetoothAdapter;
    private ArrayAdapter<String> deviceAdapter;
    private List<String> listDevices;
    private ListView listView;
    private LinearLayout btContent;
    private TextView btAllData;
    private Button openBT;
    private Button searchBT;
    final private static int MESSAGE_READ = 100;
    int i = 0;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.pipeline);

        listView = (ListView) this.findViewById(R.id.list);
        btContent = (LinearLayout) findViewById(R.id.bt_content_llt);
        btAllData = (TextView) findViewById(R.id.all_data);
        btAllData.setText(btAllData.getText(), TextView.BufferType.EDITABLE);//这行可实现TextView尾部追加http://blog.csdn.net/u013168302/article/details/48785927

        openBT = (Button) findViewById(R.id.open_btn);
        searchBT = (Button) findViewById(R.id.search_btn);

        listDevices = new ArrayList<String>();
        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        if (bluetoothAdapter.isEnabled()) {
            openBT.setText("关闭蓝牙");
        }
        deviceAdapter = new ArrayAdapter<String>(getApplicationContext(), R.layout.list_item, listDevices);

        openBT.setOnClickListener(new BTListener());
        searchBT.setOnClickListener(new BTListener());

        listView.setAdapter(deviceAdapter);
        listView.setOnItemClickListener(new ItemClickListener());//添加监听
    }


3.注册广播(因为下面搜索时要用到,所以现在前面注册)

private BroadcastReceiver receiver = new BroadcastReceiver() {

        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            
            //下面几行是为了在logcat里面看到搜索到的设备细节,需要的话,可以将注释打开
//            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 (listDevices.indexOf(str) == -1)// 防止重复添加
                    listDevices.add(str); // 获取设备名称和mac地址
                if (deviceAdapter != null) {
                    deviceAdapter.notifyDataSetChanged();
                }
            }
        }
    };



 
 
4.点击开启蓝牙,搜索蓝牙设备。 将搜索到设备名称和m 
ac地址通过BroadcastReceiver保存到list集合,再在listview中展示 

/**
     * 蓝牙开启与搜索按钮点击监听
     */
    class BTListener implements View.OnClickListener {
        @Override
        public void onClick(View view) {
            if (view.getId() == R.id.open_btn) {
                if (!bluetoothAdapter.isEnabled()) {
                    bluetoothAdapter.enable();//开启蓝牙
                    Intent enable = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
                    enable.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300); //300秒为蓝牙设备可见时间
                    startActivity(enable);
                    openBT.setText("关闭蓝牙");

                } else {
                    bluetoothAdapter.disable();//关闭蓝牙
                    openBT.setText("开启蓝牙");
                    if (btSocket != null) {
                        try {
                            btSocket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } else if (view.getId() == R.id.search_btn) {
                if (!bluetoothAdapter.isEnabled()) {
                    Toast.makeText(getApplicationContext(), "请先开启蓝牙", Toast.LENGTH_SHORT).show();
                } else {
                    btContent.setVisibility(View.GONE);
                    listView.setVisibility(View.VISIBLE);
                    if (listDevices != null) {
                        listDevices.clear();
                        if (deviceAdapter != null) {
                            deviceAdapter.notifyDataSetChanged();
                        }
                    }
                    bluetoothAdapter.startDiscovery();
                    IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
                    registerReceiver(receiver, filter);

                }
            }
        }
    }



5.点击listview的item,通过反射连接设备。连接设备之前需要UUID来配对。查看网上的例子和自己的一些实践发现,通过UUID会出现一些问题,当然也有可能只是我自己的代码有问题。所以在此采取反射来获取蓝牙socket对象。

/**
     * 蓝牙选项,listview列表点击监听
     */
    class ItemClickListener implements AdapterView.OnItemClickListener {
        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {

            if (!bluetoothAdapter.isEnabled()) {
                Toast.makeText(getApplicationContext(), "请先开启蓝牙", Toast.LENGTH_SHORT).show();
            } else {
                bluetoothAdapter.cancelDiscovery();//停止搜索
                String str = listDevices.get(position);
                String macAdress = str.split("\\|")[1];

                BluetoothDevice device = bluetoothAdapter.getRemoteDevice(macAdress);
                try {
                    Method clientMethod = device.getClass()
                            .getMethod("createRfcommSocket", new Class[]{int.class});
                    btSocket = (BluetoothSocket) clientMethod.invoke(device, 1);
                    connect(btSocket);//连接设备

                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }


6.连接蓝牙设备,并开辟子线程通信

在这里值得注意while里面的2行代码。这2行代码花了不少功夫才弄到的

(1)if(inputStream.available() >0==false)

(2)Thread.sleep(400);//等待0.4秒,作用:让数据接收完整
这2行用于处理inputStream读取数据不完整或混乱的问题,但感觉还是不够好。若谁有更好的方法,欢迎提出。
另外inputStream在这里是关不掉的,因为这是蓝牙通信,单片机每产生的数据都要传输到手机端。试过多次:一旦断开inputstream,while循环结束,单片机再次产生的数据无法传送。不知道我有没有弄错,欢迎指出。

 

 /**
     * 连接蓝牙及获取数据
     */
    public void connect(final BluetoothSocket btSocket) {
        try {
            btSocket.connect();//连接
            if (btSocket.isConnected()) {
                Log.e("----connect--- :", "连接成功");
                Toast.makeText(getApplicationContext(), "蓝牙连接成功", Toast.LENGTH_SHORT).show();
                listView.setVisibility(View.GONE);
                btContent.setVisibility(View.VISIBLE);
                new ConnetThread().start();//通信

            } else {
                Toast.makeText(getApplicationContext(), "蓝牙连接失败", Toast.LENGTH_SHORT).show();
                btSocket.close();
                listView.setVisibility(View.VISIBLE);
                btContent.setVisibility(View.GONE);
                Log.e("--------- :", "连接关闭");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 蓝牙通信管理
     */
    private class ConnetThread extends Thread {
        public void run() {
            try {
                InputStream inputStream = btSocket.getInputStream();
                byte[] data = new byte[1024];
                int len = 0;
                String result = "";

                while (len != -1) {
                    if (inputStream.available() > 0 == false) {//inputStream接收的数据是一段段的,如果不先
                        continue;
                    } else {
                        try {
                            Thread.sleep(500);//等待0.5秒,让数据接收完整
                            len = inputStream.read(data);
                            result = URLDecoder.decode(new String(data, "utf-8"));
//                          Log.e("----result:----- :", ">>>" + result);
                            Message msg = new Message();
                            msg.what = MESSAGE_READ;
                            msg.obj = result;
                            handler.sendMessage(msg);

                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                inputStream.close();//关不了,也好像不能关
                Log.e("--------- :", "关闭inputStream");
                if (btSocket != null) {
                    btSocket.close();
                }
            } catch (IOException e) {
                Log.e("TAG", e.toString());
            }
        }

    }


7.用Handler处理Message

private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MESSAGE_READ:
                    String result = (String) msg.obj;
                    String data = result.split("\\r\\n")[0];
                    Log.e("----data:----- :", ">>>" + data);
                    if (i < 6) {
                        Editable text = (Editable) btAllData.getText();
                        text.append(data);
                        btAllData.setText(text + "\r\n");
                        i++;
                    } else {
                        btAllData.setText(data + "\r\n");
                        i = 0;
                    }
                    break;
            }
        }
    };

    @Override
    protected void onDestroy() {
        unregisterReceiver(receiver);
        super.onDestroy();
    }




8.1布局文件(尚未优化)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:background="#000000"
        android:gravity="center"
        android:paddingLeft="20dp"
        android:text="@string/pipeline"
        android:textColor="#ffffff"
        android:textSize="20sp" />

    <LinearLayout
        android:visibility="gone"
        android:id="@+id/bt_content_llt"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_weight="1"
        android:background="#ffffff"
        android:orientation="vertical">

        <LinearLayout style="@style/bt_llt_style"
            android:layout_weight="3">

            <TextView
                style="@style/btTV_style"
                android:gravity="left"
                android:layout_weight="1"
                android:paddingTop="20dp"
                android:text="@string/all_data" />

            <TextView
                android:id="@+id/all_data"
                style="@style/btTV_style"
                android:gravity="left"
                android:paddingLeft="2dp"
                android:paddingTop="20dp"
                android:textSize="16sp"
                android:layout_weight="2" />

        </LinearLayout>

    </LinearLayout>

    <ListView
        android:id="@+id/list"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_weight="1"
        android:background="#f0f0f0"
        android:divider="#c0c0c0"
        android:dividerHeight="1dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:layout_gravity="center"
        android:layout_marginTop="10dp"
        android:orientation="horizontal">

        <Button
            android:id="@+id/open_btn"
            style="@style/bt_button_style"
            android:text="@string/open_bluetooth" />

        <Button
            android:id="@+id/search_btn"
            style="@style/bt_button_style"
            android:text="@string/search_bluetooth" />
    </LinearLayout>

</LinearLayout>


8.2 item布局

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/device_name_tv"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:paddingLeft="15dp"
    android:gravity="center|left"
    android:textColor="#000000"
    android:background="@drawable/list_item_selector"
    android:textSize="18sp">

</TextView>

8.3 资源文件strings.xml

<resources>
    <string name="app_name">bluetooth</string>

    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
  
    <string name="pipeline">管道检测仪</string>
    <string name="open_bluetooth">开启蓝牙</string>
    <string name="close_bluetooth">关闭蓝牙</string>
    <string name="search_bluetooth">搜索蓝牙</string>
    <string name="all_data">全部数据:</string>
    <color name="item_default">#F0F0F0</color>
    <color name="item_press">#A5D8F5</color>
</resources>

8.4 style

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
    </style>

    <style name="btTV_style">
        <item name="android:background">#ffffff</item>
        <item name="android:gravity">left|center</item>
        <item name="android:paddingLeft">10dp</item>
        <item name="android:paddingRight">10dp</item>
        <item name="android:textSize">20sp</item>
        <item name="android:layout_width">1dp</item>
        <item name="android:layout_height">match_parent</item>
    </style>

    <style name="bt_llt_style">
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">1dp</item>
        <item name="android:layout_weight">1</item>
        <item name="android:orientation">horizontal</item>
    </style>

    <style name="bt_button_style">
        <item name="android:layout_width">1dp</item>
        <item name="android:layout_height">match_parent</item>
        <item name="android:layout_marginLeft">20dp</item>
        <item name="android:layout_marginRight">20dp</item>
        <item name="android:layout_weight">1</item>
        <item name="android:background">#5CB85C</item>
    </style>

</resources>


8.5  listview的item选择器

 
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- 选中 -->
    <item android:state_pressed="true" android:drawable="@color/item_press" />
    <!-- 默认 -->
    <item android:drawable="@color/item_default"/>

</selector>