zoukankan      html  css  js  c++  java
  • android蓝牙通讯开发(详细)

    新建一个工程之后,我们可以先看到界面左边的项目栏,我们可以看到,除了app目录以外,大多数的文件和目录都是自动生成的,我们也不需要对他们进行修改,而app目录之下的文件才是我们工作的重点。下面,我先对app目录下的内容进行一些讲解。

    1.AndroidManifest.xml

    这是整个项目的配置文件,我们在程序中定义的四大组件都需要在这里注册,另外,也可以在这里给应用程序添加权限声明。

    2.java

    这个是放置我们所有java代码的地方。

    3.res

    这个项目中所使用到的所有图片、布局、字符串资源都要存放在这个项目。其中,drawable文件夹和mipmap文件夹都是用来存放图片资源的。layout文件夹是用来存放布局资源的,values文件夹是存放字符串等资源的。

    现在,我开始介绍一下关于android 蓝牙的通信的知识。

    首先,我们要在一个页面中打开和关闭蓝牙。我们可以点击layout下的布局文件,先在其中添加两个按钮。在布局文件中,我们可以使用两种方法来调出两个按钮。第一种就是直接在design界面,点击Palette,找到Button,点击然后拖动到旁边的界面上。第二种就是在Text界面直接打入代码,例如:

    1 <Button
    android:id="@+id/bt" 2 android:layout_width="match_parent" 3 android:layout_height="wrap_content" 4 tools:ignore="MissingConstraints" />

    其中,layout_width和layout_height是必须要有的属性,设置为match_parent表示充满父控件空间,wrap_content表示根据控件自身大小显示。id则是这个控件的名称。然后我们开始设置这个按钮的点击事件。在我们的java文件中先定义一个Button对象,然后使用Button的对象调用findViewId方法。方法里的参数为控件的id。然后就设置点击事件。

    复制代码
    button.findViewById(R.id.bt);
    button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    
                }
            });
    复制代码

    在onClick方法中添加事件的代码即可。

    蓝牙的开启

    首先,要在新建项目中的AndroidManifest.xml中声明两个权限:BLUETOOTH权限和BLUETOOTH_ADMIN权限。其中,BLUETOOTH权限用于请求连接和传送数据;BLUETOOTH_ADMIN权限用于启动设备、发现或进行蓝牙设置,如果要拥有该权限,必须现拥有BLUETOOTH权限。

     因为android 6.0之后采用新的权限机制来保护用户的隐私,如果我们设置的targetSdkVersion大于或等于23,则需要另外添加ACCESS_COARSE_LOCATION和ACCESS_FINE_LOCATION权限,否则,可能会出现搜索不到蓝牙设备的问题。

    复制代码
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    
     
    复制代码

    然后要在Activity中,添加

    BluetoothAdapter blueadapter = BluetoothAdapter.getDefaultAdapter();
    //获取蓝牙适配器

    来获取蓝牙适配器的对象

    在按钮的点击事件中添加以下代码来开启蓝牙

    if(blueadapter==bull)
    //表示手机不支持蓝牙
    return;
    
     
    复制代码
     if (!blueadapter.isEnabled())
            //判断本机蓝牙是否打开
            {//如果没打开,则打开蓝牙
            blueadapter.enable();
            }
    
     
    复制代码

    同理,使用disable()可以关闭蓝牙。

    搜索蓝牙

    接下来是搜索蓝牙设备,如果想要改变自己蓝牙能否被搜索的状态,可以使用以下的代码来使自己的蓝牙设备可被发现

    复制代码
    if (blueadapter.getScanMode() != BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE) //不在可被搜索的范围
            {
            Intent discoverableIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
            discoverableIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);//设置本机蓝牙在300秒内可见
            startActivity(discoverableIntent);
            }
    复制代码

    如果想搜索别人的设备,就可以在按钮的点击事件中调用startDiscover()来搜索蓝牙

    复制代码
    public void doDiscovry() {
        if (blueadapter.isDiscovering()) {
            //判断蓝牙是否正在扫描,如果是调用取消扫描方法;如果不是,则开始扫描
            blueadapter.cancelDiscovery();
        } else
            blueadapter.startDiscovery();
    
    }
    复制代码

    我们要想获得搜索到的结果,就需要注册广播

    首先,要定义一个列表控件listview和一个集合适配器和两个ArrayList集合类的对象,这两个对象都是用来存设备的地址,只是有一个不是用来显示出来的。在主类中定义以下的集合适配器和两个ArrayList集合类的对象,然后在onCreate中定义适配器

    复制代码
    public ArrayAdapter adapter;
    ListView listView = (ListView) findViewById(R.id.list);//控件 列表
    //定义一个列表,存蓝牙设备的地址。
    public ArrayList<String> arrayList=new ArrayList<>();
    //定义一个列表,存蓝牙设备地址,用于显示。
    public ArrayList<String> deviceName=new ArrayList<>();
    //定义适配器
    adapter = new ArrayAdapter(this, android.R.layout.simple_expandable_list_item_1, deviceName);

    //给列表添加适配器
    listView.setAdapter(adapter);
    复制代码

    然后定义广播以及处理广播的消息

    复制代码
    IntentFilter intentFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);//注册广播接收信号
    registerReceiver(bluetoothReceiver, intentFilter);//用BroadcastReceiver 来取得结果
    
    private final BroadcastReceiver bluetoothReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (BluetoothDevice.ACTION_FOUND.equals(action)) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                deviceName.add("设备名:"+device.getName()+"\n" +"设备地址:"+device.getAddress() + "\n");//将搜索到的蓝牙名称和地址添加到列表。
                arrayList.add( device.getAddress());//将搜索到的蓝牙地址添加到列表。
                adapter.notifyDataSetChanged();//更新
            }
        }
     };
    复制代码

    搜索完设备后,要记得注销广播。注册后的广播对象在其他地方有强引用,如果不取消,activity会释放不了资源 。

    protected void onDestroy(){
        super.onDestroy();//解除注册
        unregisterReceiver(bluetoothReceiver);
    }

    4.了解targetSdkVersion是否大于或等于23

           若是大于或等于23,除了添加了蓝牙权限外,还要动态获取位置权限,才能将搜索到的蓝牙设备显示出来。若是小于,则不需要动态获取权限。
    动态申请权限

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public void applypermission() {
            if (Build.VERSION.SDK_INT >= 23) {
                //检查是否已经给了权限
                int checkpermission = ContextCompat.checkSelfPermission(getApplicationContext(),
                        Manifest.permission.ACCESS_FINE_LOCATION);
                if (checkpermission != PackageManager.PERMISSION_GRANTED) {//没有给权限
                    Log.e("permission""动态申请");
                    //参数分别是当前活动,权限字符串数组,requestcode
                    ActivityCompat.requestPermissions(WiFiMainActivity.thisnew String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
                }
            }
        }
     
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(WiFiMainActivity.this"已授权", Toast.LENGTH_SHORT).show();
            else {
                Toast.makeText(WiFiMainActivity.this"拒绝授权", Toast.LENGTH_SHORT).show();
            }
     
        }

     配对蓝牙设备

     蓝牙的配对和连接有两种方式。一种是每个设备作为一个客户端去连接一个服务端,向对方发起连接。另一种则是作为服务端来接收客户端发来连接的消息。蓝牙之间的数据传输采用的是和TCP传输类似的传输机制。

    1.作为客户端连接
           首先要获取一个代表远程设备BluetoothDevice的对象,然后使用该BluetoothDevice的对象来获取一个BluetoothSocket对象。BluetoothSocket对象调用connect()可以建立连接。

           蓝牙连接整个过程需要在子线程中执行的,并且要将 scoket.connect()放在一个新的子线程中,因为如果将这个方法也放在同一个子线程中解决的话,就会永远报错read failed, socket might closed or timeout, read ret: -1;借鉴网上的方法:再开一个子线程专门执行socket.connect()方法,问题可以解决;

           另外,借鉴网上方法和建议,在获得socket的时候 ,尽量不使用uuid方式;因为这样虽然能够获取到socket 但是不能进行自动,所以使用的前提是已经配对了的设备连接;

           使用反射的方式,能够自动提示配对,也适合手机间通信。

    首先,在设置列表中显示的蓝牙的点击事件,其中ClientThread是连接,发送和接收的线程类

    复制代码
    //定义列表Item的点击事件
            listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
                    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
                    device = adapter.getRemoteDevice(arrayList.get(i));
                    mge = editText.getText().toString() + "\r\n";//获得编辑文本框里的文字
                    clientThread = new ClientThread(device, mge, context);
                    clientThread.start();
                }
    
    
            });
    复制代码

    然后定义一个线程类,在他的构造方法中接收listview点击事件里传来的参数

    class ClientThread extends Thread {
    
    public void run() {
    }
    }

    在run方法中输入以下代码

    final BluetoothSocket socket = (BluetoothSocket) device.getClass().getDeclaredMethod("createRfcommSocket", new Class[]{int.class}).invoke(device, 1);

       代码中的device需要把注册广播时的device作为参数传进线程中。注意,传进来的device的值要为远程设备的地址,若不是或有出入,则可能会出现NullPointerException异常,并提示尝试调用一个空的对象。为了解决这个问题,可以把显示获得的device名字、地址和传入线程的device的地址分在不同的集合类。传入线程的device使用只有设备地址的集合类。

           在连接蓝牙之前,还要先取消蓝牙设备的扫描,否则容易连接失败。

    adapter.cancelDiscovery();//adapter为获取到的蓝牙适配器
    socket.connect();//连接

    2.作为服务端连接
           服务端接收连接需要使用BluetoothServerSocket类,它的作用是监听进来的连接,在一个连接被接收之后,会返回一个BluetoothSocket对象,这个对象可以用来和客户端进行通信。

           与客户端一样,服务端也要在子线程中实现。通过调用listenUsingRfcommWithServiceRecord(String,UUID)方法可以得到一个BluetoothServerSocket的对象,然后再用这个对象来调用accept()来返回一个BluetoothSocket对象。由于accept()是个阻塞的方法,它会直到接收到一个连接或异常之后才会返回,所以要放在子线程中。

    bluetoothServerSocket=bluetoothAdapter.listenUsingRfcommWithServiceRecord(bluetoothAdapter.getDefaultAdapter().getName(), UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
    //bluetoothServerSocket= (BluetoothServerSocket) bluetoothAdapter.getClass().getMethod("listenUsingRfcommOn",new Class[]{int.class}).invoke(bluetoothAdapter,10);
    socket=bluetoothServerSocket.accept();//接收连接

     这个连接时只允许一个客户端连接,因此在BluetoothServerSocket对象接收到一个连接请求时就要立刻调用close()方法把服务端关闭。

    当两个设备成功连接之后,双方都会有一个BluetoothSocket对象,这时,就可以在设备之间传送数据了。

           1.使用getOutputStream()方法来获取输出流来处理传输。

           2.调用write()。

    复制代码
    复制代码
    os = socket.getOutputStream();//获取输出流
    if (os != null) {//判断输出流是否为空
        os.write(message.getBytes("UTF-8"));
    }
    os.flush();//将输出流的数据强制提交
    os.close();//关闭输出流
    }
    复制代码
    复制代码

     3.使用getInputStream()方法来获取输出流来处理传输。

    4.创建一个新的线程来read()输入流,这里是接收16进制数然后转换为10进制数显示

    复制代码
    ew Thread(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    while (true) {
                                        int count = 0;
                                        while (count == 0) {
                                            count = is.available();
                                        }
                                        byte buf[] = new byte[count];
                                        if (buf != null) {
                                            is.read(buf);
                                           // BuletoothMainActivity.num++;
                                            String message = BuletoothMainActivity.bytesToIntString(buf);
    
                                            BuletoothMainActivity.UpdateRevMsg(message);
                                        }
    
                                    }
                                } catch (IOException e) {
                                    e.printStackTrace();
                                }
                            }
                        }).start();
    复制代码
    复制代码
    public static String bytesToIntString(byte[] bytes) {//16进制转10进制以字符串形式输出显示
            String result = "";
            for (int i = 0; i < bytes.length; i++) {
                String hexString = Integer.toString(bytes[i] & 0xFF);
                if (hexString.length() == 1) {
                    hexString = '0' + hexString;
                }
                result += hexString.toUpperCase();
            }
            return result;
        }
    复制代码
    public static void UpdateRevMsg(String revMsg) {
        mRevMsg=revMsg;
        handler.post(RefreshTextView);
    }
    
    private static Runnable RefreshTextView=new Runnable() {
        @Override
        public void run() {
            textView2.setText(mRevMsg);
        }
    };

    在服务端发送和接收也类似上面。

    转自:https://www.cnblogs.com/lwkdbk/p/10644838.html

  • 相关阅读:
    'Undefined symbols for architecture i386,clang: error: linker command failed with exit code 1
    The codesign tool requires there only be one 解决办法
    XCode iOS project only shows “My Mac 64bit” but not simulator or device
    Provisioning profile XXXX can't be found 的解决办法
    UIView 中的控件事件穿透 Passthrough 的实现
    Xcode4.5出现时的OC新语法
    xcode 快捷键(持续更新)
    打越狱包
    php缓存与加速分析与汇总
    浏览器的判断
  • 原文地址:https://www.cnblogs.com/javalinux/p/14549137.html
Copyright © 2011-2022 走看看