Android蓝牙ble扫描

酒已经准备好了,各位看客,请准备好小板凳。


理论性的东西就不写太多了,推荐一个挺好的博文

http://www.jianshu.com/p/de82f411a7fc

 官方文档的翻译(简书):

http://www.jianshu.com/p/bc408af3dd92

复制一些重要的理论过来,也方便你们查看:


扫盲

  1. 蓝牙有传统蓝牙(3.0以下)和低功耗蓝牙(ble,又称蓝牙4.0)之分
  2. android手机必须系统版本4.3及以上才支持BLE API。低功耗蓝牙较传统蓝牙, 传输速度更快,覆盖范围更广,安全性更高,延迟更短,耗电极低等等优点,这也是为什么近年来智能穿戴的东西越来越多,越来越火
  3. 传统蓝牙与低功耗蓝牙通信方式也有所不同,传统的一般通过socket方式,而低功耗蓝牙是通过Gatt协议来实现
  4. 低功耗蓝牙也叫BLEAndroid蓝牙ble扫描

二、解释:

  1. BLE分为ServiceCharacteristicDescriptor三部分,每个部分都拥有不同的 UUID来标识

2一个BLE设备可以拥有多个Service,一个Service可以包含多个Characteristic, 一个Characteristic包含一个Value和多个Descriptor,一个Descriptor包含一个Value

3通信数据一般存储在Characteristic内,目前一个Characteristic中存储的数据最大为20 byte

4与Characteristic相关的权限字段主要有READ、WRITE、WRITE_NO_RESPONSE、NOTIFY。 Characteristic具有的权限属性可以有一个或者多个

非常感谢作者对知识的分享


这篇只会介绍Android蓝牙4.0Ble的扫描,大大们,请。。。。。



效果图先奉上,直接拍个照
Android蓝牙ble扫描



酒喝完了,上菜
在6.0版本前,使用蓝牙功能,只需要配置下面的权限即可:

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

6.0以后

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

添加动态权限

下面是ActivityMain的布局
<?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"
    >


    <Button
        android:id="@+id/startScale"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="startScale" />

    <Button
        android:id="@+id/stopScale"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="stopScale" />

    <ProgressBar
        android:id="@+id/progressbar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="stopScale"/>

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

</LinearLayout>


MainActivity的代码
package com.ityingli.www.bluetoothtest2;

import android.Manifest;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static final int REQUEST_CODE_LOCATION_SETTINGS = 2; //用于Gps打开

    private BluetoothManager bluetoothManager;        //蓝牙管理器
    private BluetoothAdapter bluetoothAdapter;       //蓝牙适配器
    private Boolean scaleIng ;                       //是否正在扫描
    private static  final int REQUEST_ENABLE_BLE = 1;    //蓝牙请求
    private static final long SCALE_PERIOD= 10*1000;     //扫描时长   10    //布局中的控件
    private Button startScaleButton,stopScaleButton;       //开始扫描按钮,停止扫描按钮
    private ListView listview;                             //展示扫描到的结果Listview;
    private List<BlueTooth_item__Bean>   BlueToothDevice_Info;          //蓝牙设备的信息
    private ListViewAdapter adapter;
    private ProgressBar progressbar;
    private Handler handler;

    private static final int REQUEST_CODE_ACCESS_COARSE_LOCATION = 1;   //动态申请权限

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();                                                  //初始化控件
        initEvent();                                                //初始化事件
        initData();                                                  //初始化数据
        adapter = new ListViewAdapter(MainActivity.this,BlueToothDevice_Info);       //listView的适配器
        listview.setAdapter(adapter);


        //检测当前设备是否支持蓝牙ble
        if(!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)){
            Toast.makeText(MainActivity.this, "当前蓝牙不支持蓝牙设备", Toast.LENGTH_SHORT).show();
            finish();
        }


        //通过蓝牙管理器得到一个蓝牙适配器
        bluetoothManager  = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        bluetoothAdapter = bluetoothManager.getAdapter();
        if(bluetoothAdapter==null){
            Toast.makeText(MainActivity.this,"该设备不支持蓝牙",Toast.LENGTH_SHORT).show();
        }


        //android6.0之后要求有定位权限才可以扫描到蓝牙
        //动态申请权限
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){   //如果api版本大于23Android6.0的时候
            //判断是否具有权限
            if(ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)!=PackageManager.PERMISSION_GRANTED){
                //判断是否需要向用户解释为什么需要申请权限
                if(ActivityCompat.shouldShowRequestPermissionRationale(this,Manifest.permission.ACCESS_COARSE_LOCATION)){
                    Toast.makeText(MainActivity.this,"需要获取定位才可以使用BLE扫描",Toast.LENGTH_SHORT).show();
                }
                //请求权限
                ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},REQUEST_CODE_ACCESS_COARSE_LOCATION);//请求码
            }
        }
    }

    // //执行完上面的请求权限后,系统会弹出提示框让用户选择是否允许改权限。选择的结果可以在回到接口中得知:


    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if(requestCode==REQUEST_CODE_ACCESS_COARSE_LOCATION){
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                //用户允许改权限,0表示允许,-1表示拒绝 PERMISSION_GRANTED = 0 PERMISSION_DENIED = -1
                //permission was granted, yay! Do the contacts-related task you need to do.
                //这里进行授权被允许的处理
            } else {
                //permission denied, boo! Disable the functionality that depends on this permission.
                //这里进行权限被拒绝的处理
            }
        } else {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }


    //以上就是如何在应用运行的过程中申请位置权限。做完上面你以为就 OK 了,但是可能你会发现,并没有什么卵用,依然不能搜索到周围的 Ble 设备,原因是可能的你的位置服务(定位 GPS)没有打开。
    //检测定位是否打开
    public static final boolean isLocationEnable(Context context) {
        LocationManager locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        boolean networkProvider = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
        boolean gpsProvider = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
        if (networkProvider || gpsProvider) return true;
        return false;
    }

    //如果没有就打开,进入定位设置界面,让用户自己选择是否打开定位。选择的结果获取:
    private void setLocationService() {
        Intent locationIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
        this.startActivityForResult(locationIntent, REQUEST_CODE_LOCATION_SETTINGS);
    }


    @Override
    protected void onResume() {
        super.onResume();
        //确保蓝牙可以使用,如果不可以使用一个弹窗
        if(!bluetoothAdapter.enable()){
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent,REQUEST_ENABLE_BLE);
        }
    }


    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        //不同打开蓝牙
        if(requestCode ==REQUEST_ENABLE_BLE && resultCode == RESULT_CANCELED){
            finish();
            return;
        }
        //定位
        if (requestCode == REQUEST_CODE_LOCATION_SETTINGS) {
            if (isLocationEnable(this)) {
                //定位已打开的处理
            } else {
                //定位依然没有打开的处理
                Toast.makeText(MainActivity.this,"请打开GPS",Toast.LENGTH_SHORT).show();
            }
        } else super.onActivityResult(requestCode, resultCode, data);
        super.onActivityResult(requestCode, resultCode, data);
    }

    private void initData() {
        //用于定时取消扫描
        handler = new Handler();
        //模拟蓝牙设备的信息
        BlueToothDevice_Info = new ArrayList<>();
        for(int i  = 0  ;i<10;i++){
            BlueTooth_item__Bean bluetooth_device_item_info = new BlueTooth_item__Bean();
            bluetooth_device_item_info.blueToothDevie_Name = "蓝牙设备名字"+i;
            bluetooth_device_item_info.blueToothDevie_Adress = "蓝牙设备mac地址"+i;
            BlueToothDevice_Info.add(bluetooth_device_item_info);
        }
    }

    private void initEvent() {
        startScaleButton.setOnClickListener(this);                    //开始扫描事件
        stopScaleButton.setOnClickListener(this);                    //停止扫描
    }

    private void initView() {
        startScaleButton = (Button)findViewById(R.id.startScale);               //初始化控件。开始扫描按钮
        stopScaleButton= (Button)findViewById(R.id.stopScale);                  //初始化控件,停止扫描按钮
         listview = (ListView) findViewById(R.id.listview);                    //用于展示扫描到的设备信息,Listview
        progressbar = (ProgressBar) findViewById(R.id.progressbar);
        progressbar.setVisibility(View.GONE);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.startScale:                                             //扫描操作     (扫描一定的事件就自动关闭扫描)
            BlueToothDevice_Info.clear();
            progressbar.setVisibility(View.VISIBLE);
            bluetoothAdapter.startLeScan(scaleCallback);
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    bluetoothAdapter.stopLeScan(scaleCallback);
                    progressbar.setVisibility(View.GONE);
                }
            },10000);
            break;
        case R.id.stopScale:                                                  //停止扫描
            progressbar.setVisibility(View.GONE);
            bluetoothAdapter.stopLeScan(scaleCallback);
            break;
        }
    }



    //扫描到设备之后的回调方法
    private BluetoothAdapter.LeScanCallback  scaleCallback = new BluetoothAdapter.LeScanCallback() {
        @Override
        public void onLeScan(final BluetoothDevice device, int rssi, byte[] scanRecord) {
            if(device.getName()!=null && device.getAddress()!=null) {
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        final BlueTooth_item__Bean bean = new BlueTooth_item__Bean();
                        bean.blueToothDevie_Name = device.getName();
                        bean.blueToothDevie_Adress = device.getAddress();
                        if(BlueToothDevice_Info.contains(bean)) {
                            //如果集合中已经包含相同的对象,则不添加进去
                        }else{
                            BlueToothDevice_Info.add(bean);
                            adapter.notifyDataSetChanged();
                        }
                    }
                });
            }
        }
    };

}







Listview的适配器

package com.ityingli.www.bluetoothtest2;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.List;

/**
 * Created by Administrator on 2017/7/29.
 */

public class ListViewAdapter extends BaseAdapter {

    private Context mcontext;                                    //上下文
    private List<BlueTooth_item__Bean>  listDatas;              //listview需要用到的信息包括蓝牙名字和蓝牙设备的mac地址;
    private LayoutInflater layoutInflater;

    ListViewAdapter(Context mcontext, List<BlueTooth_item__Bean> listDatas){
        this.mcontext = mcontext;
        this.listDatas = listDatas;
        layoutInflater =  LayoutInflater.from(mcontext);
    }
    @Override
    public int getCount() {
        return listDatas.size();
    }

    @Override
    public Object getItem(int position) {
        return listDatas.get(position);
    }

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

    @Override
    public View getView(int position, View view, ViewGroup parent) {
        ListviewHolder listviewHolder;


        if(view ==null){
            listviewHolder = new ListviewHolder();
            view = layoutInflater.inflate(R.layout.layout_listview_item,null);
            listviewHolder.device_Name = (TextView) view.findViewById(R.id.device_name);
            listviewHolder.device_Address= (TextView) view.findViewById(R.id.device_address);
            view.setTag(listviewHolder);
        }else{
            listviewHolder =  (ListviewHolder) view.getTag();
        }
        listviewHolder.device_Name.setText(listDatas.get(position).blueToothDevie_Name);
        listviewHolder.device_Address.setText(listDatas.get(position).blueToothDevie_Adress);
        return view;
    }



    static class  ListviewHolder{
        private TextView device_Name;
        private TextView device_Address;
    }
}


适配器中用的的布局(ListView的Item的布局)

<?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"
    >
    <TextView
        android:id="@+id/device_name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="蓝牙设备名字"
        android:layout_margin="2dp"
        />
    <TextView
        android:id="@+id/device_address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="蓝牙设备的名字"
        android:layout_margin="5dp"
        />
</LinearLayout>


一个实体类,主要是蓝牙设备的name和address;

package com.ityingli.www.bluetoothtest2;

/**
 * Created by Administrator on 2017/7/29.
 */

public class BlueTooth_item__Bean {           //该数据用于listviewitem
       String blueToothDevie_Name;
       String blueToothDevie_Adress;          //蓝牙设备的名字和mas地址

    //判断内容是否相等
    @Override
    public boolean equals(Object obj) {
        if(obj instanceof  BlueTooth_item__Bean){
             if(blueToothDevie_Adress.equals(((BlueTooth_item__Bean) obj).blueToothDevie_Adress) && blueToothDevie_Name.equals(((BlueTooth_item__Bean) obj).blueToothDevie_Name)){
                 return true;
             }
        }else{
            return false;
        }
        return super.equals(obj);
    }


    @Override
    public int hashCode() {
        return blueToothDevie_Adress.hashCode();
    }
}


注释也是挺详细的了,各位晚安,不早了    2017/7/30    0:53