是什么
对客户端而言,权限隐私可分为权限和隐私两个大的方面。
-
权限为用户通过app内弹窗设置或者手机设置内对应app的权限设置方式给予对应app相应的权限,如电话权限,定位权限,相机权限等,本文主要集中介绍隐私相关的权限部分。
-
隐私为app使用过程中与用户个人相关的个人信息,如所在位置,Mac地址,设备id等。就Android端而言,多数隐私信息需要对应授权后才能获取,但目前仍存在部分隐私信息无需授权就可以拿到的。
为什么
-
大众隐私意识觉醒,权限隐私安全性差会直接导致用户不愿使用;
-
日趋严格的权限治理和隐私安全治理,工信部和市场的严格管控;
-
客户端作为与用户最直接的交互信息收集入口,有义务合规化的收集和使用用户信息。
具体实践
一、Android各版本对权限的适配处理
1.1 早期的注册权限
Android6.0(SDK版本为23)之前的版本,安装App页面会列出当前app所注册的所有权限,无同意与否按钮,只有安装和取消,开发App时只需要在清单文件中注册所需的对应权限即可:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CAMERA" />
- 1.
- 2.
- 3.
- 4.
1.2 动态权限授予
Android自6.0(SDK版本为23)开始,将权限分为普通权限,危险权限,特殊权限。而其中的 危险权限 需要在调用某些系统方法之前需要用户手动授予对应权限,包括PHONE,LOCATION,STORAGE等多个权限组。如果在没授权的情况下直接调用相关方法,就会抛出,应用也随之崩溃。报错信息类似下方这种:
java.lang.SecurityException: getDeviceId: has android.permission.READ_PHONE_STATE.
- 1.
而要解决以上的报错问题,可以自行封装权限处理类工具,也可使用一些开源的权限工具进行处理。核心代码都逃不过:
//判断某个权限是否已经被同意
ContextCompat.checkSelfPermission(context, perm) == PackageManager.PERMISSION_GRANTED)
//请求某个权限,调用后会弹出权限系统弹窗
ActivityCompat.requestPermissions((Activity) object, perms, requestCode);
- 1.
- 2.
- 3.
- 4.
注:如果用户拒绝权限且不让再次显示系统权限授权弹窗的话,最好是提供端内可点击进入手机系统的权限设置页面以让用户可以选择开启对应权限。
1.3 READ_PHONE_STATE权限的变化
1.3.1 演变
- READ_PHONE_STATE权限是允许访问电话状态权限,此权限主要集中在
TelephonyManager
类中,控制对imei,deviceId,Meid,SimSerialNumber等id和其他手机状态的获取。而多数app都会单独引用或者组合引用这些字段作为设备唯一码来标识用户设备,进而服务端进行数据分析,下发等操作。在6.0之前只需注册后就可以使用。 -
在Android6.0之后,READ_PHONE_STATE权限变为危险权限,需要用户主动授权后方可使用,故部分App处理的操作为必须授权该权限后才允许使用App,不授权的话就退出应用。这显然不是一个很好的解决方式,但在过渡阶段还是能有一定效果的。可以预留出时间重新定义在未授权情况下如何标识特定设备。
- 在适配Android10.0之后,READ_PHONE_STATE权限直接被取消掉了,换成了系统权限 READ_PRIVILEGED_PHONE_STATE ,此权限只能在系统App中才可以被使用。而如果代码处理上仍使用READ_PHONE_STATE权限进行授权的话,手机上不会再弹出同样的权限授予弹窗了,此时如果仍调用
getDeviceId
方法,会直接抛出 SecurityException 。源码上的可以体现在对应方法的注解上:
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getDeviceId()
@RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String getImei()
- 1.
- 2.
- 3.
- 4.
1.3.2 适配处理
-
清单文件中设定使用READ_PHONE_STATE权限最高为28,即Android10以上的设备不进行该权限的获取。
<uses-permission
android:name="android.permission.READ_PHONE_STATE"
android:maxSdkVersion="28"/>
- 1.
- 2.
- 3.
getDeviceId
getImei
Build.VERSION.SDK_INT < Build.VERSION_CODES.Q
- 1.
- 2.
- 3.
-
由于Android10.0以上手机获取不到这些id,对国内广告的精准推送也都产生了一定的影响,故各大产商联盟推出了OAID的标识,本意是用于广告精准推送,但目前也不失作为客户端App唯一标识的一种选择。具体获取方式参考联盟的集成文档即可。
1.4 存储分区的处理
自Android10.0之后,Google开始采用存储分区,主要目的是改变现有App胡乱使用手机存储导致垃圾和其他安全问题。适配 Android11 后强制使用存储分区。具体分区如下,扩展的外部存储是无权限进行读取的。而其他私有存储会在App卸载后清理掉:
具体部分代码改动如下:
1.拍照存储路径:
Environment.getExternalStorageDirectory().getAbsolutePath()
修改为 getExternalFilesDir(Environment.DIRECTORY_DCIM)
;
2.原本的存储路径 /storage/emulated/0 改为 /storage/emulated/0/Android/data
具体调用的修改为:
Environment.getExternalStorageDirectory()
改为 context.getExternalFilesDir()
;
3.如果App在sdcard中有重要存储,可以在适配android10.0的过渡阶段将之前的数据复制出来到新的存储分区中。
二、隐私信息合规化处理
上半部分较为粗略的过了一下权限相关的部分改动和对应修改,接下来说一说隐私信息的合规化处理。当然,权限作为隐私处理的前提,如果权限都没有合理的修改完毕,那隐私处理合规化更谈不上了,毕竟很多隐私是依赖于权限的。
2.1 隐私信息获取告知的直接化和透明化
-
在首次打开App时,需要在进行初始化之前就向用户展示用户协议与隐私保护弹窗或页面,只有用户在同意之后才能进入App进行使用。而对于 手机号、MAC地址、IMEI、所在位置信息、手机存储权限、相册访问权限 , 手机流量使用 等敏感信息需要让用户在第一屏就能够看到。并且提供用户协议和隐私政策链接,能让用户点击后查看具体详细的条款。设计完成后要让法务进行确认,是否符合。
-
在处理1.2中的动态权限时,需要在系统弹窗中或者之前说明需要用户授予该权限的原因。如:获取定位权限之前需要告知用户该权限是为了获取定位信息,然后精准推送相关内容。获取相机权限是为了要使用摄像头进行拍照。
-
在进入App操作三步以内能看到法律条款和隐私政策入口,正常处理方式会在App的 设置页 内加上对应入口。同时在注册登录页面,需要明显展示出法律条款和隐私条款入口,且需要默认不勾选,需要用户主动同意后才能进行账号注册和登录。如下图:
-
分发广告的App需要注意处理广告下载逻辑,在用户点击后需要展示所下载App的信息,所需的权限和隐私条款,让用户清楚的知道下载的App是否是自己想要的,且不允许自动下载。这样能很好的解决用户无意识的在手机上下载了很多无用App,这对很多老年人使用手机很有帮助。
2.2 隐私信息获取和传输的安全化
- 避免频繁的调用系统方法获取隐私信息,可以在单次启动App调用该获取数据后使用全局变量进行缓存,之后每次使用时直接调用全局变量使用就行,不必每次都调用系统方法。包括
getDeviceId
,getMacAddress
等。 -
诸如imei,mac,定位的经纬度等敏感信息,需要避免多次在网络中传输,可以处理为单独接口收集相关信息一次后保存在服务端即可,无需每次传输;另外需要避免以明文的方式在数据接口中传输。像imei可以通过MD5加密算法进行加密处理,并不会影响用户的区分;
-
由于READ_PHONE_STATE权限升级为了系统权限 READ_PRIVILEGED_PHONE_STATE ,部分通过native方式(C代码)直接调用imei等信息时也会报错或者为空。建议这部分尽量使用java方式调用,如果有变动可以明显的感知到错误并修改,不至于需要重新修改C代码,然后又进行jni编译。
2.3 部分隐私Api调用的严格化
-
在未授权的情况下,需要保证App中与该权限无关联的功能可以正常使用。所以就不能简单的处理为1.3.1中提到的不给权限就不让使用App的方案了。
-
Android端目前尚存在部分无需动态授权就可以获取的隐私,如用户手机上的应用安装列表。此信息可用于分析用户喜好,如小说类产品还是视频类产品;也可以用于分析用户某些App还未安装,便于推送广告的拉新。但目前国内市场已经开始治理,如果存在获取手机内应用列表的情况,会进行下架处理或者不予上架。
-
目前工信部和各应用市场对App上架要求严格,使用第三方检测工具可以很细致的检测出App中存在哪些不合理的系统方法调用,比如:在未同意协议与隐私之前就进行了网络请求;在未同意协议与隐私之前获取了Mac地址;在未获取定位权限的情况下就获取了手机的基站信息。
三、遇到的一些问题和坑
这里总结部分在开发过程中遇到的一些隐蔽小点,希望能帮助到大家。
-
早期的腾讯X5内核会在隐私协议展示时就会获取mac地址,如下图。可尝试更新到新的版本继续查看。由于我方对X5内核需求不高,所以直接进行了删除清理。
-
集成开源库或者第三方sdk的初始化均需要处理为同意隐私之后再进行,大多数sdk在初始化时都会调用相关无需授权的api方法。如语音相关的讯飞sdk会在初始化的时候调用MAC地址信息。
-
部分统计库如umeng,talkingdata sdk需要升级到新版本的接入方式。老版本的talkingdata sdk在尚未授定位权限时进行初始化仍会调用手机基站信息api(属于定位)。
-
自有代码逻辑中相关隐私信息的获取和赋值,也都要放到隐私同意之后去进行,故在用户协议和隐私同意之前尽量少的进行代码逻辑处理。
总结
权限隐私的发展趋势只会越来越严格和规范。在日常的客户端开发当中,我们就需要时刻持有隐私安全的意识,让自己站在用户的角度上合理的保证隐私安全。并紧跟隐私安全的发展,提前布局。这样才能不至于临时出问题后手忙脚乱的去处理。
本文仅粗略的记录了一些权限隐私相关的一些情况和做法,如有不当,欢迎指正 。