本人整理的完整版蓝牙开发Demo,包含了运行时权限的判断,自定义的progressBar,listView去除重复的蓝牙设备对象。可以说,写的很全面了,需要使用的话可以直接copy。
先上效果图:
①扫描中
②扫描完成
扫描时间间隔大概为13s(不同机型会略有不同)
1,项目结构:
2,BlueTestActivity.java代码如下(注释写的很详细了,流程也比较清晰):
package com.test.myapplication;
import android.Manifest;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
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.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
public class BlueTestActivity extends Activity implements View.OnClickListener {
private static final String TAG = "BlueTestActivity";
private Context context = BlueTestActivity.this;
private Button bt_scan;
private ListView lv_list;
private TextView tv_state;
private ProgressBar pb_bt;
private final int OPEN_BLUETOOTH = 0x01;
private final int MY_PERMISSIONS_REQUEST_OPEN_BLUETOOTH = 0x02;
private final int MY_PERMISSIONS_REQUEST_OPEN_BLUETOOTH_ADMIN = 0x03;
private boolean startDiscovery;
private boolean isBtOpen = false;
private boolean open_bluetooth = true;
private boolean bluetooth_admin = true;
private boolean permission = true;
private ArrayList<BluetoothBean> btList = new ArrayList<>();
private BtAdapter mBtAdapter;
private BluetoothAdapter mBluetoothAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_btlist);
initView();
initData();
//运行时权限
checkRuntimePermission();
}
private void initView() {
bt_scan = (Button) findViewById(R.id.bt_scan);
tv_state = (TextView) findViewById(R.id.tv_state);
lv_list = (ListView) findViewById(R.id.lv_list);
pb_bt = (ProgressBar) findViewById(R.id.pb_bt);
lv_list.setVisibility(View.VISIBLE);
pb_bt.setVisibility(View.VISIBLE);
bt_scan.setVisibility(View.GONE);
bt_scan.setOnClickListener(this);
}
private void initData() {
mBtAdapter = new BtAdapter();
lv_list.setAdapter(mBtAdapter);
//注册receiver來获取扫描到的蓝牙设备
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
// 注册搜索完时的receiver
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter);
}
/**
* 运行时权限
*/
private void checkRuntimePermission() {
//android版本6.0以上
if (Build.VERSION.SDK_INT > 22) {
Log.e(TAG, "6.0以上版本");
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.BLUETOOTH)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.BLUETOOTH},
MY_PERMISSIONS_REQUEST_OPEN_BLUETOOTH);
}
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.BLUETOOTH_ADMIN)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.BLUETOOTH_ADMIN},
MY_PERMISSIONS_REQUEST_OPEN_BLUETOOTH_ADMIN);
}
//两者都通过,再打开蓝牙
if (permission) {
openBluetooth();
}
} else {
Log.e(TAG, "6.0以下版本");
//android版本6.0以下,无需询问用户
openBluetooth();
}
}
/**
* 开启蓝牙
*/
private void openBluetooth() {
if (checkBtIsValueble()) {
//尝试自动开启
openBtAuto();
//检查蓝牙是否打开
if (mBluetoothAdapter.isEnabled()) {
//蓝牙已经自动开启
tv_state.setText("蓝牙状态:开启");
Toast.makeText(context, "蓝牙自动开启成功~!", Toast.LENGTH_SHORT).show();
//扫描蓝牙设备
scanBtDevices();
} else {
//蓝牙未自动开启
openBtHand();
}
} else {
//无蓝牙设备,告知用户
notice();
}
}
/**
* 自动开启蓝牙设备
*/
private void openBtAuto() {
if (!mBluetoothAdapter.isEnabled()) {
mBluetoothAdapter.enable();
}
}
/**
* 手动开启蓝牙
*/
private void openBtHand() {
lv_list.setVisibility(View.VISIBLE);
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, OPEN_BLUETOOTH);
}
/**
* 提示用户无蓝牙设备
*/
private void notice() {
tv_state.setText("无蓝牙设备");
Toast.makeText(context, "未发现蓝牙设备,请检查重试", Toast.LENGTH_LONG).show();
}
/**
* 参数 无
* 返回值 true 表示可以用嘛,否则不可以
* 异常 无
* 描述:这个方法用于检查蓝牙是否可用
*/
private boolean checkBtIsValueble() {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
//蓝牙可用
return false;
} else {
//蓝牙不可用
return true;
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OPEN_BLUETOOTH) {
if (resultCode == Activity.RESULT_OK) {
//蓝牙开启成功
isBtOpen = true;
tv_state.setText("蓝牙状态:开启");
Toast.makeText(context, "蓝牙手动开启成功~!", Toast.LENGTH_SHORT).show();
} else {
//蓝牙开启失败
isBtOpen = false;
tv_state.setText("蓝牙状态:关闭");
Toast.makeText(context, "蓝牙手动开启失败~!", Toast.LENGTH_SHORT).show();
}
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.bt_scan: //扫描列表
btList.clear();
//隐藏按钮,防止用户不停点击
bt_scan.setVisibility(View.GONE);
scanBtDevices();
break;
}
}
/**
* 扫描蓝牙设备
*/
private void scanBtDevices() {
pb_bt.setVisibility(View.VISIBLE);
lv_list.setVisibility(View.VISIBLE);
// 判断是否在搜索,如果在搜索,就取消搜索
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
// 开始搜索
startDiscovery = mBluetoothAdapter.startDiscovery();
}
/**
* 提供了手动关闭蓝牙的方式
*/
private void closeBtHand() {
if (isBtOpen) {
mBluetoothAdapter.disable();
}
btList.clear();
Log.e(TAG, "clear");
//关闭列表
lv_list.setVisibility(View.GONE);
}
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
Log.e(TAG, "扫描到了设备。。。");
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
//获取蓝牙信号强度
short signal = intent.getExtras().getShort(BluetoothDevice.EXTRA_RSSI);
Log.e(TAG, "扫描到了 --- > name : " + device.getName() + "-----> address:"
+ device.getAddress() + startDiscovery + ",signal:" + signal);
boolean hasDevice = false;
for (BluetoothBean bean : btList) {
if (bean.getAddress().equals(device.getAddress())) {
hasDevice = true;
break;
}
}
if (!hasDevice) {
BluetoothBean bean = new BluetoothBean();
bean.setName(device.getName());
bean.setAddress(device.getAddress());
btList.add(bean);
mBtAdapter.notifyDataSetChanged();
}
} else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) {
//扫描完成
Log.e(TAG, "蓝牙扫描完成");
pb_bt.setVisibility(View.GONE);
bt_scan.setVisibility(View.VISIBLE);
Toast.makeText(context, "扫描完成", Toast.LENGTH_SHORT).show();
}
}
};
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == MY_PERMISSIONS_REQUEST_OPEN_BLUETOOTH) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
} else {
// Permission Denied
open_bluetooth = false;
Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT).show();
}
return;
}
if (requestCode == MY_PERMISSIONS_REQUEST_OPEN_BLUETOOTH_ADMIN) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
} else {
// Permission Denied
bluetooth_admin = false;
Toast.makeText(context, "Permission Denied", Toast.LENGTH_SHORT).show();
}
return;
}
//蓝牙所需权限都通过了
if (open_bluetooth && bluetooth_admin) {
permission = true;
} else {
permission = false;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
/**
* 蓝牙列表适配器
*/
private class BtAdapter extends BaseAdapter {
@Override
public int getCount() {
Log.e(TAG, "btList.size():" + btList.size());
return btList.size();
}
@Override
public Object getItem(int position) {
return btList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//fbc
ViewHolder holder = null;
if (convertView == null) {
holder = new ViewHolder();
convertView = View.inflate(context, R.layout.listview_bt, null);
holder.name = (TextView) convertView.findViewById(R.id.tv_name);
holder.signal = (TextView) convertView.findViewById(R.id.tv_signal);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
//对列表上的控件赋值
Log.e(TAG, "1:" + btList.get(position).getName() + ";2:" + btList.get(position).getAddress());
holder.name.setText(btList.get(position).getName());
holder.signal.setText("mac:" + btList.get(position).getAddress());
return convertView;
}
}
/**
* 备忘录
*/
static class ViewHolder {
TextView name;
TextView signal;
}
@Override
protected void onDestroy() {
btList.clear();
// 判断是否在搜索,如果在搜索,就取消搜索
if (mBluetoothAdapter.isDiscovering()) {
mBluetoothAdapter.cancelDiscovery();
}
unregisterReceiver(mReceiver);
mBluetoothAdapter = null;
super.onDestroy();
}
}
上面开启蓝牙的方式分为了两种:自动开启和手动开启。因为部分android设备不支持自动开启,所以增加了手动开启的逻辑。另外需注意,扫描蓝牙设备的时候,同一个设备可能会被多次扫描到,所以在onReceive中,我们需要对接收到的每个设备信息进行判断,判断依为每个蓝牙设备的mac地址(mac地址唯一性),以此过滤掉重复的对象,再添加到list集合中。
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".BlueTestActivity">
<RelativeLayout
android:id="@+id/rl_state"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:text="蓝牙设备列表"
android:textColor="#000"
android:textSize="20dp" />F
<TextView
android:id="@+id/tv_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginTop="5dp"
android:text="扫描设备中..."
android:textColor="#000"
android:textSize="10dp" />
<View
android:id="@+id/v_line"
android:layout_width="wrap_content"
android:layout_height="1dp"
android:layout_alignBottom="@id/tv_list"
android:background="#000"
android:paddingTop="10dp" />
<ProgressBar
android:id="@+id/pb_bt"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_centerVertical="true"
android:layout_marginLeft="16dp"
android:layout_marginStart="5dp"
android:layout_toEndOf="@+id/tv_list"
android:layout_toRightOf="@+id/tv_list"
android:indeterminateDrawable="@drawable/progressbar_btlist" />
</RelativeLayout>
<ListView
android:id="@+id/lv_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"></ListView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="horizontal">
<Button
android:id="@+id/bt_scan"
android:layout_width="120dp"
android:layout_height="60dp"
android:text="搜索设备"
android:textSize="15dp" />
</LinearLayout>
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dp">
<TextView
android:id="@+id/tv_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_marginLeft="5dp"
android:textSize="15dp"
android:text="设备名称" />
<TextView
android:id="@+id/tv_signal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_marginRight="5dp"
android:textSize="15dp"
android:text="设备信号" />
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360">
>
<shape
android:innerRadiusRatio="3"
android:shape="ring"
android:thicknessRatio="10"
android:useLevel="false">
<gradient
android:centerColor="#4000"
android:centerY="0.50"
android:endColor="#000"
android:startColor="#fff"
android:type="sweep"
android:useLevel="false" />
</shape>
</animated-rotate>
注意该样式的添加方式:
android:indeterminateDrawable="@drawable/progressbar_btlist"