基于Android Studio经典蓝牙APP—继上一次的完善版
- 考虑到好友网友们反馈的问题总结了以下几点:
1、工程下载爆红:版本问题—gradle:4.1.1。
2、无接收数据功能,怎么实现:这里我会附上程序大家自行理解.
3、程序突然闪退问题:那是因为发送和接收广播等线程上起冲突了,大家看修改后的程序进行理解。
6、工程加载是报错:可能是中文路径等问题。以下是好心好友分享解决的方法:新添工程报错问题
5、界面上控件的颜色调不了:
res/values/themes.xml下的
<style name="Theme.HelloWorld" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
改为:
<style name="Theme.HelloWorld" parent="Theme.MaterialComponents.DayNight.DarkActionBar.Bridge">
- 这里我把我修改完善好的程序贴上并做相关的解释,还会附加上工程文件给大家自行下载。
如果不是直接使用我的工程文件,而是下自己手写一遍的,只需要我下面贴上的程序即可。
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.mybluetooth_finish">
<!-- 管理蓝牙需要 -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- 搜索蓝牙需要,因为蓝牙可以被用来定位,所以需要定位权限 -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- android:icon="@mipmap/ic_launcher"-->
<application
android:allowBackup="true"
android:icon="@drawable/huawei1"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Mybluetooth_finish">
<meta-data
android:name="com.google.android.actions"
android:resource="@xml/frame" />
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<!--流式布局一-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/Reminder"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#F42C2C"
android:gravity="center"
android:text="@string/reminder"
android:textSize="15dip" />
</LinearLayout>
<!--流式布局二-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="@drawable/frame3"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/Broadcast"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/frame1"
android:gravity="center"
android:text="@string/Broadcast"
android:textSize="10dp" />
<Button
android:id="@+id/CloseBT"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/frame2"
android:gravity="center"
android:onClick="CloseBlueTooth"
android:text="@string/close"
android:textSize="10dp" />
<Button
android:id="@+id/OpenBT"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/frame5"
android:gravity="center"
android:onClick="OpenBlueTooth"
android:text="@string/open"
android:textColor="#37E2F8"
android:textSize="13dp" />
<Button
android:id="@+id/DiscoveryBT"
android:layout_width="60dp"
android:layout_height="60dp"
android:layout_marginLeft="15dp"
android:layout_marginRight="15dp"
android:background="@drawable/myframe"
android:gravity="center"
android:text="@string/discovery"
android:textSize="10dp" />
</LinearLayout>
<!--流式布局三-->
<LinearLayout
android:id="@+id/beizhu"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#C3C3C3"
android:gravity="center"
android:text="@string/divide" />
</LinearLayout>
<!--流式布局四-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@drawable/search">
<ListView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<TextView
android:id="@+id/receive"
android:layout_width="match_parent"
android:layout_height="280dp"
android:background="@drawable/receive"
android:textSize="22dp"
android:textColor="#FF6297" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="40dp"
android:orientation="horizontal">
<EditText
android:id="@+id/send"
android:layout_width="match_parent"
android:layout_height="40dp"
android:layout_weight="1"
android:background="@drawable/keyboard"
android:textColor="#00B0FF" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/button_send"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_weight="1"
android:background="@drawable/frame2"
android:text="发送"
android:textColor="#e069F0AE"
android:textSize="15dp" />
<Switch
android:id="@+id/button_open_close1"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_weight="1"
android:background="@drawable/frame4"
android:text=" 电磁阀"
android:textSize="15dp"
android:textColor="#FBE204" />
<Switch
android:id="@+id/button_open_close2"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_weight="1"
android:background="@drawable/frame3"
android:text=" LED"
android:textSize="15dp"
android:textColor="#FBE204" />
<Switch
android:id="@+id/button_open_close3"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_weight="1"
android:background="@drawable/frame1"
android:text=" 电磁锁"
android:textSize="15dp"
android:textColor="#FBE204" />
</LinearLayout>
</LinearLayout>
MainActivity.java
package com.example.mybluetooth_finish;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class MainActivity extends AppCompatActivity {
private Toast mToast;
//创建广播接收者的对象
MyReceiver myReceiver=new MyReceiver();
//获取蓝牙适配器
private BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
//创建搜索蓝牙列表的///
public ArrayAdapter arrayAdapter; //这个适配器列表是用来加载到列表的数据
public ArrayList<BluetoothDevice> deviceAdress = new ArrayList<>(); //存放蓝牙设备(这里Adress我忘了改过来了,这是存放设备不是设备地址)
public ArrayList<String> deviceName = new ArrayList<>(); //存放蓝牙名称和地址
public ListView listView; //定义展示列表
//手机连接的UUID
//设备连接的UUID由厂商决定。 00001101-0000-1000-8000-00805F9B34FB
private final String BLUETOOTH_UUID = "00001101-0000-1000-8000-00805F9B34FB"; //蓝牙通信的UUID,必须为这个,如果换成其他的UUID会无法通信
private BluetoothSocket bluetoothSocket = null;
private EditText editText_send;
private TextView textView_receive;
private Button button_send;
InputStream inputStream; // 用来收数据
//InputStream inputStream = bluetoothSocket.getInputStream();
OutputStream outputStream; // 用来发数据
String Receive_Data;//存储接收的数据
int BlueTooth_Connect_State=0; //记录蓝牙连接状态的
//
//"11","22","33","44","55","66","77","88","99"
//##########################################################################################################################//
public class MyReceiver extends BroadcastReceiver {
//private static final String action1="com.mingrisoft"; //声明第一个动作
//private static final String action2="mingrisoft"; //声明第二个动作
@Override
public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
Toast.makeText(context, "开始", Toast.LENGTH_SHORT).show();
} else if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//创建搜索蓝牙列表的///
for (int i = 0; i < deviceAdress.size(); i++) {
if (deviceAdress.get(i).getAddress().equals(device.getAddress())) return;
//上面if语句就是去除已经获取的蓝牙设备
}
// 不是重复的就添加到列表中(获取未配对的蓝牙设备)
deviceAdress.add(device); //添加地址到列表中 用于鉴别是否已经添加列表和点击事件用的
deviceName.add("地址:"+device.getAddress()+"\n"+"名称:"+device.getName()); //存放蓝牙名称和地址用于显示到列表上的
arrayAdapter.notifyDataSetChanged(); //更新列表
Connect_BT(deviceAdress);
///
} else if(intent.getAction().equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
}
//if (intent.getAction().equals(action1)){
// Toast.makeText(context, "MyReceiver收到:com.mingrisoft的广播", Toast.LENGTH_SHORT).show(); //回复该动作收到广播
//} else if (intent.getAction().equals(action2)){
// Toast.makeText(context, "MyReceiver收到:mingrisoft的广播", Toast.LENGTH_SHORT).show(); //回复该动作收到广播
//}
}
}
//##########################################################################################################################//
///
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//创建搜索蓝牙列表的///并把
arrayAdapter = new ArrayAdapter(this, android.R.layout.simple_expandable_list_item_1,deviceName); //实例化ArrayAdapter对象deviceName集合数据放入arrayAdapter适配器集合内
listView = (ListView) findViewById(R.id.list); //获取列表框的
listView.setAdapter(arrayAdapter); //将arrayAdapter集合内的数据加载到列表框 就是适配器对象与ListView关联
// //
// //创建广播接收者的对象
// MyReceiver myReceiver=new MyReceiver();
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BluetoothDevice.ACTION_FOUND);
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
intentFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED );
//上面是添加动作事件
//过滤器
intentFilter.addAction("com.mingrisoft");
//注册广播接收者的对象
registerReceiver(myReceiver,intentFilter);
Button button= (Button) findViewById(R.id.Broadcast); //获取布局文件中的广播按钮
button.setOnClickListener(new View.OnClickListener() { //为按钮设置单击事件
@Override
public void onClick(View v) {
//Intent intent=new Intent(); //创建Intent对象
//intent.setAction("com.mingrisoft"); //为Intent添加动作com.mingrisoft
//sendBroadcast(intent); //发送广播
//蓝牙刷新///
deviceAdress.clear(); //
deviceName.clear(); //
startDiscovery(); //
// listView.setAdapter(arrayAdapter); //将arrayAdapter集合内的数据加载到列表框 就是适配器对象与ListView关联
}
});
//按钮搜寻蓝牙
Button button_discovery = (Button) findViewById(R.id.DiscoveryBT);
button_discovery.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startDiscovery();
}
});
button_send = (Button) findViewById(R.id.button_send);
textView_receive = findViewById(R.id.receive);
editText_send = findViewById(R.id.send);
button_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
textView_receive.setText(" 发送到数据:" + editText_send.getText());
String text = editText_send.getText().toString();
try {
if(BlueTooth_Connect_State==1){
outputStream = bluetoothSocket.getOutputStream();
outputStream.write((text+"\r\n").getBytes()); //后面加了回车换行然后进行转换为比特流传输
outputStream.flush();
editText_send.setText(""); //清空编辑框
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
/***************************一系列的开关*********************************************/
Switch button_oc1 = (Switch) findViewById(R.id.button_open_close1);
button_oc1.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(BlueTooth_Connect_State==1){
if(isChecked){
try {
outputStream = bluetoothSocket.getOutputStream();
outputStream.write("button1ON\r\n".getBytes());
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}else{
try {
outputStream = bluetoothSocket.getOutputStream();
outputStream.write("button1OFF\r\n".getBytes());
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
});
Switch button_oc2 = (Switch) findViewById(R.id.button_open_close2);
button_oc2.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(BlueTooth_Connect_State==1){
if(isChecked){
try {
outputStream = bluetoothSocket.getOutputStream();
outputStream.write("button2ON\r\n".getBytes());
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}else{
try {
outputStream = bluetoothSocket.getOutputStream();
outputStream.write("button2OFF\r\n".getBytes());
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
});
Switch button_oc3 = (Switch) findViewById(R.id.button_open_close3);
button_oc3.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if(BlueTooth_Connect_State==1){
if(isChecked){
try {
outputStream = bluetoothSocket.getOutputStream();
outputStream.write("button3ON\r\n".getBytes());
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}else{
try {
outputStream = bluetoothSocket.getOutputStream();
outputStream.write("button3OFF\r\n".getBytes());
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
});
}
//判断是否支持蓝牙
public boolean isSupportBlueTooth() {
if(mBluetoothAdapter != null) {
return true;
}
else {
return false;
}
}
//获取蓝牙状态
public boolean BlueToothState() {
assert (mBluetoothAdapter != null); //若不支持该蓝牙设备会有个断言
return mBluetoothAdapter.isEnabled();
}
//打开蓝牙
public void OpenBlueTooth(View view) {
if(isSupportBlueTooth() == true) {
if(!BlueToothState()) {
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(intent,0);
showToast("亲,打开了噢!需要什么帮助吗?");
}
else {
showToast("亲,已经打开了噢,无需重复打开。");
}
}
else {
showToast("亲,您不支持此蓝牙设备!");
}
}
//关闭蓝牙
public void CloseBlueTooth(View view) {
mBluetoothAdapter.disable();
showToast("亲,我们会再见面的!");
}
// 开始搜索
public void startDiscovery() {
if (mBluetoothAdapter.isDiscovering()) {mBluetoothAdapter.cancelDiscovery(); Toast.makeText(this,"搜索器打开",Toast.LENGTH_SHORT).show();}
mBluetoothAdapter.startDiscovery();
if (!mBluetoothAdapter.isDiscovering()) {Toast.makeText(this,"搜索器没打开",Toast.LENGTH_SHORT).show();}
}
//取消搜索
public void cancelScanBule() {
mBluetoothAdapter.cancelDiscovery();
}
//在activity结束时要注销;
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
//解除注册
unregisterReceiver(myReceiver);
}
//连接蓝牙
public void Connect_BT(ArrayList<BluetoothDevice> deviceAdress) {
//MainActivity 实现OnItemClickListener 然后重写方法
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//final BluetoothDevice[] romoteDevice = new BluetoothDevice[1];
if(mBluetoothAdapter.isDiscovering())
{
mBluetoothAdapter.cancelDiscovery();
}
BluetoothDevice clickDevice = (BluetoothDevice)deviceAdress.get(position);
String s1 = String.valueOf(position); //编号
Toast.makeText(MainActivity.this, s1 + "--" + clickDevice.getName() + "--" + clickDevice.getAddress(), Toast.LENGTH_SHORT).show();
//在连接前需要先关闭搜索
//点击列表,去请求服务器
if (clickDevice != null) {
new Thread(new Runnable() {
@Override
public void run() {
try {
//需要配对码进行配对连接
// Method method = BluetoothDevice.class.getMethod("createBond");
Log.e(getPackageName(), "开始配对");
// method.invoke(deviceAdress.get(position));
// deviceAdress.get(position).createBond();
//这里如果没有配对过的设备是会弹出窗口输入配对码的
bluetoothSocket = clickDevice.createRfcommSocketToServiceRecord(UUID.fromString(BLUETOOTH_UUID));
bluetoothSocket.connect();
textView_receive.setText("连接成功");
BlueTooth_Connect_State=1;
// showToast("连接成功");
readThread mreadThread = new readThread();
mreadThread.start();
//下面提示框没有回调效果显示,不知道为什么
// Looper.prepare();
// Toast.makeText(MainActivity.this, "连接成功", Toast.LENGTH_SHORT);
// Looper.loop();
//错误原因是自己想在网络请求成功后,弹出一个Toast提醒,但由于程序在主线程中创建handler后会创建一个looper对象,而子线程却不会,那什么时候需要looper?
//
// Looper用于封装了android线程中的消息循环,默认情况下一个线程是不存在消息循环(message loop)的,需要调用Looper.prepare()来给线程创建一个消息循环,调用Looper.loop()来使消息循环起作用,使用Looper.prepare()和Looper.loop()创建了消息队列就可以让消息处理在该线程中完成。
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
});
}
/**
* 读取数据
*/
private class readThread extends Thread {
public void run() {
byte[] buffer = new byte[1024];
int bytes; //字符串长度
int ch; //读取字符变量
try {
inputStream = bluetoothSocket.getInputStream();
textView_receive.setText("客户端:获得输入流");
} catch (IOException e1) {
e1.printStackTrace();
}
while(true) {
try {
bytes=0;
while ((ch = inputStream.read()) != '\n' ){ //这里读取的是字符串的结尾\r\n
if(ch!=-1) {
buffer[bytes] = (byte) ch; //将读取的字符写入缓存中
bytes++; //接收下一个字符
}
}
buffer[bytes+1] = (byte) '\n';
Receive_Data = new String(buffer);
textView_receive.setText("客户端读取数: "+Receive_Data);
} catch (IOException e) {
try {
inputStream.close();
} catch (IOException e1) {
e1.printStackTrace();
}
break;
}
}
/* 下面这个接收代码使用它自带API直接接受整个字符串,它无法判断哪个进行结尾,导致字符接收乱截取 */
// while (true) {
// try {
// if ((bytes = inputStream.read(buffer)) > 0) {
// byte[] buf_data = new byte[bytes];
// for (int i = 0; i < bytes; i++) {
// buf_data[i] = buffer[i];
// }
// Receive_Data = new String(buf_data);
// textView_receive.setText("客户端读取数: "+Receive_Data);
// }
// } catch (IOException e) {
// try {
// inputStream.close();
// } catch (IOException e1) {
// e1.printStackTrace();
// }
// break;
// }
// }
}
}
//在完成BluetoothSocket的处理后,始终记得调用close()方法来进行清理
//数据传输
///
// 强制打开蓝牙
// mBluetoothAdapter.enable();
//
// 客户端线程
//显示函数
public void showToast(String text) {
//Toast显示的时间有限,过一定的时间就会自动消失
//因此这里要判断是否消失了
//Toast.LENGTH_SHORT这个是设置显示两秒,LONG是3秒
if( mToast == null ) { //检查mToast文本空了没
//下面这行程序是一般是只执行一次的,就是刚开始空文本时初始化一次里面显示设置
mToast = Toast.makeText(this,text,Toast.LENGTH_SHORT);
}
else {
mToast.setText(text);//这里是倒数两秒,也就是浮框消失后重新设置文本内容
//达到事件不同回显文本不同的切换效果
}
mToast.show();
}
}
//强制转化字符串三种方法
/*
int a = 123;
1、 String s1 = String.valueOf(a);
2、 String s2 = Iteger.toString(a);
3、 String s3 = "" + a;
*/
显示单选列表项的对话框
//AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);//创建AlertDialog.Builder对象
// builder.setIcon(R.drawable.discovery);
// builder.setTitle("搜索设备");
// //第一个是参数为列表数组,第二个为从0开始选择,第三个单击内部监听器 对话框单选->添加项
// builder.setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() {
//@Override
//public void onClick(DialogInterface dialog, int which) {
// Toast.makeText(MainActivity.this,"---"+items[which]+"---",Toast.LENGTH_SHORT).show();
// }
// });
// //这里的按钮名字设置为确定,后面那个单击内部监听器没有设置
// //设置确定按钮
// builder.setNegativeButton("扫描", null);
// builder.setPositiveButton("连接",null);
// builder.create().show();
下图为包含图标、背景颜色、图片等相关程序及程序等的文件,这些在我工程上都是有使用到的,大家第一次写可以先把这些添加上去,熟练后,后面自己再根据需求自行修改。
下载到手机的现象如下:
**下载工程文件网址为:
安卓蓝牙APP工程