Android给每一个APK进程分配一个单独的空间,manifest中的userid就是对应一个分配的Linux用户ID,而且为它建立一个沙箱,以防止影响其余应用程序(或者被其余应用程序影响)。java
一般,不一样的APK会具备不一样的userId,所以运行时属于不一样的进程中,而不一样进程中的资源是不共享的(好比只能访问/data/data/本身包名下面的文件),保障了程序运行的稳定。而后在有些时候,咱们本身开发了多个APK而且须要他们之间互相共享资源,那么就须要经过设置shareUserId来实现这一目的。android
经过Shared User id,拥有同一个User id的多个APK能够配置成运行在同一个进程中,能够互相访问任意数据。也能够配置成运行成不一样的进程, 同时能够访问其余APK的数据目录下的数据库和文件,就像访问本程序的数据同样(使用IPC机制,不一样进程之间,好比AIDL)。
数据库
前面说了,Android中每一个app都对应一个uid,每一个uid都有本身的一个沙箱,这是基于安全考虑的,那么说到沙箱,咱们会想到的是data/data/XXXX/目录下面的全部数据,由于咱们知道这个目录下面的全部数据是一个应用私有的,通常状况下其余应用是没有权限访问的,固然root以后是另外状况,这里就很少说了。这里只看没有root的状况,下面咱们在来看一个场景:安全
A应用和B应用都是一家公司的,如今想在A应用中可以拿到B应用存储的一些值,那么这时候该怎么办呢?app
这时候就须要用到了shareUserId属性了,可是这里咱们在介绍shareUserId属性前,咱们先来看一个简单的例子:ui
仍是使用上面的两个工程:google
ShareUserIdPlugin中的MainActivity.java代码以下:插件
这里很简单,咱们使用SharedPreferences来存储一个密码,注意模式是:Context.MODE_PRIVATE,關於模式後面會詳細介紹。orm
Context提供了几种模式:xml
一、Context.MODE_PRIVATE:为默认操做模式,表明该文件是私有数据,只能被应用自己访问,在该模式下,写入的内容会覆盖原文件的内容,若是想把新写入的内容追加到原文件中。可使用Context.MODE_APPEND
二、Context.MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,不然就建立新文件。
三、Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用来控制其余应用是否有权限读写该文件。
MODE_WORLD_READABLE:表示当前文件能够被其余应用读取;
MODE_WORLD_WRITEABLE:表示当前文件能够被其余应用写入
其余應用讀取該應用目錄下此配置文件中passwd确定是失敗的。
若是咱们想让A应用访问到B应用的数据,咱们能够这么作:把B应用建立模式改为可读模式的,那么A应用就能够操做了,那么这就有一个问题,A应用能够访问了,其余应用也能够访问了,这样全部的应用均可以访问B应用的沙盒数据了,太危险了。
因此要用另外的一种方式,那么这时候就要用到shareUserId属性了,咱们只须要将B应用建立方式仍是private的,而后A应用和B应用公用一个uid便可,咱们下面就来修改一下代码,仍是前面的那两个工程,修改他们的AndroidManifest.xml,添shareUserId便可。
这时候,咱们发现把代碼中的模式改为private的,A应用任然能够访问数据了,其实也好理解,他们两个的uid都相同了,A的文件就是B的,B的就是A的了,他们两个没有沙盒的概念了,数据也是透明的了。
因此这里咱们就看到了,使用shareUserId能够达到多个应用之间的数据透明性互相访问。
注意:这里有一个误点,就是这里全部的修改的前提是这个应用的AndroidManifest.xml自己就定义了这个shareUserId,而后咱们能够反编译看到这个值,把咱们本身的shareUserId改为他的就能够了,可是若是这个应用自己没有这个属性,那么这里就没有办法的,为何呢,若是要添加,那就是另一条路了,就是逆向,修改AndroidManifest.xml以后,还须要重新打包在验证,可是这时候不必了,咱们也知道有时候回编译仍是很艰难的,若是都能回编译了,那都不须要这些工做了,因此这里须要注意的一个前提。
那么修改以后是否是真的能够呢?
答案是确定不能够的,若是能够的话,那google也太傻比了,其实Android系统中有一个限制,就是说若是多个应用的uid相同的话,那么他们的apk签名必须一致,否则是安装失败的,以下错误:
经过上面的分析,咱们就知道了,Android中是不容许相同的uid的不一样签名的应用。
(1)在AndroidManifest.xml中添加android:sharedUserId="android.uid.system"
(2)在Android.mk文件里面添加LOCAL_CERTIFICATE := platform(使用系统签名)
(3)在源码下面进行mm编译
这样生成的apk可以获取system权限,能够在任意system权限目录下面进行目录或者文件的建立,以及访问其余apk资源等(注意建立的文件(夹)只有建立者(好比system,root除外)拥有可读可写权限-rw-------)。
系统中全部使用android.uid.system做为共享UID的APK,都会首先在manifest节点中增长android:sharedUserId="android.uid.system",而后在Android.mk中增长LOCAL_CERTIFICATE := platform。能够参见Settings等
系统中全部使用android.uid.shared做为共享UID的APK,都会在manifest节点中增长android:sharedUserId="android.uid.shared",而后在Android.mk中增长LOCAL_CERTIFICATE := shared。能够参见Launcher等
系统中全部使用android.media做为共享UID的APK,都会在manifest节点中增长android:sharedUserId="android.media",而后在Android.mk中增长LOCAL_CERTIFICATE := media。能够参见Gallery等。
在说这个知识点以前,咱们须要了解的一个知识点,就是咱们能够经过一个包名来获得对应的Context的全局变量,能够直接使用Context的一个静态方法:createPackageContext
关于这个方法其实很简单,他有两个参数:
第一个参数:须要构造出来Context的包名字符串
第二个参数:构造出来的Context的开启模式
下面咱们能够直接使用一个例子来看看效果:
首先咱们弄一个插件工程:ShareUserIdPlugin
这个工程很简单,咱们编译安装运行便可。
在弄一个宿主工程:ShareUserIdHost
这里有一个核心方法,咱们首先经过插件工程的包名:cn.wjdiankong.shareuseridplugin;建立出一个Context对象。
这里看到第二参数有两个模式:
Context.CONTEXT_INCLUDE_CODE:这个标志是在咱们须要执行插件中的某段代码须要加上的值。(下面代碼缺失會发现报错了,找不到指定的类。因此若是想运行代码的话,这个值必定要加上。)
CONTEXT_IGNORE_SECURITY:这个标志是必须的,是忽视安全性,若是没有这个值的话,那么咱们访问什么都是失败的。(下面代碼缺失報安全错误)
获得了Context变量以后,咱们下面就能够经过反射来执行代码和获取资源了,这里须要注意的是,必定要先拿到Context对应的ClassLoader,而后才能加载对应的类,ClassLoader必定是Context的,是插件工程中的类加载器。
下面咱们运行结果看看:
运行成功了啦~~是否是很简单呢。