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;
                }
            }
        }
    }
}
    
每一部分的实现参考上一篇博客:
效果: