安卓开发--BLE蓝牙设备扫描界面的实例

添加相关权限

AndroidManifest.xml

    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <!-- 对于 Android 6.0+ 需要位置权限来扫描蓝牙设备 -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <!-- 对于 Android 12+ 需要添加以下权限 -->
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation"/>
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>

MainActivity.java

package com.example.test;

import android.Manifest;
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.Build;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

    //请求码,可以随便设置,但是在软件中必须唯一
    private static final int REQUEST_ENABLE_BT = 1;
    private static final int PERMISSION_REQUEST_FINE_LOCATION = 101;
    private static final long SCAN_PERIOD = 10000; // 10 seconds

    private BluetoothAdapter mBluetoothAdapter;//蓝牙适配器
    private LeDeviceListAdapter mLeDeviceListAdapter;//列表适配器
    private ListView listView;
    private Button scanButton;
    private boolean mScanning;
    private Handler mHandler;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mHandler = new Handler();//初始化Handler

        // 检查设备是否支持蓝牙
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, "蓝牙低功耗不支持", Toast.LENGTH_SHORT).show();
            finish();
        }

        // 初始化蓝牙适配器(通过BluetoothManager去获取BluetoothAdapter的实例)
        final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothAdapter = bluetoothManager.getAdapter();

        // 检查设备是否支持蓝牙
        if (mBluetoothAdapter == null) {
            Toast.makeText(this, "蓝牙不可用", Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        // 初始化视图组件
        listView = findViewById(R.id.list_view);
        scanButton = findViewById(R.id.scan_button);

        scanButton.setOnClickListener(new View.OnClickListener() {//扫描按钮的点击事件
            @Override
            public void onClick(View v) {
                if (mScanning) {
                    stopScan();
                } else {
                    startScan();
                }
            }
        });

        // 初始化列表适配器
        mLeDeviceListAdapter = new LeDeviceListAdapter();
        listView.setAdapter(mLeDeviceListAdapter);//给列表设置适配器

        // 设置列表项点击事件
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                final BluetoothDevice device = mLeDeviceListAdapter.getDevice(position);
                if (device == null) return;

                // 这里可以实现连接蓝牙设备的逻辑
                if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                    // TODO: Consider calling
                    //    ActivityCompat#requestPermissions
                    // here to request the missing permissions, and then overriding
                    //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                    //                                          int[] grantResults)
                    // to handle the case where the user grants the permission. See the documentation
                    // for ActivityCompat#requestPermissions for more details.
                    return;
                }
                Toast.makeText(MainActivity.this,
                        "设备名称: " + device.getName() + "\n设备地址: " + device.getAddress(),
                        Toast.LENGTH_SHORT).show();
            }
        });

        // 注册蓝牙状态变化的广播接收器
        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
        registerReceiver(mReceiver, filter);
    }

    @Override
    protected void onResume() {
        super.onResume();

        // 确保蓝牙已开启
        if (!mBluetoothAdapter.isEnabled()) {
            //蓝牙不可用则请求用户开启蓝牙
            Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
        }

        // 检查定位权限(Android 6.0及以上需要)
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
                    != PackageManager.PERMISSION_GRANTED) {//检查
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
                        PERMISSION_REQUEST_FINE_LOCATION);//请求开启定位权限
            } else {
                startScan();
            }
        } else {
            startScan();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        stopScan();
        mLeDeviceListAdapter.clear();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        unregisterReceiver(mReceiver);//注销广播接收器
    }

    //开启定位的系统回调方法
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case PERMISSION_REQUEST_FINE_LOCATION:
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    startScan();
                } else {
                    Toast.makeText(this, "需要定位权限来扫描蓝牙设备", Toast.LENGTH_SHORT).show();
                }
                break;
        }
    }

    //跳转开启蓝牙界面的系统回调
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == REQUEST_ENABLE_BT && resultCode == RESULT_CANCELED) {
            finish();
            return;
        }
    }

    private void startScan() {
        mLeDeviceListAdapter.clear();
        mScanning = true;
        scanButton.setText("停止扫描");

        // 如果是Android 5.0及以上版本,使用新的蓝牙扫描API
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                return;
            }
            mBluetoothAdapter.getBluetoothLeScanner().startScan(mLeScanCallback);
        } else {
            // Android 5.0以下版本使用旧的蓝牙扫描API
            mBluetoothAdapter.startLeScan(mLeScanCallbackCompat);
        }

        // 停止扫描的计时器
        mHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                stopScan();
            }
        }, SCAN_PERIOD);
    }

    private void stopScan() {
        mScanning = false;
        scanButton.setText("开始扫描");

        // 如果是Android 5.0及以上版本,使用新的蓝牙扫描API
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                return;
            }
            mBluetoothAdapter.getBluetoothLeScanner().stopScan(mLeScanCallback);
        } else {
            // Android 5.0以下版本使用旧的蓝牙扫描API
            mBluetoothAdapter.stopLeScan(mLeScanCallbackCompat);
        }
    }

    // 蓝牙状态变化的广播接收器
    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();

            if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
                        BluetoothAdapter.ERROR);
                switch (state) {
                    case BluetoothAdapter.STATE_OFF:
                        break;
                    case BluetoothAdapter.STATE_TURNING_OFF:
                        break;
                    case BluetoothAdapter.STATE_ON:
                        break;
                    case BluetoothAdapter.STATE_TURNING_ON:
                        break;
                }
            }
        }
    };

    // 新的蓝牙扫描回调(Android 5.0及以上)
    private android.bluetooth.le.ScanCallback mLeScanCallback =
            new android.bluetooth.le.ScanCallback() {
                @Override
                public void onScanResult(int callbackType, android.bluetooth.le.ScanResult result) {
                    super.onScanResult(callbackType, result);
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mLeDeviceListAdapter.addDevice(result.getDevice(), result.getRssi());
                            mLeDeviceListAdapter.notifyDataSetChanged();
                        }
                    });
                }
            };

    // 旧的蓝牙扫描回调(Android 5.0以下)
    private BluetoothAdapter.LeScanCallback mLeScanCallbackCompat =
            new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(final BluetoothDevice device, final int rssi, byte[] scanRecord) {
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            mLeDeviceListAdapter.addDevice(device, rssi);
                            mLeDeviceListAdapter.notifyDataSetChanged();
                        }
                    });
                }
            };

    // 蓝牙设备列表适配器
    private class LeDeviceListAdapter extends BaseAdapter {
        private ArrayList<BluetoothDevice> mLeDevices;// 存储蓝牙设备对象
        //BluetoothDevice:Android 蓝牙设备类,包含设备名称、地址等信息。
        private ArrayList<Integer> mRssiValues; // 存储对应设备的信号强度(RSSI)
        //RSSI:接收信号强度指示(Received Signal Strength Indication),单位为 dBm,数值越小表示信号越弱。
        private LayoutInflater mInflator;// 用于加载布局文件

        //初始化
        public LeDeviceListAdapter() {
            super();
            mLeDevices = new ArrayList<>();
            mRssiValues = new ArrayList<>();
            mInflator = MainActivity.this.getLayoutInflater();
        }

        //添加设备
        public void addDevice(BluetoothDevice device, int rssi) {
            if (!mLeDevices.contains(device)) {//两个列表同步添加数据,保证索引一致
                mLeDevices.add(device);// 添加设备到列表末尾
                mRssiValues.add(rssi);// 将 RSSI 添加到相同索引位置
            } else {
                // 设备已存在则更新RSSI值
                //1. ArrayList.indexOf(Object o) 方法:这是 Java 集合框架中 ArrayList 类的一个方法,用于查找某个对象在列表中的位置。
                int index = mLeDevices.indexOf(device);
                mRssiValues.set(index, rssi);
            }
        }

        public BluetoothDevice getDevice(int position) {
            return mLeDevices.get(position);
        }

        public void clear() {
            mLeDevices.clear();
            mRssiValues.clear();
        }

        @Override
        public int getCount() {
            return mLeDevices.size();
        }

        @Override
        public Object getItem(int i) {
            return mLeDevices.get(i);
        }

        @Override
        public long getItemId(int i) {
            return i;
        }

        @Override
        public View getView(int i, View view, ViewGroup viewGroup) {
            ViewHolder viewHolder;
            // ViewHolder:通过 setTag() 和 getTag() 复用视图组件,避免重复调用 findViewById(),提高性能。
            // 一般的视图优化
            if (view == null) {
                // 1. 首次创建 View
                view = mInflator.inflate(R.layout.listitem_device, null);
                // 2. 创建 ViewHolder 并缓存控件引用
                viewHolder = new ViewHolder();
                viewHolder.deviceAddress = view.findViewById(R.id.device_address);
                viewHolder.deviceName = view.findViewById(R.id.device_name);
                viewHolder.deviceRssi = view.findViewById(R.id.device_rssi);
                // 3. 将 ViewHolder 绑定到 View
                view.setTag(viewHolder);
            } else {
                // 4. 复用 View 时,直接从 Tag 中获取 ViewHolder
                viewHolder = (ViewHolder) view.getTag();
            }

            BluetoothDevice device = mLeDevices.get(i);
            if (ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) {
                // TODO: Consider calling
                //    ActivityCompat#requestPermissions
                // here to request the missing permissions, and then overriding
                //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                //                                          int[] grantResults)
                // to handle the case where the user grants the permission. See the documentation
                // for ActivityCompat#requestPermissions for more details.

            }

            // 5. 使用缓存的控件引用,无需再次 findViewById()
            final String deviceName = device.getName();
            if (deviceName != null && deviceName.length() > 0) {
                viewHolder.deviceName.setText(deviceName);// 显示真实设备名称
            }else {
                viewHolder.deviceName.setText("未知设备");// 名称为空时显示默认文本
            }
            viewHolder.deviceAddress.setText(device.getAddress());

            // 显示信号强度
            int rssi = mRssiValues.get(i);
            viewHolder.deviceRssi.setText("信号强度: " + rssi + " dBm");

            return view;
        }
    }

    //创建一个静态内部类 ViewHolder,用于存储 View 中的所有控件引用。
    //当第一次创建 View 时,将控件引用存入 ViewHolder,并通过 setTag() 将 ViewHolder 绑定到 View 上。
    //当复用 View 时,通过 getTag() 直接获取已缓存的 ViewHolder,无需重新查找控件。
    static class ViewHolder {
        TextView deviceName;
        TextView deviceAddress;
        TextView deviceRssi;
    }
}

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"
    android:padding="16dp">

    <Button
        android:id="@+id/scan_button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="开始扫描"
        android:textSize="18sp"
        android:background="#ffff00"
        android:textColor="#FFFFFF"
        android:layout_marginBottom="16dp"/>

    <ListView
        android:id="@+id/list_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

listitem_device.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:orientation="vertical"
    android:padding="10dp">

    <TextView
        android:id="@+id/device_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="18sp"
        android:textStyle="bold"/>

    <TextView
        android:id="@+id/device_address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="14sp"
        android:textColor="#666666"/>

    <TextView
        android:id="@+id/device_rssi"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="14sp"
        android:textColor="#666666"
        android:layout_marginTop="4dp"/>
</LinearLayout>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值