github 源码: https://github.com/AnyLifeZLB/Bluetooth-AntiLost
Demo 地址:https://play.google.com/store/apps/details?id=com.bipbip.ble (翻墙下载)
/**
* 防丢核心算法,早期整理版本不完善,根据自身业务完善,
* @anylife.zlb@gmail.com
*/
public class BlePreventLostCore {
private static String TAG=BlePreventLostCore.class.getSimpleName();
private final static int leastNum=3; // 设备采集到的SSID[] size>3才是有意义的值
private final static double deviateBaseFlag=0.1; //
private final static double WCF=0.5; //Weight Compensation Flag,权重补偿因子
private static Map<String,List<Integer>> sacnedBleDevicesData;
/**
* 去除采集到数据中的脏值
* deviateBaseFlag 为基准的脏值偏移标志,如果长度比较大的话,可以适当的加大
* 先简单的以算术平均数作为参照,大于average 的deviateBaseFlag*average 就判断是脏值。
*
*
*/
public static void clearDigest() {
// double deviateFlag=0.2**……&&N; //
Map<String,Double> sAverageSSID=new HashMap<>(); //简单算术平均数
Set<String> ks =sacnedBleDevicesData.keySet();
//求对应手表的算术平均数。
for(String key : ks ){
double average=0.0;
List<Integer> ssidList=sacnedBleDevicesData.get(key);
int size=sacnedBleDevicesData.get(key).size();
for(int i=0;i<size;i++){
average=average+ssidList.get(i);
}
average=(double)average/size;
sAverageSSID.put(key, average);
}
//去除脏值。
for(String key : ks ){
// double average=0.0;
List<Integer> ssidList=sacnedBleDevicesData.get(key);
int size=sacnedBleDevicesData.get(key).size();
if(size>leastNum){
double averageTemp=sAverageSSID.get(key);
while(--size!=-1){
Log.e(TAG,key+"i: "+size);
if(Math.abs(ssidList.get(size)-averageTemp) > Math.abs(deviateBaseFlag*averageTemp)){
Log.d(TAG,key+"移除 脏值索引 i: "+size);
ssidList.remove(size);
}
}
sacnedBleDevicesData.put(key, ssidList);
}
}
}
/**
* ssid 对用的权重分布[... , ...]
*
* @return
*/
public static Map<String,List<Double>> getWeghtCompensationMap(){
Map<String,List<Double>> weghtCompensationDatas = new HashMap<String,List<Double>>(); //权数分布
// Map<String,Double> weghtCompensationDecreasing; //权重递减因子
//1.初始化权数分布
Set<String> ks =sacnedBleDevicesData.keySet();
for(String key : ks ){
final int size=sacnedBleDevicesData.get(key).size();
List<Double> weghtCompDataList=new ArrayList<Double>(size-1);
if(size>leastNum){
//1.权重分布的前半部分初始化
for(int i=0;i<size;i++){
double decreasing=WCF-WCF*2*(i+1)/size;
if(decreasing>0&&i<size/2){
weghtCompDataList.add(i, decreasing);
}else{
weghtCompDataList.add(i, (double) 0);
}
}
//2.权重分布的后半部分初始化
for(int j=size-1;j>size/2;j--){
double temp=weghtCompDataList.get(size-j-1);
weghtCompDataList.set(j,temp);
}
//3.权重分布的 实际处理
for(int i=0;i<size/2;i++){
weghtCompDataList.set(i, (1-weghtCompDataList.get(i))/size);
}
for(int j=size-1;j>size/2;j--){
weghtCompDataList.set(j, (1+weghtCompDataList.get(j))/size);
}
//4.权重分布的 中位数实际处理
if(size%2==0){
weghtCompDataList.set(size/2,1.0/size);
}else{
weghtCompDataList.set(size/2, 1.0/size);
weghtCompDataList.set(size/2-1, 1.0/size);
}
//5.权重分布测试,相加应该无限接近 100/100=1
double test=0;
for(int s=0;s<size;s++){
test=test+weghtCompDataList.get(s);
}
Log.e(TAG,key+" test 和: "+test);
}else{
for(int k=0;k<size;k++){
weghtCompDataList.add(k, 1.0/size);
}
}
weghtCompensationDatas.put(key, weghtCompDataList);
}//权重分布完成
/**
* 时间越后,权重越大
*
* @param sacnedBleDD
* @return
*/
public static Map<String,Double> getDeviceState(final Map<String,List<Integer>> sacnedBleDD){
sacnedBleDevicesData=sacnedBleDD;
clearDigest(); //去除脏值
Map<String,List<Double>> weightCompensation=getWeghtCompensationMap(); //权数分布因子
Log.e(TAG," "+weightCompensation);
Map<String,Double> averageSSID=new HashMap<>();
Set<String> ks =sacnedBleDevicesData.keySet();
for(String key : ks ){
double average=0.0;
List<Double> weghtCompDataList=weightCompensation.get(key);
List<Integer> ssidList=sacnedBleDevicesData.get(key);
for(int i=0;i<weghtCompDataList.size();i++){
average=average+weghtCompDataList.get(i)*ssidList.get(i);
}
Log.d(TAG,key+" 加权算术平均:"+average);
averageSSID.put(key, average);
//bingo.
}
return averageSSID;
}
}
Activity
package com.bipbip.ble;
import com.bipbip.main.BaseActivity;
/**
*
*
* @author anylife.zlb@gmail.com
*/
@SuppressLint("NewApi")
public class BlePreventLostActivity extends BaseActivity implements OnClickListener{
private String TAG=BlePreventLostActivity.class.getSimpleName();
private TextView instruction_tips;
private ListView bleListView;
//已经扫描出来的设备列表集Set,自定义obj是否重复
private List<BleDevice> findedBleDevicesList = new ArrayList<BleDevice>();
private List<BleDevice> sacnedBleDevicesList = new ArrayList<BleDevice>();
//已经扫描出来的设备(MAC作为Key)数据集合(List<?> ?需要是BleDevice?不如integer )
private Map<String,List<Integer>> sacnedBleDevicesData = new HashMap<String,List<Integer>>();
private BleDeviceListAdapter mDeviceListAdapter;
private BluetoothAdapter mBluetoothAdapter;
private boolean mFindDevice=true; //true : 刚刚进入蓝牙防丢的设备扫描阶段; false:对扫描出来的设备进行蓝牙防丢
private boolean mLiveDevice=false; //true : 刚刚进入蓝牙防丢的设备扫描阶段; false:对扫描出来的设备进行蓝牙防丢
private Handler mHandler;
private DeviceLiveThread deviceLiveThread;
private static final int REQUEST_ENABLE_BT = 1;
// Stops scanning after 10 seconds.
private static final long SCAN_PERIOD = 1000*10; //点击开始扫描后的10秒停止扫描
private static final long LIVE_PERIOD = 1000*7; //点击开始扫描后的10秒停止扫描
@Override
public void onCreate(Bundle savedInstanceState) { //
super.onCreate(savedInstanceState);
setContentView(R.layout.ble_prevent_lost);
instruction_tips=(TextView) findViewById(R.id.instruction_tips);
instruction_tips.setOnClickListener(this);
bleListView=(ListView) findViewById(R.id.ble_device_list);
// Initializes list view adapter.
mDeviceListAdapter =new BleDeviceListAdapter(this,findedBleDevicesList);// new mDeviceListAdapter(this);
bleListView.setAdapter(mDeviceListAdapter);
mHandler = new Handler();
// Use this check to determine whether BLE is supported on the device. Then you can
// selectively disable BLE-related features.
//如果手机不支持蓝牙4.0,api>18. 那么直接退出
if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
finish();
}
// Initializes a Bluetooth adapter. For API level 18 and above, get a reference to
// BluetoothAdapter through BluetoothManager.
final BluetoothManager bluetoothManager =(BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
mBluetoothAdapter = bluetoothManager.getAdapter();
// Checks if Bluetooth is supported on the device.
if (mBluetoothAdapter == null) {
Toast.makeText(this, R.string.error_bluetooth_not_supported, Toast.LENGTH_SHORT).show();
finish();
return;
}
Log.e(TAG," test"+BleDeviceState.UNKNOW.getStateName());
findDevice(true);
}
/**
* 扫描设备蓝牙设备
*
*
* @param enable
* true:扫描设备,并在SCAN_PERIOD后停止
* false:不扫描,直接就停止了(在onPause中停止了)
*/
private void findDevice(final boolean enable) {
if (enable) {
setInstructStytle(true);
mFindDevice = true;
mBluetoothAdapter.startLeScan(mLeScanCallback); //开始扫描设备
mDeviceListAdapter.notifyDataSetChanged();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
Log.d(TAG,"\n\n设备个数:"+findedBleDevicesList.size()+"\n设备信息 :\n"+findedBleDevicesList.toString());
if(mFindDevice){
if(findedBleDevicesList!=null&&findedBleDevicesList.size()>0){
instruction_tips.setVisibility(View.VISIBLE);
// findedBleDevicesList --> sacnedBleDevicesData
for(int i=0;i<findedBleDevicesList.size();i++){
List<Integer> tempList=new ArrayList<Integer>();
tempList.add(findedBleDevicesList.get(i).getSsid());
sacnedBleDevicesData.put(findedBleDevicesList.get(i).getMacAddr(), tempList);
}
}
}
mBluetoothAdapter.stopLeScan(mLeScanCallback);
mFindDevice=false;
invalidateOptionsMenu();
}
}, SCAN_PERIOD);
} else {
setInstructStytle(false);
mFindDevice = false;
mBluetoothAdapter.stopLeScan(mLeScanCallback);
}
invalidateOptionsMenu();
}
/**
* Device scan callback.
*
* if you call [mBluetoothAdapter.startLeScan(mLeScanCallback);]this will call back.
*
*/
private BluetoothAdapter.LeScanCallback mLeScanCallback =
new BluetoothAdapter.LeScanCallback() {
@Override
public void onLeScan(final BluetoothDevice device, final int rssi, final byte[] scanRecord) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.e(TAG,"设备"+device.getName()+" Rssi强度:"+rssi+" "+device.getAddress());
BleDevice bleDevice=new BleDevice(0, device.getName(), device.getAddress(), "uuid", rssi, device.getBondState(), device.getType());
if(mFindDevice){ //处于第一次的发现设备阶段
if(findedBleDevicesList!=null&&!findedBleDevicesList.contains(bleDevice)){
findedBleDevicesList.add(bleDevice);
mDeviceListAdapter.notifyDataSetChanged();
}
}else{
//
if(sacnedBleDevicesList!=null&&!sacnedBleDevicesList.contains(bleDevice)){
sacnedBleDevicesList.add(bleDevice);
// mDeviceListAdapter.notifyDataSetChanged();
}
//2. 向MAC 地址对应的List<Integer> 添加一个值。
List<Integer> bleDevices=sacnedBleDevicesData.get(bleDevice.getMacAddr());
if(null==bleDevices){
bleDevices=new ArrayList<Integer>();
}
bleDevices.add(rssi);
sacnedBleDevicesData.put(bleDevice.getMacAddr(), bleDevices);
}
}
});
}
};
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// User chose not to enable Bluetooth.
if (requestCode == REQUEST_ENABLE_BT && resultCode == Activity.RESULT_CANCELED) {
finish();
return;
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* 设置一些样式
*
* @param needReScan
*/
private void setInstructStytle(boolean needReScan){
if(needReScan){
instruction_tips.setVisibility(View.INVISIBLE);
mDeviceListAdapter.clear();
}else{//要那种慢慢显示出来的效果
instruction_tips.setVisibility(View.VISIBLE);
}
}
public class DeviceLiveThread extends Thread {
private boolean isRunning=true;
private void stopThread(){
isRunning=false;
}
@Override
public void run() {
// TODO Auto-generated method stub
while(isRunning){
if(mLiveDevice){
Log.d(TAG,"统计结果,采集数据为==================\n");
Set<String> keyset=sacnedBleDevicesData.keySet();
for(String key:keyset){
Log.e(TAG,"size="+sacnedBleDevicesData.get(key).size()+" "+sacnedBleDevicesData.get(key));
}
Log.d(TAG,"统计结束,清除本次统计*****************************************************\n");
if(null!=sacnedBleDevicesData&&sacnedBleDevicesData.size()>0){
Map<String,Double> rssiValueList=BlePreventLostCore.getDeviceState(sacnedBleDevicesData); //根据换回的结果统计
displayLiveResult(rssiValueList);
rssiValueList.clear();
sacnedBleDevicesData.clear();//一次分析完后,清除
} else{ //上一次分析完后再也没有采集到数据,全部丢失
displayLiveResult(null);
}
try {
Thread.sleep(LIVE_PERIOD);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
try {
Thread.sleep(LIVE_PERIOD);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
/**
* 开始进入
*
*/
private void startDeviceLiving(){
if(deviceLiveThread==null){
deviceLiveThread=new DeviceLiveThread();
deviceLiveThread.start();
}
}
/**
* 更新数据显示的结果
*
* @param rssiValueList
*/
private void displayLiveResult(Map<String,Double> rssiValueList){
//0.处理异常的情况
if(null==rssiValueList||rssiValueList.size()==0){
int size=findedBleDevicesList.size();
for(int i=0;i<size;i++){
findedBleDevicesList.get(i).setSsid(0);
}
}else{
//1.处理返回来的适配数据
Set<String> keySet=rssiValueList.keySet();
int size=findedBleDevicesList.size();
for(int i=0;i<size;i++){
String findedDeviceKey=findedBleDevicesList.get(i).getMacAddr(); //
if(keySet.contains(findedDeviceKey)){ //假如以前扫描到的设备在监听阶段还是存在,那么重新赋值RSSI,否则丢失了
findedBleDevicesList.get(i).setSsid(rssiValueList.get(findedDeviceKey).intValue());
}else{ //置设备状态为丢失,震动报警
findedBleDevicesList.get(i).setSsid(0);
}
}
}
//2.更新显示数据。
runOnUiThread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
mDeviceListAdapter.notifyDataSetChanged();
}
});
}
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.instruction_tips:
mLiveDevice=!mLiveDevice;
invalidateOptionsMenu();
if(mLiveDevice){
instruction_tips.setText(R.string.device_live_stop);
mBluetoothAdapter.startLeScan(mLeScanCallback); //开始扫描设备
startDeviceLiving();
}else{
instruction_tips.setText(R.string.device_live_start);
mBluetoothAdapter.stopLeScan(mLeScanCallback); //开始扫描设备
stopDeviceLiving();
}
break;
default:
break;
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.blemenu, menu);
if(mLiveDevice){
menu.findItem(R.id.menu_stop).setVisible(false);
menu.findItem(R.id.menu_scan).setVisible(false);
menu.findItem(R.id.menu_refresh).setActionView(null);
}else if (!mFindDevice) {
menu.findItem(R.id.menu_stop).setVisible(false);
menu.findItem(R.id.menu_scan).setVisible(true);
menu.findItem(R.id.menu_refresh).setActionView(null);
} else {
menu.findItem(R.id.menu_stop).setVisible(true);
menu.findItem(R.id.menu_scan).setVisible(false);
menu.findItem(R.id.menu_refresh).setActionView(
R.layout.actionbar_indeterminate_progress);
}
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_scan:
findDevice(true);
break;
case R.id.menu_stop:
findDevice(false);
break;
}
return true;
}
private long exitTime = 0;
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN){
if((System.currentTimeMillis()-exitTime) > 2000){
Toast.makeText(getApplicationContext(), R.string.double_return_exit, Toast.LENGTH_SHORT).show();
exitTime = System.currentTimeMillis();
}else{
BlePreventLostActivity.this.finish();
}
return true;
}
return super.onKeyDown(keyCode, event);
}
//========================Activity life cycle=======================================================
@Override
protected void onStart(){
super.onStart();
Log.e(TAG,"*************onStart");
// Ensures Bluetooth is enabled on the device. If Bluetooth is not currently enabled,
// fire an intent to display a dialog asking the user to grant permission to enable it.
if (!mBluetoothAdapter.isEnabled()) {
if (!mBluetoothAdapter.isEnabled()) {
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_ENABLE_BT);
}
}
}
@Override
protected void onRestart(){
super.onRestart();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onPause() {
super.onPause();
Log.e(TAG,"onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.e(TAG,"*************onStop");
}
@Override
protected void onDestroy(){
super.onDestroy();
Log.e(TAG,"*************onDestroy");
if(deviceLiveThread!=null){
deviceLiveThread.stopThread();
}
mLiveDevice=false;
findDevice(false);
sacnedBleDevicesList.clear();
findedBleDevicesList.clear();
sacnedBleDevicesData.clear();
mDeviceListAdapter.notifyDataSetChanged();
}
}