总结整理了一下android权限相关的知识,分享一下:
uses-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
permission标签
<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:权限的等级
- normal 低风险权限,只要申请了就可以使用(在AndroidManifest.xml中添加<uses-permission>标签),安装时不需要用户确认;
- dangerous 高风险权限,安装时需要用户的确认才可使用;
- signature 只有当申请权限的应用程序的数字签名与声明此权限的应用程序的数字签名相同时(如果是申请系统权限,则需要与系统签名相同),才能将权限授给它;
- signatureOrSystem 签名相同,或者申请权限的应用为系统应用(在system image中),与signature类似,只是增加了rom中自带的app的声明 ,尽量不要使用该选项,因为signature已经适合绝大部分的情况。
对于普通和危险级别的权限,我们称之为低级权限,应用申请即授予。其他两级权限,我们称之为高级权限或系统权限。当应用试图在没有权限的情况下做受限操作,应用将被系统杀掉以警示。系统应用可以使用任何权限。权限的声明者可无条件使用该权限。
下面通过指定一个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标签
<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 就不行。
permission-group标签
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属性,命名这个权限可以控制访问此组件。
- Activity 权限 (使用<activity>标签) 限制能够启动与Activity权限相关联的组件或应用程序。在Context.startActivity()和Activity.startActivityForResult()期间检查;
- Service 权限(应用<service>标签)限制启动、绑定关联服务的组件或应用程序。此权限在Context.startService(),Context.stopService() 和 Context.bindService() 期间要经过检查;
- BroadcastReceiver 权限(应用<receiver>标签)能够为相关联的接收者组件或应用程序设置限制。在 Context.sendBroadcast() 返回后此权限将被检查,同时系统设法将广播递送至相关接收者。因此,权限失败将会导致抛回给调用者一个异常;它将不能递送到目的地。为了接收你的广播,你请求一个接收器的应用程序必须持有要求的那个权限(如上面例子所示),而且sendBroadcast的相关几个函数中也可以加入permission参数用来指定只带有该permission的接受者可以接受该广播。
- ContentProvider 权限(使用 <provider> 标签)用于限制能够访问 ContentProvider 中的数据的组件或应用程序。如果调用者没有请求权限,那么会为调用抛出一个安全异常( SecurityException )。数据库本身的读写可以处理多线程问题,但是数据的先后可以考虑同步问题,设置android:multiprocess=”true”属性来保证数据的正确性
对于组件而言,除了使用权限限制与外部交互的实体外,还有一个属性也具有该功能,那就是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相关函数
- 如果传入的 permission 名称为 null ,那么返回PackageManager.PERMISSION_DENIED 。
- 判断调用者uid是否符合要求 。
- 如果uid为0,说明是root权限的进程,对权限不做控制。
- 如果uid为system server进程的uid,说明是system server,对权限不作控制。
- 如果是 ActivityManager 进程本身,对权限不作控制。
- 如果调用者uid与参数传入的req uid不一致,那么返回PackageManager.PERMISSION_DENIED。
- 首先它通过调用getUserIdLP,去PackageManagerService.Setting.mUserIds数组中,根据uid查找uid(也就是package)的权限列表。一旦找到,就表示有相应的权限。
- 如果没有找到,那么再去PackageManagerService.mSystemPermissions中找。这些信息是启动时,从/system/etc/permissions/platform.xml中读取的。这里记录了一些系统级的应用的 uid 对应的 permission 。
- 返回结果 。
- 如果uid为0,说明是root用户,那么不控制权限。
- 否则,在ActivityManagerService维护的mGrantedUriPermissions这个表中查找这个uid是否含有这个权限,如果有再检查其请求的是读还是写权限。
URI权限
到目前为止我们讨论的标准的permission系统对于content provider来说是不够的。一个content provider可能想保护它的读写权限,而同时与它对应的直属客户端也需要将特定的URI传递给其它应用程序,以便其它应用程序对该URI进行操作。一个典型的例子就是邮件程序处理带有附件的邮件。进入邮件需要使用permission来保护,因为这些是敏感的用户数据。然而,如果有一个指向图片附件的URI需要传递给图片浏览器,那个图片浏览器是不会有访问附件的权利的,因为他不可能拥有所有的邮件的访问权限。 针对这个问题的解决方案就是per-URI permission:当启动一个activity或者给一个activity返回结果的时候,呼叫方可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION ([另外一篇intent博客中也有介绍到](http://blog.csdn.net/self_study/article/details/48055011))。这会使接收该intent的activity获取到进入该Intent指定的URI的权限,而不论它是否有权限进入该intent对应的content provider。 这种机制允许一个通常的capability-style模型,这种模型是以用户交互(如打开一个附件, 从列表中选择一个联系人)为驱动,特别获取fine-grained permissions(更细粒化的权限)。这是一种减少不必要权限的重要方式,这种方式主要针对的就是那些和程序的行为直接相关的权限。 这些URI permission的获取需要content provider(包含那些URI)的配合。强烈推荐在content provider中提供这种能力,并通过android:grantUriPermissions或者<grant-uri-permissions>标签来声明支持。总结
只要signature相同,就算不显式声明<ues-permission>也能access设定了normal或dangerous权限设定的数据或功能。
拥有system级别权限的使用者可以access其他普通signature权限声明设定过的功能。所以,设定为拥有system级别权限即可。
应用程序安装的时候,应用程序请求的permissions是通过package installer来批准获取的。package installer是通过检查该应用程序的签名来确定是否给予该程序request的权限。在用户使用过程中不会去检查权限,也就是说要么在安装的时候就批准该权限,使其按照设计可以使用该权限;要么就不批准,这样用户也就根本无法使用该feature,也不会有任何提示告知用户尝试失败。
例如高级权限用有system级别权限设定的api时,需要使其apk拥有system权限。比如在 android 的API中有供给SystemClock.setCurrentTimeMillis()函数来修改系统时间。有两个方法:
第1个方法简单点,不过需要在Android系统源码的情况下用make来编译:
- 在应用程序的AndroidManifest.xml中的manifest节点中插手android:sharedUserId=”android.uid.system”这个属性。
- 修改Android.mk文件,插手LOCAL_CERTIFICATE := platform这一行
- 使用mm命令来编译,生成的apk就有修改系统时间的职权范围了
第2个方法麻烦点,不过不用开虚拟机跑到源码环境下用make来编译:
- 同上,插手android:sharedUserId=”android.uid.system”这个属性。
- 使用eclipse编译出apk文件,但是这个apk文件是不能用的。
- 用压缩软件打开apk文件,删掉META-INF目录下的CERT.SF和CERT.RSA两个文件。
使用目标系统的platform密钥来重新给apk文件签名。这步比较麻烦,首先找到密钥文件,在我的Android源码目录中的位置是”build/target/product/security”,下面的platform.pk8和platform.x509.pem两个文件。然后用Android提供的Signapk工具来签名,signapk的源代码是在”build/tools/signapk”下,用法为”signapk platform.x509.pem platform.pk8 input.apk output.apk”,文件名最好使用绝对路径防止找不到,也可以修改源代码直接使用。
PS:6.0版本权限修改
转载于:https://github.com/k0shk0sh/PermissionHelper
Android 6.0 相比之前的Android版本有一个很大的不同点,就是动态获取权限。
说到拨号,一个 Intent 就搞定,代码如下:
private void callDirectly(String mobile){ Intent intent = new Intent(); intent.setAction("android.intent.action.CALL"); intent.setData(Uri.parse("tel:" + mobile)); mContext.startActivity(intent); }
当然 你可别忘了在 Manifest 文件中去声明拨号的权限
<uses-permission android:name="android.permission.CALL_PHONE" />
如果在 Android 6.0 以前的设备上,上面的代码都是没有问题的,但是如果是在 Android 6.0 设备上,并且项目的 targetSdkVersion 你设置的是23,那么 当你执行上面的拨号代码时,程序将会奔溃掉。
此时你肯定想到了 如果 targetSdkVersion 值设置的小于23是不是就不会奔溃了,恩,确实如此, 此时即使使用Android6.0的设备,程序也不会奔溃,原因显而易见,Android 的权限机制是 Android M 后才加入的。从 Android M 开始 应用程序申请的权限是在运行时动态赋给用户的。
权限动态分配
在 Android6.0 之前,下载好一个应用程序,点击安装我们看到的大都是像这样的界面。
上图分别是Nexus6和小米手机在安装软件时的界面。
在安装时你会发现,手机操作系统会提示,这个软件会索要了你手机的那些权限,并且给用一个列表进行展示,但是这些提示只是在安装是提示,只要你点击接受或者安装, 表示你允许这个应用在可以获取它申明的所有权限。一般很少有人在安装时,会因为看到某个应用因为申请了某一个敏感权限而放弃安装应用。因为这个权限虽然敏感, 但是对于当前的用户是不可感知的,因为他现在并没有立即去查看你的最近通话、短信记录…
说到这里,我们自然而然的会想到,其实最好的方式是,当这个应用在用户使用过程中,正准备使用某个权限时,比如说读取短信列表,系统能及时的弹出一个提示框,说这个应用要读取您的短信内容, 您是否允许。然后用户结合当前应用的执行动作,依据当前条件判断,是不是应该授予应用读取短信记录的权限。这绝对的最完美的。 因为在具体的使用过程中,用户可以结合当前应用的使用场景,去思考、判断是不是应该给这个应用相应的权限。不给能怎样,给了会怎样, 这样对用户而言,完全是主动的,相比安装时那种选择,这样的做法无疑是对用户莫大的尊重,同时这也保证了用户的个人隐私。
说到这里,不得不插一句,其实 MIUI 早就实现了这个系统特性,在这一点上 MIUI 确实走到了 Android团队的前面,恩,给 MIUI 点个赞。
然而直到 Android 6.0 这个版本开始,上面的假设终于得到了谷歌的实践,除了在应用安装时,操作系统会提示应用会获取那些权限,在运行过程中,当应用去真的获取一些敏感 权限时,系统还会弹出一个提示框,询问用户是不是授予应用相应的权限。如下图所示。
这就是 Android 6.0 的运行时权限检查机制。下面是Google官方对此的解释,只截取介绍部分
Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app. This approach streamlines the app install process, since the user does not need to grant permissions when they install or update the app. It also gives the user more control over the app’s functionality; for example, a user could choose to give a camera app access to the camera but not to the device location. The user can revoke the permissions at any time, by going to the app’s Settings screen.
解决方案
其实上面已经说了一种取巧的方案,将 targetSdkVersion 设为小于23的值,程序将不会奔溃, 但是在Android 6.0 上你的应用程序依旧拨不了电话,这是真的。所以要想兼容6.0版本,必须通过下面的方式进行代码层面的兼容。
对Android版本做判断,然后对Android 6.0 做特殊处理,代码如下
final public static int REQUEST_CODE_ASK_CALL_PHONE = 123; public void onCall(String mobile){ this.mMobile = mobile; if (Build.VERSION.SDK_INT >= 23) { int checkCallPhonePermission = ContextCompat.checkSelfPermission(mContext,Manifest.permission.CALL_PHONE); if(checkCallPhonePermission != PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(mContext,new String[]{Manifest.permission.CALL_PHONE},REQUEST_CODE_ASK_CALL_PHONE); return; }else{ //上面已经写好的拨号方法 callDirectly(mobile); } } else { //上面已经写好的拨号方法 callDirectly(mobile); } }
此时,如果一个Android6.0的用户触发拨号动作,执行上面的代码,那么他将会看到一个很好看的MaterialDialog,如下图所示。
那么用户点击拒绝或者允许,我们怎么才能拿到回调呢,如果能拿到回调,我们就可以根据用户的选择来执行不同的操作了。
这里应该会看到在 ActivityCompat 的 requestPermissions 方法中,最后一个参数是一个requestCode,看到它自然而然想到了经常用到的onActivityResult, 这里当执行 ActivityCompat 的 requestPermissions 方法后有一个回调机制,需要我们在当前 Activity 中实现 onRequestPermissionsResult 这个方法,具体如下
@Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { switch (requestCode) { case REQUEST_CODE_ASK_CALL_PHONE: if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { // Permission Granted callDirectly(mobile); } else { // Permission Denied Toast.makeText(MainActivity.this, "CALL_PHONE Denied", Toast.LENGTH_SHORT) .show(); } break; default: super.onRequestPermissionsResult(requestCode, permissions, grantResults); } }
这里会对提供了一个对用户点击做判断的入口,开发者可以根据 grantResults[0] 的类型,来判断用户点击的是允许还是拒绝,接着就可以执行相应的逻辑了。
关于AndroidM上权限的动态获取,这里只给出了一个最简单的示例,如果你还没有尽兴,那么下面这篇国外的博文,一定会让你满足。
Everything every Android Developer must know about new Android’s Runtime Permission
这篇英文博文内容很长、内容也比较多,十足的干货。您慢用~
后记:偶然发现已经有哥们把上面的这篇文章做了翻译,真是极好的,附上翻译链接,给翻译者同学点赞,辛苦!
另外,最近看到一个Github上的开源项目 PermissionHelper ,专门用于处理 Android 6.0 的权限兼容问题。
引用文章:
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
- 3楼u0109276402小时前
- 抢了一个沙发,嘿嘿。
- 2楼u010927640昨天 23:32
- 抢了一个沙发,嘿嘿。
- 1楼u010927640昨天 23:31
- Android,刚开始学习,感谢楼主分享。
- Re: zhao_zepeng昨天 23:32
- 回复u010927640n这么晚了,还在抢沙发。。。。。