从android N开始,如果应用targetSdk大于等于24, 应用data目录默认情况下,不能被其他应用访问,如果想分享本地文件给其他应用(如相册分享图片) , 只能通过ContentProvider提供,接下来看看Content Uri相关几个问题。
1. 为何需要Uri权限
通过Uri分享文件,需要通过ContentProvider实现,如果不通过Uri权限授予读取权限,就只能exported整个ContentProvider,访问权限过大。通过Uri Permission授予权限,可以将访问权限限制在单个文件的Uri上。
1. 如何授予权限?
有两种方式:
1) 请求方通过startActivityForResult获取文件Uri
这种情况下,通过setResult返回调用方一个intent,通过intent.setData将文件Uri 作为data返回,同时给intent.setFlag(Intent.FLAG_GRANT_READ_URI_PERMISSION)等权限,这样,调用方就获取了临时权限。当caller activity销毁时,授予的权限会自动移除,不需要显示的revokePermission
2) 通过Context.grantUriPermission()方法授予
该方法有三个参数,分别为targetPackage, uri, permission, 通过这种方法授予权限,必须显示调用Context.revokeUriPermission()方法移除掉
可以看到,两种方法的区别在于:
- 通过intent.setData()只能用于startActivityForResult获取uri的情况
- 通过intent.setData()授予的权限会自动移除,而content.grantUriPerimssion授予的权限必须显示调用context.revokeUriPermission移除,需要自己管理权限声明周期
Uri Permission生命周期
从上一节可以看到,通过intent.setData 授予的权限生命周期随着caller activity销毁而销毁。
通过context.grantUriPermission需要调用context.revokeUriPermission移除。但grantUriPermission授予的权限也有其生命周期。默认情况下,权限是不跨设备重启的,一旦设备重启了,权限就自动回收了,如果想重启后依然具有权限,需要grantUriPermission时加上Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION属性,并且被授权方需要调用方法ContentResolver.takePersistableUriPermission(Uri, int) 等方法(参考Intent.java), 才能使权限跨重启。
另一种特例,如果授权方被清除数据了,同样会移除掉权限,需要特别谨慎处理
Uri Permission or exported ContentProvider?
完全exported ContentProvider的风险在于,可以不受任何限制调用ContentProvider, 如果对应的文件获取方法没有处理好访问权限控制则很容易造成隐私数据泄漏,因此需要非常严谨的处理文件读取接口,常见的方式是限制可访问文件路径,如要求file path必须是cache目录等。
Uri Permission的问题在于,如果要持久化已授予的权限,被授权方需要处理takePersistableUriPermission等,同时,需要处理授权方清除数据问题。
综合来看: 如果权限都是临时的,则通过Uri Permission比较合适,如果是持久的,则Exported ContentProvider, 并且限制文件路径的方式会更好。