总结整理了一下android权限相关的知识,由于篇幅过长,分为两篇博客来写,上篇博客主要是详解权限和安全,下篇主要是介绍android6.0权限适配问题:
android permission权限与安全机制解析(下)
用法为<uses-permission android:name=”string” android:maxSdkVersion=”integer”/>
为了保证application的正常运行,需要系统授予app的权限声明。这个权限是在用户安装应用的时候授予的。
android:name的值可以是其他app通过<permission>声明的(用于两个应用之间的交互),也可以是系统的权限名称,例如
android.permission.CAMERA或android.permission.READ_CONTACTS等等。需要注意的一点是uses-permission的权限要求说明,可能会引起app在Android Market中的过滤。
android:maxSdkVersion用来标注该权限所支持的最大api版本号,如果当从某个特定版本时,不需要该权限时就可以加上该限制。
系统权限列表有很多,各自的用途也不一样,有几个链接可以参考一下:
http://blog.csdn.net/zjl5211314/article/details/6595080
http://developer.android.com/reference/android/Manifest.permission.html
http://developer.android.com/guide/topics/security/permissions.html
http://developer.android.com/guide/topics/manifest/uses-permission-element.html
<permission android:description=”string resource”
android:icon=”drawable resource”
android:label=”string resource”
android:name=”string”
android:permissionGroup=”string”
android:protectionLevel=[“normal” | “dangerous” |
“signature” | “signatureOrSystem”] />
android:description:对权限的描述,比lable更加的详细,介绍该权限的相关使用情况,比如当用户被询问是否给其他应用该权限时。注意描述应该使用的是string资源,而不是直接使用string串。
android:icon:用来标识该权限的一个图标。
android:label:权限的一个给用户展示的简短描述。方便的来说,这个可以直接使用一个string字串来表示,但是如果要发布的话,还是应该使用string资源来表示。
android:name:权限的唯一名字,由于独立性,一般都是使用包名加权限名,该属性是必须的,其他的可选,未写的系统会指定默认值。
android:permissionGroup: 权限所属权限组的名称,并且需要在这个或其他应用中使用<permission-group>标签提前声明该名称,如果没有声明,该权限就不属于该组。
android:protectionLevel:权限的等级
对于普通和危险级别的权限,我们称之为低级权限,应用申请即授予。其他两级权限,我们称之为高级权限或系统权限。当应用试图在没有权限的情况下做受限操作,应用将被系统杀掉以警示。系统应用可以使用任何权限。权限的声明者可无条件使用该权限。
下面通过指定一个BroadcastReceiver的权限来实验,首先创建了两个app:app A ,app B 。app A中注册了一个BroadcastReceiver ,app B 发送消息,app A的manifest文件:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.testbutton" android:versionCode="1" android:versionName="1.0" >
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="15" />
<!-- 声明权限 -->
<permission android:name="com.example.testbutton.RECEIVE" />
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" launcheMode="singleTask" android:configChanges="locale|orientation|keyboardHidden" android:screenOrientation="portrait" android:theme="@style/android:style/Theme.NoTitleBar.Fullscreen" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<!-- 注册Broadcast Receiver,并指定了给当前Receiver发送消息方需要的权限 -->
<receiver android:name="com.example.testbutton.TestButtonReceiver" android:permission="com.example.testbutton.RECEIVE" >
<intent-filter>
<action android:name="com.test.action" />
</intent-filter>
</receiver>
</application>
</manifest>
app B 的manifest 文件内容
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.testsender" android:versionCode="1" android:versionName="1.0" >
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="15" />
<!-- 声明使用指定的权限 -->
<uses-permission android:name="com.example.testbutton.RECEIVE" />
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" android:label="@string/title_activity_main" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
这样app B 给app A 发送消息,A就可以收到了,若未在app B的manifest文件中声明使用相应的权限,app B发送的消息,A是收不到的。 另外,也可在app B 的manifest文件中声明权限时,添加android:protectionLevel=“signature”,指定app B只能接收到使用同一证书签名的app 发送的消息。
<permission-tree android:icon=”drawable resource”
android:label=”string resource”
android:name=”string” />
该标签包含于在< manifest >标签中。
该标签声明权限树的基础名称。 应用程序拥有树中的所有名称。 可以通过调用 PackageManager.addPermission() 在权限树中动态添加新的权限。 树中的名称以句点(’.’)分隔。 比如,假定基础名称为com.example.project.taxes,则可加入类似以下格式的权限:
com.example.project.taxes.CALCULATE
com.example.project.taxes.deductions.MAKE_SOME_UP
com.example.project.taxes.deductions.EXAGGERATE
注意本元素并不是声明权限,而只是为后续要加入的权限定义一个命名空间。
android:icon
代表树中所有权限的图标。 本属性必须设为对 Drawable 资源的引用。
android:label
供用户阅读的权限组名称。 为了方便起见可以将其直接设为字符串, 但在应用程序准备发布时,应该设为对字符串的引用,以便对其进行本地化。
android:name
权限树的基础名称,用作树中所有权限的前缀。 为了保证名称的唯一性,应该采用 Java 风格的域名规则。 名称的路径必须至少包含两个句点分割的字段 — 比如:com.example.base 可以,但 com.example 就不行。
Android在定义 permission 时, 为每个Permission都进行了分组,一个权限主要包含三个方面的信息:权限的名称;属于的权限组;保护级别。一个权限组是指把权限按照功能分成的不同的集合。每一个权限组包含若干具体 权限,例如在 COST_MONEY 组中包含 android.permission.SEND_SMS , android.permission.CALL_PHONE 等和费用相关的权限。具体如下SDK所示:
http://developer.android.com/reference/android/Manifest.permission_group.html
再来看看源码(在frameworks/base/core/res /AndroidManifest.xml):
<!-- Used for permissions that can be used to make the user spend money without their direct involvement. For example, this is the group for permissions that allow you to directly place phone calls, directly send SMS messages, etc. -->
<permission-group android:name="android.permission-group.COST_MONEY" android:label="@string/permgrouplab_costMoney" android:description="@string/permgroupdesc_costMoney" />
<!-- Allows an application to send SMS messages. -->
<permission android:name="android.permission.SEND_SMS" android:permissionGroup="android.permission-group.COST_MONEY" android:protectionLevel="dangerous" android:label="@string/permlab_sendSms" android:description="@string/permdesc_sendSms" />
<!-- Allows an application to initiate a phone call without going through the Dialer user interface for the user to confirm the call being placed. -->
<permission android:name="android.permission.CALL_PHONE" android:permissionGroup="android.permission-group.COST_MONEY" android:protectionLevel="dangerous" android:label="@string/permlab_callPhone" android:description="@string/permdesc_callPhone" />
可以看到,这边先定义了一个permissiongroup : android.permission-group.COST_MONEY, 然后又定义了两个permission :android.permission.SEND_SMS 和 android.permission.CALL_PHONE , 需要注意的是,这两个权限中都有一个android:permissionGroup属性,这个属性就指定了这个权限所属的PermissionGroup。
android:description
这个属性用于给权限组定义一个用户可读的说明性文本。这个文本应该比标签更长、更详细。这个属性不应该直接使用字串,而应该使用字符串引用。
android:icon
这个属性定义了一个代表权限的图标,这个属性应该使用资源文件来定义。
android:label
这个属性给权限组定义了一个用户可读的名称。
android:name
这个属性定义了权限组的名称,它是能够分配给<permission>元素的permissionGroup属性的名称。
Android是一个权限分离的系统,这是利用Linux已有的权限管理机制,通过为每一个Application分配不同的uid和gid,从而使得不同的Application之间的私有数据和访问(native以及java层通过这种sandbox机制,都可以)达到隔离的目的 。与此同时,Android 还在此基础上进行扩展,提供了permission机制,它主要是用来对Application 可以执行的某些具体操作进行权限细分和访问控制,同时提供了per-URI permission 机制,用来提供对某些特定的数据块进行ad-hoc方式的访问。
通过AndroidManifest.xml文件可以设置高级权限,以限制访问系统的所有组件或者使用应用程序。所有的这些请求都包含在你所需要的组件中的 android:permission属性,命名这个权限可以控制访问此组件。
对于组件而言,除了使用权限限制与外部交互的实体外,还有一个属性也具有该功能,那就是android:exported,这个属性用于指示该服务是否能够被其他应用程序组件调用或跟它交互。如果设置为true,则能够被调用或交互,否则不能。设置为false时,只有同一个应用程序的组件或带有相同用户ID的应用程序才能启动或绑定该服务。
它的默认值依赖于该服务所包含的过滤器。没有过滤器则意味着该服务只能通过指定明确的类名来调用,这样就是说该服务只能在应用程序的内部使用(因为其他外部使用者不会知道该服务的类名),因此这种情况下,这个属性的默认值是false。另一方面,如果至少包含了一个过滤器,则意味着该服务可以给外部的其他应用提供服务,因此默认值是true。
首先是root用户和system用户拥有所有的接口调用权限,然后对于其它用户可以使用以下这几个函数来检测 :
PackageManager.checkPermission (String permName, String pkgName)
用来检测一个package中是否授予了指定permission。
Context.checkCallingOrSelfPermission (String permission)
用来检测自己或者调用进程中是否授予了指定permission。
Context.checkCallingOrSelfUriPermission (Uri uri, int modeFlags)
用来检测自己或者调用进程中是否授予了一个uri通过modeFlags指定的permission。
Context.checkCallingPermission (String permission)
检查正在处理的调用者进程是否授予指定permission 权限,如果调用者是自己那么返回 。
Context.checkCallingUriPermission (Uri uri, int modeFlags)
用来检测调用进程中是否授予了一个uri通过modeFlags指定的permission。
Context.checkPermission (String permission, int pid, int uid)
用来检测指定uid和pid的进程中是否授予了指定的permission。
checkSelfPermission (String permission)
23版本api添加,用来检测自己是否授予了指定permission
Context.checkUriPermission (Uri uri, int pid, int uid, int modeFlags)
用来检测指定uid和pid的进程中是否授予了一个uri通过modeFlags指定的permission。
Context.checkUriPermission (Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags)
比上面的方法多了一个检测permission的功能,相当于同时调用checkPermission(String, int, int)和checkUriPermission(Uri, int, int, int)。
-------------
下面这一组和上面一一对应,区别就在于如果遇到检查不通过时,会抛出异常,打印消息 :
Context.enforceCallingOrSelfPermission(String permission, String message)
Context.enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message)
Context.enforceCallingPermission (String permission, String message)
Context.enforceCallingUriPermission (Uri uri, int modeFlags, String message)
Context.enforcePermission (String permission, int pid, int uid, String message)
Context.enforceUriPermission (Uri uri, int pid, int uid, int modeFlags, String message)
Context.enforceUriPermission (Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags, String message)
-----------
Context.grantUriPermission (String toPackage, Uri uri, int modeFlags)
为指定package添加访问指定uri 的读或者写权限
Context.revokeUriPermission (Uri uri, int modeFlags)
为指定package删除访问指定uri 的读或者写权限。
以上函数中check开头的,只做检查。enforce开头的,不单检查,没有权限的还会抛出异常。
checkPermission相关函数
安装在设备中的每一个apk文件,Android给每个APK进程分配一个单独的用户空间,其manifest中的userid(userid的特点: 作为APK身份的标识 ;userid对应一个Linux用户,所以不同APK(用户)间互相访问数据默认是禁止的)就是对应一个Linux用户都会被分配到一个属于自己的统一的Linux用户ID,并且为它创建一个沙箱,以防止影响其他应用程序(或者其他应用程序影响它)。用户ID 在应用程序安装到设备中时被分配,并且在这个设备中保持它的永久性。所以让两个apk使用相同的userID,这样它们就可以看到对方的文件,如果在此基础之上再加上相同的android:process,他们就可以运行在一个进程中,能够做更多的事情了。为了节省资源,具有相同ID的apk也可以在相同的linux进程中进行(注意,并不是一定要在一个进程里面运行),共享一个虚拟机。
所以当两个应用公用一个userid的时候就可以使用SharedPreference之类的机制进行文件数据共享:
//第一个应用程序为的menifest文件代码如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.demo_A"
android:versionCode="1"
android:versionName="1.0"
android:sharedUserId="com.android.test"
>
//第二个应用程序的menifest文件代码如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.demo_B"
android:versionCode="1"
android:versionName="1.0"
android:sharedUserId="com.android.test"
>
上面给出了两个程序的menifest定义,两者共用一个ShareUserId,下面我们看看从一个程序里面如何获取另外一个程序的Context。假设我们从package=“com.android.demo_A”的程序获取package=”com.android.demo_B”的程序的context:
Context ct=this.createPackageContext("com.android.demo_B", Context.CONTEXT_IGNORE_SECURITY);
这样我们就能够获取到另外一个应用的Application context了,获取到上下文之后就能够实现数据共享和通信,具体可以查看我的 IPC通信博客。
第2个方法麻烦点,不过不用开虚拟机跑到源码环境下用make来编译:
http://blog.csdn.net/xyz_lmn/article/details/7372040
http://blog.csdn.net/t12x3456/article/details/7749200
http://yelinsen.iteye.com/blog/1012740
http://berdy.iteye.com/blog/1782854
http://my.oschina.net/u/589963/blog/316912
http://blog.csdn.net/vshuang/article/details/44001661
http://www.chawenti.com/articles/12078.html
http://ee.ofweek.com/2012-04/ART-8300-2808-28609680_4.html
http://blog.csdn.net/liujian885/article/details/5404834
http://maoruibin.github.io/%E6%8A%80%E6%9C%AF/2015/11/10/android_m_permission.html
https://www.zhihu.com/question/37317693
http://blog.csdn.net/wirelessqa/article/details/8581652