功能:搜索附近的蓝牙设备(仅限于经典蓝牙)并显示、对指定的蓝牙设备发送配对请求、获取已配对的蓝牙设备名称、对指定的蓝牙设备取消配对。
android 蓝牙app(搜索、配对) - Chi4ki - 博客园 (cnblogs.com)
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<MyAdapter.MyViewHoder> { 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<Bluetooth> mNewsList ; public MyAdapter(List<Bluetooth> 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<Bluetooth> myNewsList = new ArrayList<>(); IntentFilter filter_found; BlueToothFoundReceiver blueToothFoundReceiver; List<String> list = new ArrayList<String>(); Set<BluetoothDevice> 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; } } } } }
每一部分的实现参考上一篇博客:android studio蓝牙学习记录 - Chi4ki - 博客园 (cnblogs.com)
效果: