android 蓝牙app(搜索、配对)
功能:搜索附近的蓝牙设备(仅限于经典蓝牙)并显示、对指定的蓝牙设备发送配对请求、获取已配对的蓝牙设备名称、对指定的蓝牙设备取消配对。
AndroidManifests.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.recyclerviewtest"> <uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.BLUETOOTH_SCAN" /> <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" /> <uses-permission android:name="android.permission.LOCAL_MAC_ADDRESS" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.RecyclerviewTest"> <activity android:name=".MainActivity" android:exported="true"> <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"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:context=".MainActivity"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recyclerview" android:layout_width="413dp" android:layout_height="370dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/edx2" app:layout_constraintVertical_bias="0.0" /> <Button android:id="@+id/btn1" android:layout_width="150dp" android:layout_height="60dp" android:layout_marginStart="16dp" android:layout_above="@id/btn3" android:layout_marginBottom="16dp" android:text="搜索" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/recyclerview" app:layout_constraintVertical_bias="0.0" /> <Button android:id="@+id/btn2" android:layout_width="150dp" android:layout_height="60dp" android:layout_above="@id/btn4" android:layout_marginEnd="16dp" android:text="停止" app:layout_constraintBottom_toTopOf="@+id/btn4" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@+id/recyclerview" app:layout_constraintVertical_bias="0.0" /> <Button android:id="@+id/btn3" android:layout_width="150dp" android:layout_height="60dp" android:layout_above="@id/btn5" android:text="配对" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.061" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/recyclerview" app:layout_constraintVertical_bias="0.358" /> <Button android:id="@+id/btn4" android:layout_width="150dp" android:layout_height="60dp" android:layout_above="@id/btn5" android:layout_marginStart="79dp" android:layout_marginEnd="16dp" android:text="取消配对" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toEndOf="@+id/btn3" app:layout_constraintTop_toBottomOf="@+id/recyclerview" app:layout_constraintVertical_bias="0.358" /> <EditText android:id="@+id/edx1" android:layout_width="0dp" android:layout_height="50dp" android:ems="10" android:inputType="textPersonName" android:text="Name" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <EditText android:id="@+id/edx2" android:layout_width="match_parent" android:layout_height="50dp" android:ems="10" android:inputType="textPostalAddress" android:text="Address" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/edx1" /> <Button android:id="@+id/btn5" android:layout_width="380dp" android:layout_height="60dp" android:text="获取匹配列表" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.516" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/recyclerview" app:layout_constraintVertical_bias="0.701" /> androidx.constraintlayout.widget.ConstraintLayout>
item_list.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="wrap_content" android:background="@color/white" android:foreground="?attr/selectableItemBackground" android:orientation="vertical"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_vertical" android:padding="16dp"> <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" /> <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:orientation="vertical"> <TextView android:id="@+id/tv_device_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:ellipsize="end" android:singleLine="true" android:text="设备名称" android:textColor="@color/black" android:textSize="16sp" /> <TextView android:id="@+id/tv_mac_address" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:ellipsize="end" android:singleLine="true" android:text="Mac地址" /> LinearLayout> LinearLayout> <View android:layout_width="match_parent" android:layout_height="0.5dp" android:background="#EEE" /> LinearLayout>
Bluetooth.java
package com.example.recyclerviewtest; public class Bluetooth { public String name; public String address; public Bluetooth(String n,String a){ this.name=n; this.address=a; } public void setBluetooth(String n,String a){ this.name=n; this.address=a; } public void setName(String n){this.name=n;} public void setAddress(String a){this.address=a;} public String getName(){return(this.name);} public String getAddress(String a){return(this.address);} public String print(){ return this.name+"\n"+this.address; } }
MyAdapter.java
package com.example.recyclerviewtest; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.recyclerview.widget.RecyclerView; import java.security.AccessController; import java.util.ArrayList; import java.util.List; class MyAdapter extends RecyclerView.Adapter{ private MainActivity.OnItemClickListener onItemClickListener; static class MyViewHoder extends RecyclerView.ViewHolder { View item; TextView mTitleTv; TextView mTitleContent; public MyViewHoder(@NonNull View itemView) { super(itemView); item = itemView; mTitleTv = itemView.findViewById(R.id.tv_device_name); mTitleContent = itemView.findViewById(R.id.tv_mac_address); } } private final List mNewsList ; public MyAdapter(List data) { this.mNewsList = data; } @NonNull @Override public MyViewHoder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = View.inflate(parent.getContext(), R.layout.item_list, null); return new MyViewHoder(view); } @Override public void onBindViewHolder(@NonNull MyViewHoder holder, int position) { final Bluetooth c = mNewsList.get(position); Bluetooth news = mNewsList.get(position); holder.mTitleTv.setText(news.name); holder.mTitleContent.setText(news.address); holder.item.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (onItemClickListener != null) { onItemClickListener.onItemClick(c); } } }); } @Override public int getItemCount() { return mNewsList.size(); } public void setOnItemClickListener(MainActivity.OnItemClickListener onItemClickListener) { this.onItemClickListener = onItemClickListener; } }
PermissionsActivity.java
package com.example.recyclerviewtest; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; public abstract class PermissionsActivity extends AppCompatActivity { private static final int PERMISSION_REQUEST_CODE = 200; private static final String PACKAGE_URL_SCHEME = "package:"; private PermissionsChecker mPermissionsChecker; private boolean isRequireCheck; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mPermissionsChecker = new PermissionsChecker(this); } protected void requestPermission(){ isRequireCheck = true; if (isRequireCheck) { String[] permissions = getPermission(); if (mPermissionsChecker.lacksPermissions(permissions)) { requestPermissions(permissions); } else { onPermissionRequestSuccess(); } } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE && hasAllPermissionsGranted(grantResults)) { isRequireCheck = true; onPermissionRequestSuccess(); } else { isRequireCheck = false; onPermissionRequestFail(); } } private void requestPermissions(String... permissions) { ActivityCompat.requestPermissions(this, permissions, PERMISSION_REQUEST_CODE); } private boolean hasAllPermissionsGranted(@NonNull int[] grantResults) { for (int grantResult : grantResults) { if (grantResult == PackageManager.PERMISSION_DENIED) { return false; } } return true; } protected void showMissingPermissionDialog(String tip) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle("提示"); builder.setMessage(tip); builder.setCancelable(false); builder.setNegativeButton("退出", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { finish(); } }); builder.setPositiveButton("去开启", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { startAppSettings(); finish(); } }); builder.show(); } public void startAppSettings() { Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.parse(PACKAGE_URL_SCHEME + getPackageName())); startActivity(intent); } public abstract String[] getPermission(); public abstract void onPermissionRequestSuccess(); public abstract void onPermissionRequestFail(); }
PermissionsChecker.java
package com.example.recyclerviewtest; import android.content.Context; import android.content.pm.PackageManager; import androidx.core.content.ContextCompat; public class PermissionsChecker { private final Context mContext; public PermissionsChecker(Context context) { mContext = context.getApplicationContext(); } public boolean lacksPermissions(String... permissions) { for (String permission : permissions) { if (lacksPermission(permission)) { return true; } } return false; } private boolean lacksPermission(String permission) { return ContextCompat.checkSelfPermission(mContext, permission) == PackageManager.PERMISSION_DENIED; } }
MainActivity.java
package com.example.recyclerviewtest;
import androidx.core.app.ActivityCompat;
import androidx.recyclerview.widget.DefaultItemAnimator;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.Manifest;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class MainActivity extends PermissionsActivity {
String name, address,blu;
Integer id;
Button btn1,btn2,btn3,btn4,btn5;
BluetoothAdapter mBluetoothAdapter;
BluetoothManager bluetoothManager;
RecyclerView mRecyclerView;
MyAdapter mMyAdapter ;
List myNewsList = new ArrayList<>();
IntentFilter filter_found;
BlueToothFoundReceiver blueToothFoundReceiver;
List list = new ArrayList();
Set devices;
BluetoothDevice mSelectedDevice;
int count;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.recyclerview);
blueToothFoundReceiver= new BlueToothFoundReceiver();
filter_found= new IntentFilter(BluetoothDevice.ACTION_FOUND);
filter_found.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
filter_found.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(blueToothFoundReceiver, filter_found);
mMyAdapter = new MyAdapter(myNewsList);
mRecyclerView.setAdapter(mMyAdapter);
LinearLayoutManager layoutManager = new LinearLayoutManager(MainActivity.this);
mRecyclerView.setLayoutManager(layoutManager);
btn1=findViewById(R.id.btn1);
btn1.setOnClickListener(new BtnClickListener());
btn2=findViewById(R.id.btn2);
btn2.setOnClickListener(new BtnClickListener());
btn3=findViewById(R.id.btn3);
btn3.setOnClickListener(new BtnClickListener());
btn4=findViewById(R.id.btn4);
btn4.setOnClickListener(new BtnClickListener());
btn5=findViewById(R.id.btn5);
btn5.setOnClickListener(new BtnClickListener());
mMyAdapter.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(Bluetooth c) {
EditText textName=findViewById(R.id.edx1);
EditText textAddress=findViewById(R.id.edx2);
textName.setText(c.name);
textAddress.setText(c.address);
Toast.makeText(getApplicationContext(), "Click " + c.print(), Toast.LENGTH_SHORT).show();
}
});
}
class BtnClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
id=v.getId();
count=0;
if(id.equals(R.id.btn1)){
runOnUiThread(()->myNewsList.clear());
mMyAdapter.notifyDataSetChanged();
checkBleDevice();
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
mBluetoothAdapter.startDiscovery();
}
if(id.equals(R.id.btn2)){
checkBleDevice();
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
}
if(id.equals(R.id.btn3)){
count=1;
checkBleDevice();
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
mBluetoothAdapter.startDiscovery();
}
if(id.equals(R.id.btn4)){
checkBleDevice();
blueToothConnectGet();
getTargetBLEDevice();
}
if(id.equals(R.id.btn5)){
checkBleDevice();
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
runOnUiThread(()->myNewsList.clear());
mMyAdapter.notifyDataSetChanged();
blueToothConnectGet();
for (BluetoothDevice bluetoothDevice : devices) {
Bluetooth blue=new Bluetooth(bluetoothDevice.getName(),bluetoothDevice.getAddress());
myNewsList.add(blue);
mMyAdapter.notifyDataSetChanged();
}
}
}
}
@Override
public String[] getPermission() {
return new String[]{Manifest.permission.ACCESS_FINE_LOCATION,Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.BLUETOOTH,Manifest.permission.BLUETOOTH_ADMIN};
}
@Override
public void onPermissionRequestSuccess() {
}
@Override
protected void onResume() {
super.onResume();
requestPermission();
}
@Override
public void onPermissionRequestFail() {
showMissingPermissionDialog("缺失权限!");
}
@SuppressLint("HardwareIds")
private void checkBleDevice() {
bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
if (bluetoothManager != null) {
mBluetoothAdapter = bluetoothManager.getAdapter();
if (mBluetoothAdapter != null) {
if (!mBluetoothAdapter.isEnabled()) {
if (!mBluetoothAdapter.enable()) {
Log.i("tag", "蓝牙打开失败");
} else {
Log.i("tag", "蓝牙已打开");
}
}else {
Log.i("tag","同意申请");
}
}
}
}
public class BlueToothFoundReceiver extends BroadcastReceiver {
@SuppressLint("NotifyDataSetChanged")
@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);
if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.
permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 200);
if(count==1){
String a_device=device.getName()+device.getAddress();
if (a_device.equals(getDevice())){
createOrRemoveBond(1,device);
Log.d("tag", "匹配中");
}
} else{
Log.d("tag", "扫描到了:" + device.getName() + ":" + device.getAddress() + "\n");
Log.d("tag", String.valueOf(device));
name = device.getName();
address = device.getAddress();
blu = name + ":" + address;
for (String bluetooth : list) {
if (blu.equals(bluetooth)) {
return;
}
}
list.add(device.getName() + ":" + device.getAddress());
if (device.getName() == null) name = "Unknown";
myNewsList.add(new Bluetooth(name, address));
mMyAdapter.notifyDataSetChanged();
}
}
} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_STARTED)) {
Log.e("tag","搜索开启");
} else if (action.equals(BluetoothAdapter.ACTION_DISCOVERY_FINISHED)) {
Log.e("tag","搜索完成");
}
}
}
private void createOrRemoveBond(int type, BluetoothDevice device) {
Method method = null;
try {
switch (type) {
case 1:
method = BluetoothDevice.class.getMethod("createBond");
method.invoke(device);
break;
case 2:
method = BluetoothDevice.class.getMethod("removeBond");
method.invoke(device);
break;
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public interface OnItemClickListener {
void onItemClick(Bluetooth c);
}
private String getDevice() {
EditText textName=findViewById(R.id.edx1);
EditText textAddress=findViewById(R.id.edx2);
String a_name=textName.getText().toString().trim();
String a_address=textAddress.getText().toString().trim();
return a_name+a_address;
}
public void blueToothConnectGet() {
name = mBluetoothAdapter.getName();
address = mBluetoothAdapter.getAddress();
devices = mBluetoothAdapter.getBondedDevices();
for (BluetoothDevice bluetoothDevice : devices) {
Log.d("markTest", "配对列表:" + bluetoothDevice.getName());
}
}
private void getTargetBLEDevice() {
EditText textName=findViewById(R.id.edx1);
EditText textAddress=findViewById(R.id.edx2);
if (devices != null && devices.size() > 0) {
for (BluetoothDevice bluetoothDevice : devices) {
String a_name=textName.getText().toString().trim();
if (bluetoothDevice != null && bluetoothDevice.getName().equalsIgnoreCase(a_name)) {
Log.d("tag","取消匹配");
mSelectedDevice = bluetoothDevice;
createOrRemoveBond(2,mSelectedDevice);
break;
}
}
}
}
}
每一部分的实现参考上一篇博客:
效果: