android蓝牙通讯开发(详细)「建议收藏」

android蓝牙通讯开发(详细)「建议收藏」新建一个工程之后,我们可以先看到界面左边的项目栏,我们可以看到,除了app目录以外,大多数的文件和目录都是自动生成的,我们也不需要对他们进行修改,而app目录之下的文件才是我们工作的重点。下面,我先对

大家好,又见面了,我是你们的朋友全栈君。

新建一个工程之后,我们可以先看到界面左边的项目栏,我们可以看到,除了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.
this

new 
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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/154515.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • java设置content type_Response Content Type设置[通俗易懂]

    java设置content type_Response Content Type设置[通俗易懂]1.常见的contenttype:.aiff=audio/aiff.anv=application/x-anv.asa=text/asa.asf=video/x-ms-asf.asp=text/asp.asx=video/x-ms-asf.au=audio/basic.avi=video/avi.awf=application/vnd.adobe.workfl…

  • 史上最详细的IDEA优雅整合Maven+SSM框架(详细思路+附带源码)

    史上最详细的IDEA优雅整合Maven+SSM框架(详细思路+附带源码)网上很多整合SSM博客文章并不能让初探ssm的同学思路完全的清晰,可以试着关掉整合教程,摇两下头骨,哈一大口气,就在万事具备的时候,开整,这个时候你可能思路全无~中招了咩~,还有一些同学依旧在使用eclipse或者Myeclipse开发,我想对这些朋友说IDEA的编译速度很快,人生苦短,来不及解释了,直接上手idea吧。这篇文章每一步搭建过程都测试过了,应该不会有什么差错。本文章还有个比较优秀的特点,就是idea的使用,基本上关于idea的操作都算是比较详细的,所以不用太担心不会撸idea!最后,本文

  • SpringBoot连接使用PostgreSql数据库

    SpringBoot连接使用PostgreSql数据库目录一、介绍1、情况说明2、安装软件及依赖包二、配置连接数据库其他情况一、介绍1、情况说明在这里我使用SpringBoot配置Mybaits连接到PostgreSql数据库的。我的源码也会提供给大家(此文末尾),效果如下数据库:运行效果:2、安装软件及依赖包完整搭建SpringBoot及依赖包:https://blog.csdn.net…

  • RegisterStartupScript 用法

    RegisterStartupScript 用法ClientScript.RegisterStartupScript方法有3个重载:1:RegisterStartupScript(Page,String,String)向ScriptManager控件注册一个启动脚本块并将该脚本块添加到页面中。需要向第三个参数添加脚本标签。第一个参数为页面Page。第二个脚本函数的名字,随便起。第三个是形式的脚本内容。2:RegisterS

  • phpproxy建立代理服务器_proxy设计模式

    phpproxy建立代理服务器_proxy设计模式代理,指的就是一个角色代表另一个角色采取行动,就象生活中,一个红酒厂商,是不会直接把红酒零售客户的,都是通过代理来完成他的销售业务。而客户,也不用为了喝红酒而到处找工厂,他只要找到厂商在当地的代理就行了,具体红酒工厂在那里,客户不用关心,代理会帮他处理。代理模式,就是给某一对象提供代理对象,并由代理对象控制具体对象的引用。代理模式涉及的角色:抽象主题角色,声明了代理主题和真实主题的公共…

    2022年10月30日
  • 分页的sql语句_自动分页

    分页的sql语句_自动分页下文将为您介绍三种SQL分页语句写法,如果您也遇到过类似的问题,不妨一看,相信对您会有所启迪。SQL分页操作是经常会遇到的,下面就将为您介绍三种SQL分页语句,供您参考,希望对您学习SQL分页能够有所帮助。方法一(适用于SQLServer2000/2005)SELECTTOP页大小* FROMtable1 WHEREidNOTIN

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号