Android 跨应用调用组件与Uid机制

引言:本篇文章主要说明四大组件与Application间调用的一些条件注意点,包括Activity、Service等组件能够被外界访问的条件等。关于四大组件特别是Service、ContentProvider和BroadcastReceiver需要注意的地方,可以查看本人另一篇文章:查漏补缺(三):重温Service、BoardcastReceiver和ContentProvider。至于为什么没有细讲Activity,因为看过Activity生命周期以及其启动方式的读者对于Activity已经比较熟悉了,这里重点是讲讲一些细节上的东西。

一、问题描述

  1. 在开发一个App过程中,activityA通过Intent启动activityB,有显式启动和隐式启动两种方式,而官方的文档中说startActivity可能会报NotFoundException,表示被start的Activity不存在。因此,我们很容易忽略另一个可能的Exception,Permission Denial。
  2. 开发多个App时,想要让AppB去调用AppA中的Activity(如:startActivity()方式),也许你什么Exception都不会得到,也可能会直接Force Close掉。

二、问题原因

因为Start Activity时,代码是有去检验permission的。

如下情况,可以成功startActivity而不会得到permission denial

  1. 同一个application下
  2. Uid相同
  3. permission匹配
  4. 目标Activity的属性Android:exported=”true”
  5. 目标Activity具有相应的IntentFilter,存在Action动作或其他过滤器并且没有设置exported=false
  6. 启动者的Pid是一个系统服务(System Server)的Pid【也就是系统服务前来调用普通App的Activity等】
  7. 启动者的Uid是一个System Uid(Android规定android.system.uid=1000,具有该Uid的application,我们称之为获得Root权限)

如果上述调节,满足一条,一般即可(与其他几条不发生强制设置冲突),否则,将会得到Permission Denial的Exception而导致Force Close。

三、Uid机制

我们知道,Pid表示<u>进程ID</u>,Uid表示<u>用户ID</u>,只是Android和计算机不一样,计算机每个用户都具有一个Uid,哪个用户start的程序,这个程序的Uid就是那个那个用户,而Android中每个程序都有一个Uid,默认情况下,Android会给每个程序分配一个<u>普通级别互不相同的Uid</u>,如果用互相调用,只能是Uid相同才行,这就使得共享数据具有了一定安全性,每个软件之间是不能随意获得数据的。而同一个application只有一个Uid,所以application下的Activity之间不存在访问权限的问题。

让你的App将它里面含有的某些activity、service、provider等的数据进行共享

  • 法一:完全暴露。这就是android:exported=”true”的作用,而一旦设置了intentFilter之后,exported就默认被设置为true了,除非再强制设为false。当然,对那些没有intentFilter的程序体,它的exported属性默认仍然是false,也就不能共享出去。

  • 法二:权限提示暴露。这就是为什么经常要设置<uses-permission name="xxxxx"/>的原因,如果人家设置了android:permission=”xxx.xxx.xx”那么,你就必须在你的App的AndroidManifast.xml中uses-permission xxx.xxx.xx才能访问人家的东西。【下面以自定义权限的用法来作为示例,而关于系统权限大全,可以查看本人的另一篇文章:Android字典(一) — permission权限说明。】

    • <permission>格式
<!--自定义权限格式-->
<permission android:description="string resource"  //权限描述(描述权限)
          android:icon="drawable resource"  //权限图标(描述权限)
          android:label="string resource"  ///权限标签(描述权限)
          android:name="string"  //权限名称(描述权限)
          android:permissionGroup="string"
         //权限组。此属性是可选的,被用于协助系统向用户显示权限,一般会像(`android:permissionGroup="android.permission-group.SYSTEM_TOOLS" `)这个一样设置为标准系统组,很少自定义,最好使用已经定义的,使用起来也方便。
          android:protectionLevel=["normal" | "dangerous" | "signature" | "signatureOrSystem"]
         //权限级别。必须声明,用于告诉系统当前应用进行访问控制,例如对于:如网络访问(需付费)以及获取联系人(涉及隐私)等。
         //normal:低风险权限,只要申请了就可以使用(在AndroidManifest.xml中添加<uses-permission>标签),安装时不需要用户确认;
         //dangerous:高风险权限,安装时需要用户的确认才可使用;
         //signature:只有当申请权限的应用程序的数字签名与声明此权限的应用程序的数字签名相同时(如果是申请系统权限,则需要与系统签名相同),才能将权限授给它;
         //signatureOrSystem:签名相同,或者申请权限的应用为系统应用(在system image中)。
/>
* 用法步骤:
  首先在App中的androidManifast.xml文件中`<application>`标签内部定义了自己的`<permission>`,然后在`<application>`标签之外使用`<uses-permission>`来声明需要的权限,再让自己的`<activity>`或者`<receiver>`等组件添加上`android:permission="你刚刚自定义的权限名称"`来使你的组件拥有被调用时检测对方App有没有相应权限的能力。
  • 法三:私有暴露。使用sharedUserId。在Android里面每个app都有一个唯一的linux user ID,则这样权限就被设置成该应用程序的文件只对该用户可见,只对该应用程序自身可见,而我们可以使他们对其他的应用程序可见,这会使我们用到SharedUserId,也就是让两个apk使用相同的userID,这样它们就可以看到对方的文件。为了节省资源,具有相同ID的apk也可以在相同的linux进程中进行(注意,并不是一定要在一个进程里面运行),共享一个虚拟机。
      假如说一个公司做了两个产品,只想这两个产品之间可互相调用,那么这个时候就必须使用shareUserID将两个软件的Uid强制设置为一样的。这种情况下必须使用具有该公司签名的签名文档才能,如果使用一个系统自带软件的ShareUID,例如Contact,那么无须第三方签名。
    • 一般用法步骤:
      1. 第一个应用程序为的menifest文件代码如下:

<manifest xmlns:android=”http://schemas.android.com/apk/res/android
package=”com.abc.serviceID”
android:versionCode=”1″
android:versionName=”1.0″
android:sharedUserId=”com.abc.share”>
//…….
2. 第二个应用程序的menifest文件代码如下:
<manifest xmlns:android=”http://schemas.android.com/apk/res/android
package=”com.abc.clientID”
android:versionCode=”1″
android:versionName=”1.0″
android:sharedUserId=”com.abc.share”>

      3. 假设我们从package=“com.abc.clientID”的程序获取package="com.abc.serviceID"的程序的context:
      ```
Context context=this.createPackageContext("com.abc.serviceID",Context.CONTEXT_IGNORE_SECURITY);
      ```


#### Uid机制的意义在于:应用程序获得系统权限 

    如果一个activity是由system process跑出来的,那么它就可以横行霸道,任意权限,只是你无法开发一个第三方application具有系统的Pid(系统Pid不固定),但是你<u>完全可以开发一个具有系统Uid的程序,对系统中的所有程序任意访问,只需在AndroidManifest.xml中声明shareUserId为`android.uid.system`即可,生成的文件也必须经过高权限签名才行,一般不具备这种审核条件的application,google不会提供给你这样的签名文件。当然你是在编译自己的系统的话,想把它作成系统软件程序,只需在Android.mk中声明Certificate:platform则可以了,既采用系统签名。</u>这个系统Uid的获得过程,我们把它叫做获得Root权限的过程。所以很多第三方系统管理软件就是有Root权限的软件,因为他需要对系统有任意访问的权限。那么它的Root签名则需要和编译的系统一致,例如官方的系统得用官方的签名文件,CM的系统就得用CM的签名文件。至于Android.mk文件以及相关配置等,可以参考下http://dengzhangtao.iteye.com/blog/1750782 ,http://blog.sina.com.cn/s/blog_628cc2b70101dcai.html

#### 拓展:Android整个permission机制
 AndroidManifest.xml里面的sharedUserID能够让不同的apk运行在同一个进程里,分享里面的数据,比如Contacts等,当然这个sharedUserID可以设置成“android.uid.system”就可以运行在系统进程中,有权修改系统数据。
 但仅仅有着一个sharedUserID并不能够保证你的apk一定能运行成功,怎么办?签名啊。如果你有Android的源码就比较方便了,直接把Android.mk里面的LOCAL_CERTIFICATE 赋值为platform就行了。然后mm编辑,就能安装了。因为在安装的时候,PackageManager会检查,如果sharedUserId是system的,它会看这个apk的签名是不是system.crt,如果不是,会报出permission deny的error。而把LOCAL_CERTIFICATE改成platform就等于给APK签名。
 进而可以通过这个问题研究一下整个Android permission的机制。系统的安全机制通过给每个用户分配单独的uid和gid来实现,Android系统中pid代表进程ID,这个是有系统在程序运行时分配的,这一点可以防止地址空间的数据共享,增强内存空间的安全性。对于外部则用到了uid进行封锁。
 系统会给于用户进程单独的uid,当然系统也是要运行进程的,比如System,Radio,蓝牙,IO设备。系统中的init.rc文件会详细定义这些文件的权限。Android中对uid的定义是Root最高,其次是system,最低的是app。这是基于Linux系统的结果。
 那么在APP里,要对一些进程进行访问,或者接受Broadcast,或者启动Activity、Service都是需要权限的,不能说你的app什么都能做,这也是需要在manifest file中设置的。
 比如在startActivity时,如果你start自己apk里的activity,它们会在同一个application下,那么自然也就使用一个uid,start过程自然没有什么问题。如果你需要start别人写的Activity或者service,都需要用到同一个shareUserId才行,因为在ActivityManagerService要启动activity的之前,会首先检查uid,用checkPermission方法,透过binder获得pid和uid,检查你activity的binder的权限,如果你有权限则已,没权限的话就会抛出security exception。至于broadcast,检查则更为严格,会双向的检查发出者和接受者的权限。
    原文作者:androidjp
    原文地址: https://www.jianshu.com/p/ad01ac11b4f1
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞