Android 6.0/10.0扫描不到Ble设备需开启位置权限

蓝牙设备扫描需要定位权限的原因在于蓝牙定位的存在,蓝牙定位属于精确定位,因此需要开启精确定位权限,而精确定位跟GPS又是直接相关的,因此才会导致GPS权限需要获取。

项目中需要用到android Ble蓝牙4.0开发技术,于是开启了蓝牙填坑之旅,说实话,蓝牙开发坑真多,跳出一个又进入下一个,每次遇到 问题,就觉得不可能解决了,还好在自己的摸索中,都一一的化解了,以此来记录安卓蓝牙开发的心得。

     接手的蓝牙开发项目,原来的同事已经写好,不用再去写,开始也就大概看了看android蓝牙开发相关资料,对比项目中的蓝牙开发代码,发现发现搜索、连接蓝牙有一样的处理也有不一样的处理,想着项目不一样,代码处理肯定不一样,于是就没去深究,毕竟项目运行起来使用正常,然而,没过几天,客户提出蓝牙搜索太慢,然后就去调试,先看代码是搜索蓝牙8秒钟才显示搜索到的蓝牙设备列表,查看日志,其实不到8秒就搜索到外围设备,只是搜索8秒后停止扫描才显示蓝牙设备,那就把显示成3、4秒呗。
        正这样想,同事拿来一部荣耀8,装的APP怎么就是搜不到蓝牙设备,然后调试其他手机,都能搜索到,于是就纳闷了,荣耀8怎么就是搜索不到蓝牙设备呢,其他手机都能,再看一遍代码,也没有专门针对荣耀8处理的地方,再想,荣耀8是系统7.0的手机,是不是跟权限没开有关呢,对比其他华为手机,申请的蓝牙权限也都开着,荣耀8就是搜索不到,是不是荣耀8手机有问题,同事告知下载一个第三方蓝牙调试工具查查看,令人疑惑的是荣耀8用第三方蓝牙调试工具立刻就能扫描显示出来,试了几次都是立刻扫描出来,当时心想,我靠,什么鬼,这么迅速就能把蓝牙设备扫描显示出来,领导也说,别人的调试工具都能做这么快,你也得做这么快,想着目前的代码都是几秒后才能搜索到,目前的荣耀8还搜索不出来,客户那边还一个劲儿的给老板吐槽太慢,于是压力剧增,真正开始了蓝牙开发不归之旅。
     但是再考虑索慢时,领导说,得把荣耀8搜索不到的问题先解决,因为荣耀8是项目实施人员给客户安装硬件调试数据要用的,那就先把搜索慢暂时放一放,想着一边搜索慢没还没着落,荣耀8手机就是搜索不到,想想就头大,查找荣耀8为什么搜索不到蓝牙设备的资料,也没发现有效的办法,这时,用荣耀手机的人说,另一个公司的项目app(简称B吧,荣耀8搜索不到蓝牙设备的项目简称A吧)就能正常搜索到蓝牙设备,一打开调试还真是每次都能搜索到,看了一下两个项目的代码:

      两个项目中的蓝牙适配器的获取,去发现,动态注册广播、广播接收蓝牙搜索结果、蓝牙连接都一样,至于手机是否支持ble4.0,蓝牙是否开启的逻辑,连接上对数据的处理、断开mBluetoothGatt.disconnect(),关闭mBluetoothGatt.close() 的逻辑网上有很多,我这里就不在详说;
       经过2个项目对比,发现就蓝牙搜索多久停止不一样,荣耀8能搜索到蓝牙设备的时间项目代码设置是10秒,而搜索不到的项目是设置8秒,带着半信半疑把项目A的蓝牙搜索时间由8秒改成10秒,运行,奇迹还真是出现了,荣耀8可以正常搜索到蓝牙设备了,反复测了好多次都能正常搜索到蓝牙设备,我是不信邪的人,还改成8秒试试,果然还是搜索不到。
     于是和领导说,你看荣耀8必须10秒才能搜索 到蓝牙设备,搜索到的蓝牙设备列表不晚点展示不行啊,领导自然 是不同意,拿出第三方蓝牙提哦啊是工具说,那第三方就能很快搜索到,你想想其他法吧,想着有的手机快,有的手机慢,提出意见,能不能搜索到一个设备就展示到列表,不必等10秒后再展示,这样体验好些,反正领导说不行,既然领导说不行,那就回去研究吧。
    查了很多蓝牙搜索慢怎么办?看了半天,当然这期间调试过各种其他方法,都没用,功夫不负有心人,其中一个博客说:
btAdapt.startDiscovery();搜索返回的结果慢, 现在都用这个 btAdapt.startLeScan(getmLeScanCallback); 其他也没多说什么,喜出望外,这不是正是我要的吗,于是改蓝牙搜索,上代码:

            经过这样修改之后,奇迹又出现了,蓝牙搜索到确实快了很多,一秒左右甚至不到一秒就把蓝牙设备搜索出来了, 真是令人喜出望外,想起华为荣耀8那么奇葩,不知这个方法在华为荣耀8上测试如何,然而,2秒,4秒,8秒,10秒,20秒都在华为荣耀8搜索不出来,心又凉了半截,暗想这个方法看来还不适用,又白整了,于是又查各种资料,有的说android系统6.0以上的手机需要定位权限,低头一想,还真是,一直测试的手机是android系统6.0以下的,华为荣耀8是7.0以上的,抱着试试的态度,申请权限,还真是努力出奇迹,定位权限允许后,华为荣耀8确实也在不到一秒将蓝牙设备搜索出来,测试了其他6.0以上的手机,都正常很快搜出来。
        测试没有其他问题,打包发布,然而客户不给定位权限,问公司怎么一点搜索不出来,告知需要允许定位,才能搜索到,客户觉得一个蓝牙搜索你跟我要定位,很是不理解,老板也认为这样的体验也不好,客户不给权限,就无法使用,非常不友好。告知领导6.0以上必须给定位权限,否则搜不到, 这个没法啊,领导说你看第三方的蓝牙调试工具,在6.0以上的手机,不用打开定位,而且搜索的更快,第三方都能做到,你想办法吧, 我当然不苟同,下载了几个第三方蓝牙调试工具,有的确实要定位权限不允许定位就无法搜索到,有的确实禁止定位权限也能快速搜索到蓝牙设备,这么啪啪打脸,只能埋头去想办法了,同时,为了保证程序兼容性,公司又买了几部6.0以上的安卓手机,让我更惊心的是,魅族6和华为畅享7不仅要允许定位权限,GPS定位还要打开,GPS不打开,也是搜索不到蓝牙设备,想起以前开发的项目,由于安卓碎片化,不同的手机厂商对定位,GPS管理不一样,有的打开定位权限GPS权限也给了,有的打开GPS定位权限也给了,有的是他们两个独立,处理起来很麻烦,而蓝牙有的要GPS,有的不要GPS,6.0一下还不需要任何定位权限,想着这处理起来太麻烦,还是安心去找不要定位权限的方法吧,其实当时心里是没底的,虽然第三方已经实现这种不需要定位权限就能搜索到,心想,第三方也不知用的是什么黑科技,于是就查找android系统6.0一上的手机不需要定位权限就能把蓝牙设备搜索出来等相关资料,其中最有帮助的查到 btAdapt.startLeScan(getmLeScanCallback) 这个方法已过时,google已经在api里将其划掉,不推荐使用,推荐使用:

喜出过望,难道是用的方法过时了,所以需要定位权限,上代码:

停止扫描的方法是:

        然而,虽然使用了新方法,速度也是一样快,但是还是需要定位权限,魅族6和华为畅享7还得打开GPS,看来这个也不是正解,当然项目中的扫描外围蓝牙设备的方法也都换成了scanner.startScan(scanCallback);
第一天毫无结果,灰头土脸,昏天暗地,焦头烂额地回家。
第二天继续查找相关资料,在一个偶然的机会,看到一篇博客说,把app中的build.gradle中的:

中的  targetSdkVersion 25改成 targetSdkVersion 22,就不需要定位权限也可以搜索道蓝牙设备了,看到网上很少有人这样说的,搜了那么多资料,就一篇这样说的,带着半信半疑将targetSdkVersion改成 22, 编译,也没报任何错,运行,还真是又出奇迹,测试了手上的所有手机,都不需要定位,不要GPS就可以搜索到蓝牙设备。
       至于为什么会这样,可以查找 android compileSdkVersion、buildToolsVersion,targetSdkVersion之间的区别,网上有很多解释,保证能解答你的疑惑,下面附上一些简单的解释
     targetSDKVersion
    简单来说就代表着你的App能够适配的系统版本,意味着你的App在这个版本的手机上做了充分的 前向 兼容性处理和实际测试。其实我们写代码时都是经常干这么一件事,就是 if(Build.VERSION.SDK_INT >= 23) { ... } ,这就是兼容性处理最典型的一个例子。如果你的target设置得越高,其实调用系统提供的API时,所得到的处理也是不一样的,甚至有些新的API是只有新的系统才有的Android6.0普通权限normal permission 和 危险权限dangerous permission 。
 Normal Permission:写在xml文件里,那么App安装时就会默认获得这些权限,即使是在Android6.0系统的手机上,用户也无法在安装后动态取消这些normal权限,这和以前的权限系统是一样的,不变。
Dangerous Permission:还是得写在xml文件里,但是App安装时具体如果执行授权分以下几种情况:
1、targetSDKVersion < 23 & API(手机系统) < 6.0 :安装时默认获得权限,且用户无法在安装App之后取消权限。
 2、targetSDKVersion < 23 & API(手机系统) >= 6.0 :安装时默认获得权限,但是用户可以在安装App完成后动态取消授权( 取消时手机会弹出提醒,告诉用户这个是为旧版手机打造的应用,让用户谨慎操作 )。
3、targetSDKVersion >= 23 & API(手机系统) < 6.0 :安装时默认获得权限,且用户无法在安装App之后取消权限。
 4、targetSDKVersion >= 23 & API(手机系统) >= 6.0 :安装时不会获得权限,可以在运行时向用户申请权限。用户授权以后仍然可以在设置界面中取消授权,用户主动在设置界面取消后,在app运行过程中可能会出现crash。
其他:
        1.虽然解决了大问题,但是目前项目中发现,连续扫描-断开-扫描四五次,就无法再搜索到外围蓝牙设备,尤其是在每次搜索1-2秒内扫描断开,4、5次无论如何也是搜索不到,需要重新进入界面才能扫描到,每次扫描花费在4、5秒以上的,出现4、5次扫不到的几率就很低了,当然,4、5秒是老板和客户无法忍受的。
2.蓝牙连接成功自动断开;

参考链接


Android dp 和 CSS px

最近在给WebView前端传递标题栏高度的时候,传递的是实际像素Pixel,结果前端直接设置这个数值的时候,发现太高了。搜索了一下,发现前端的CSS样式设置的像素值是逻辑像素,这个逻辑像素跟实际像素是不同的,对于Android来说,就是设备的dp数值。

参考文章如下:

前情提要:设计师给的设计稿是1242分辨率的(iPhone6p),标注的字体大小是54px,前端工程师写的H5页面是18px,效果正常且能自适应iPhone6。来调查一下 为什么是标注数值除以3?为什么自动适应iPhone6?css里的px和Android的dp有怎样的关系?

在进行具体的分析之前,首先得知道下面这些关键性基本概念(术语)。

物理像素(physical pixel)

一个物理像素是显示器(手机屏幕)上最小的物理显示单元,在操作系统的调度下,每一个设备像素都有自己的颜色值和亮度值。

设备独立像素(density-independent pixel)

设备独立像素(也叫密度无关像素),可以认为是计算机坐标系统中得一个点,这个点代表一个可以由程序使用的虚拟像素(比如: css像素),然后由相关系统转换为物理像素。

所以说,物理像素和设备独立像素之间存在着一定的对应关系,这就是接下来要说的设备像素比

设备像素比(device pixel ratio)

设备像素比(简称dpr)定义了物理像素和设备独立像素的对应关系,它的值可以按如下的公式的得到:

在javascript中,可以通过window.devicePixelRatio获取到当前设备的dpr。

在css中,可以通过-webkit-device-pixel-ratio-webkit-min-device-pixel-ratio-webkit-max-device-pixel-ratio进行媒体查询,对不同dpr的设备,做一些样式适配(这里只针对webkit内核的浏览器和webview)。


iPhone6的物理像素是750 x 1334,设备像素比(devicePixelRatio)是2,设备独立像素是375 x 667 (750/2 x 1334/2)

iPhone6p的物理像素是1080 x 1920,但logic pixel是1242 x 2208, 设备像素比(devicePixelRatio)是3,设备独立像素是414 x 736 (1242/3 x 2208/3)

css里面有物理像素逻辑像素的概念,在这里,逻辑像素可以理解为设备独立像素

其实,logic point更适合翻译成逻辑点。无论是逻辑像素还是逻辑点,只需要理解一个逻辑点(逻辑像素)需要一个或一个以上的物理像素来展示就可以了。

那么,为什么iPhone6p是这样奇葩的逻辑点呢?

  • 如果逻辑点分辨率用 360x640, 360x640@3x 正好是 1080x1920。但是逻辑pt分辨率 360x640 就会比 iPhone 6的 375x667 还低,也就是说相同字号的情况下,iPhone 6如果一行显示了25个字,而 iPhone 6 Plus按这个逻辑pt方案,一行就会只能显示24 个字了。
  • 如果逻辑点分辨率用 540x960,540x960@2x 正好是 1080x1920。但是iOS UI 元素尺寸在屏幕上的实际物理面积一下子就变小了,比如标签栏或导航栏按钮的物理高度只有原来的 81.5% ,点击面积就只有iPhone 6 的0.815*0.815=66.4%,用户点击就困难了。
  • 如果物理分辨率做到1242x2208就减少了一个从1242压缩到1080的过程,但是1242的物理分辨率是在1080p和2k屏之前的尺寸,功耗和成本都会提升,而且这样非1440的2k分辨率的屏幕采购也是问题。

至于为什么一定是 414x736,有人估计应该是在 5.5inch 和 ppi=461 这两个前提限定的情况下,按这个 414x736 pt 分辨率,屏幕上 UI 元素操作物理大小最接近 iPhone 6上的表现。


Android世界中更加复杂,这里列举了几款手机的基本数据

  Nexus 4 Nexus 6 Nexus 6p LG L24 Mi 4c
物理像素 768 x 1280 1440 x 2560 1440 x 2560 1440 x 2560 1080 x 1920
设备像素比(dpr) 2 3.5 3.3 4 3
设备独立像素(dip/dp) 384 x 640 412 x 732 435 x 773 360 x 640 360 x 640

Android提供了获取density的方法:

density的官方定义是The logical density of the display,翻译过来是屏幕的逻辑密度,其实就是之前提到的设备像素比(dpr)


在浏览器中,获取设备像素比就比较简单:

比如,我写了一个网页,用手机浏览器打开:

结果显示与Android的density的结果是一致的。

注:<meta name="viewport" content="width=device-width, initial-scale=1">
By specifying width=”device-width”, you are asking the browser to apply a scaling factor to its screen pixels. One css pixel occupies one or more screen pixels. How many more? This value is called the css pixel ratio.


回顾一下

同一台设备,它的设备像素比(dpr)是确定的,无论通过Android api 获取还是浏览器 js/css 获取。
The css px is actually Device Independent Pixel(dip), and it is a common pattern to use px as dp in css.
css中的px具有与Android中的dp等同的效果

参考链接