前面我们对RRO(Runtime Resources Overlay)的使用做了介绍,并且知道了它大概的原理。下面我们详细介绍其实现过程,先说两个名词:
target包:就是要被覆盖的包
overlay包:就是我们的资源包或者主题包等
PMS的处理比较简单,它会在开机的时候对所有的包做扫描,提取出包信息,如果有RRO对应的overlay包和target包,则会以local_socket的形式请求installd去做idmap。
先看PackageManagerService.java中的一些数据结构:
/** *这个常量就是系统定义的overlay package存放的路径,也就是前面我们要把demo里的overlay package push *到/vendor/overlay的原因。 */
private static final String VENDOR_OVERLAY_DIR = "/vendor/overlay";
//mOverlays用来存储overlay相关的包信息
final ArrayMap<String, ArrayMap<String, PackageParser.Package>> mOverlays =
new ArrayMap<String, ArrayMap<String, PackageParser.Package>>();
mOverlays的key是target包的包名。由于一个target包可以被多个overlay包覆盖,所以它的vaule是一个ArrayMap:key表示overlay package的packageName,value则是overlay package。这里就有个问题:如果一个target package被多个overlay package覆盖了,那么到底哪个overlay package 生效呢?这个我们可以通过android:priority="1"
来指定overlay package 的优先级,数字越大,优先级越高,取值范围0~9999。
我们知道在PackageManagerService构造的时候就回去扫描系统一些目录。这当中,第一个扫描的就是/vendor/overlay目录,解析其AndroidManifest.xml文件,拿到它的target package和priority。我们看看frameworks/base/core/java/android/content/pm/PackageParser.java中的相关实现:
private Package parseBaseApk(Resources res, XmlResourceParser parser, int flags,
String[] outError) throws XmlPullParserException, IOException {
//......省略无关代码
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (tagName.equals("application")) {
//解析application相关属性
//.........非我们关注的重点,省略之
}else if (tagName.equals("overlay")) {
//解析我们的overlay标签
pkg.mTrustedOverlay = trustedOverlay;
sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestResourceOverlay);
//获取android:target属性值
pkg.mOverlayTarget = sa.getString(
com.android.internal.R.styleable.AndroidManifestResourceOverlay_targetPackage);
//获取android:priority属性值
pkg.mOverlayPriority = sa.getInt(
com.android.internal.R.styleable.AndroidManifestResourceOverlay_priority,
-1);
sa.recycle();
if (pkg.mOverlayTarget == null) {
outError[0] = " does not specify a target package" ;
mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return null;
}
//android:priority属性值必须在0~9999之间
if (pkg.mOverlayPriority < 0 || pkg.mOverlayPriority > 9999) {
outError[0] = " priority must be between 0 and 9999" ;
mParseError =
PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED;
return null;
}
XmlUtils.skipCurrentTag(parser);
}
}
}
在包扫描的过程中会调用到PackageManagerServices的这个方法:
private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
//..............省略无关代码
//pkg就是前面扫描过的overlay package,所以pkg.mOverlayTarget肯定不空
if (pkg.mOverlayTarget != null) {//true
//overlay 包的处理逻辑
// This is an overlay package.
if (pkg.mOverlayTarget != null && !pkg.mOverlayTarget.equals("android")) {
//由于是第一次扫描,肯定会走进这个if,如果后面再有这个target包的overlay package则跳过
if (!mOverlays.containsKey(pkg.mOverlayTarget)) {
mOverlays.put(pkg.mOverlayTarget,
new ArrayMap<String, PackageParser.Package>());
}
//将正在扫描的这个overlay package添加进去
ArrayMap<String, PackageParser.Package> map = mOverlays.get(pkg.mOverlayTarget);
map.put(pkg.packageName, pkg);
PackageParser.Package orig = mPackages.get(pkg.mOverlayTarget);
/** *orig为空,所以执行不到createIdmapForPackagePairLI方法 *但是因为我们做了动态idmap或者线程调度原因,如果此时我们的target包已经扫描完,那 *就得在这里做idmap了,否则后面就没有机会做了。 */
if (orig != null && !createIdmapForPackagePairLI(orig, pkg)) {
throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"scanPackageLI failed to createIdmap");
}
}
} else if (mOverlays.containsKey(pkg.packageName) &&
!pkg.packageName.equals("android")) {//false 但是这个条件在扫描target package的时候会走到
//target包的处理逻辑
// This is a regular package, with one or more known overlay packages.
Slog.d(TAG, "idmapidmap, " + pkg.packageName + " is a regular package");
createIdmapsForPackageLI(pkg);
}
//.........省略无关代码
return pkg;
}
这里我们看到过滤掉了android系统资源包framework-res.apk,因为系统资源包比较特殊,它的RRO是在AssetManager的native层处理的,这里就不需要处理了。另外,当扫描完/vendor/overlay/目录下的所有overlay包后,会扫描其它包,这个时候就会走到target package的处理逻辑,去做idmap:
private void createIdmapsForPackageLI(PackageParser.Package pkg) {
//pkg是target package
ArrayMap<String, PackageParser.Package> overlays = mOverlays.get(pkg.packageName);
if (overlays == null) {
Slog.w(TAG, "Unable to create idmap for " + pkg.packageName + ": no overlay packages");
return;
}
for (PackageParser.Package opkg : overlays.values()) {
// Not much to do if idmap fails: we already logged the error
// and we certainly don't want to abort installation of pkg simply
// because an overlay didn't fit properly. For these reasons,
// ignore the return value of createIdmapForPackagePairLI.
createIdmapForPackagePairLI(pkg, opkg);
}
}
找到target package的所有overlay package,然后一个一个去做idmap。这里不太关心做idmap的结果是成功了还是失败了,如果失败,我们就当没有那个overlay package就完了,所以不处理createIdmapForPackagePairLI方法的返回值。
private boolean createIdmapForPackagePairLI(PackageParser.Package pkg,
PackageParser.Package opkg) {
//先做相关检查
if (!opkg.mTrustedOverlay) {
Slog.w(TAG, "Skipping target and overlay pair " + pkg.baseCodePath + " and " +
opkg.baseCodePath + ": overlay not trusted");
return false;
}
//拿到一个target包的所有overlay包
ArrayMap<String, PackageParser.Package> overlaySet = mOverlays.get(pkg.packageName);
if (overlaySet == null) {
Slog.e(TAG, "was about to create idmap for " + pkg.baseCodePath + " and " +
opkg.baseCodePath + " but target package has no known overlays");
return false;
}
final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
// 告诉installer去做idmap
if (mInstaller.idmap(pkg.baseCodePath, opkg.baseCodePath, sharedGid) != 0) {
Slog.e(TAG, "Failed to generate idmap for " + pkg.baseCodePath + " and "
+ opkg.baseCodePath);
return false;
}
PackageParser.Package[] overlayArray =
overlaySet.values().toArray(new PackageParser.Package[0]);
//按优先级排序!
Comparator<PackageParser.Package> cmp = new Comparator<PackageParser.Package>() {
public int compare(PackageParser.Package p1, PackageParser.Package p2) {
return p1.mOverlayPriority - p2.mOverlayPriority;
}
};
Arrays.sort(overlayArray, cmp);
//把所有overlay package的路径写入target package的applicationInfo
pkg.applicationInfo.resourceDirs = new String[overlayArray.length];
int i = 0;
for (PackageParser.Package p : overlayArray) {
pkg.applicationInfo.resourceDirs[i++] = p.baseCodePath;
}
return true;
}
这里我们可以看到:PMS调用了mInstaller的idmap方法去做idmap,然后还会对同一个target package的所有overlay package按照优先级排序,最后还会把所有overlay package的路径写入target package的ApplicationInfo的resourceDirs里面去。在Android资源管理中的SharedLibrary和Dynamic Reference-------之资源共享库(一)我们对ApplicationInfo的resourceDirs变量有过说明,不知道大家是否还记得。另外,mInstaller是Installer类的实例,我们看看它的实现:
//frameworks/base/services/core/java/com/android/server/pm/Installer.java
public final class Installer extends SystemService {
//......省略无关代码
private final InstallerConnection mInstaller;
//......省略无关代码
public Installer(Context context) {
super(context);
mInstaller = new InstallerConnection();
}
//......省略无关代码
public int idmap(String targetApkPath, String overlayApkPath, int uid) {
StringBuilder builder = new StringBuilder("idmap");
builder.append(' ');
builder.append(targetApkPath);
builder.append(' ');
builder.append(overlayApkPath);
builder.append(' ');
builder.append(uid);
return mInstaller.execute(builder.toString());
}
}
Installer的实现非常简单,包装了InstallerConnection的一个实例,让它去执行一条命令:idmap targetApkPath overlayApkPath uid
public class InstallerConnection {
private InputStream mIn;
private OutputStream mOut;
//这里有一个socket,用于跨进程和installd通信
private LocalSocket mSocket;
private final byte buf[] = new byte[1024];
public int execute(String cmd) {
String res = transact(cmd);
try {
return Integer.parseInt(res);
} catch (NumberFormatException ex) {
return -1;
}
}
public synchronized String transact(String cmd) {
//检查连接状态,如果没有连接,则去做连接动作
if (!connect()) {
Slog.e(TAG, "connection failed");
return "-1";
}
//给对端也就是installd发命令,如果不成功会做二次尝试
if (!writeCommand(cmd)) {
if (!connect() || !writeCommand(cmd)) {
return "-1";
}
}
if (LOCAL_DEBUG) {
Slog.i(TAG, "send: '" + cmd + "'");
}
//读取installd执行的结果并返回
final int replyLength = readReply();
if (replyLength > 0) {
String s = new String(buf, 0, replyLength);
return s;
}else {
return "-1";
}
}
private boolean connect() {
//已经连接
if (mSocket != null) {
return true;
}
Slog.i(TAG, "connecting...");
try {
//连接installd
mSocket = new LocalSocket();
LocalSocketAddress address = new LocalSocketAddress("installd",
LocalSocketAddress.Namespace.RESERVED);
mSocket.connect(address);
mIn = mSocket.getInputStream();
mOut = mSocket.getOutputStream();
} catch (IOException ex) {
disconnect();
return false;
}
return true;
}
//往installd那里发数据
private boolean writeCommand(String cmdString) {
final byte[] cmd = cmdString.getBytes();
final int len = cmd.length;
if ((len < 1) || (len > buf.length)) {
return false;
}
buf[0] = (byte) (len & 0xff);
buf[1] = (byte) ((len >> 8) & 0xff);
try {
mOut.write(buf, 0, 2);
mOut.write(cmd, 0, len);
} catch (IOException ex) {
Slog.e(TAG, "write error");
disconnect();
return false;
}
return true;
}
}
InstallerConnection的工作就是建立socket,向installd发送命令,然后读取installd返回的结果。到这里,对于生成idmap文件而言,PMS和Installer的工作已经完成,剩下的就交给installd这个守护进程了。
installd进程在init进程解析init.rc的时候就会起来,它主要负责我们安装包的管理,比如install、uninstall、rename、dexopt,当然还有idmap。我们来看看它的具体实现:
//frameworks/native/cmds/installd/installd.c
//没什么好说的,直接看idmap函数了
static int do_idmap(char **arg, char reply[REPLY_MAX])
{
ALOGD("do_idmap : %s %s %s", arg[0], arg[1], arg[2]);
return idmap(arg[0], arg[1], atoi(arg[2]));
}
//frameworks/native/cmds/installd/commands.c
int idmap(const char *target_apk, const char *overlay_apk, uid_t uid)
{
ALOGV("idmap target_apk=%s overlay_apk=%s uid=%d\n", target_apk, overlay_apk, uid);
int idmap_fd = -1;
char idmap_path[PATH_MAX];
//.....路径、uid、权限等的检查,如果不通过直接返回,代码就不贴了
pid_t pid;
//fork出子进程
pid = fork();
if (pid == 0) {
//.........子进程,又是一些列的检查,代码不贴
//在这里做idmap
run_idmap(target_apk, overlay_apk, idmap_fd);
exit(1); /* only if exec call to idmap failed */
} else {
//父进程,等结果
int status = wait_child(pid);
if (status != 0) {
ALOGE("idmap failed, status=0x%04x\n", status);
goto fail;
}
}
close(idmap_fd);
return 0;
fail:
if (idmap_fd >= 0) {
close(idmap_fd);
unlink(idmap_path);
}
return -1;
}
static void run_idmap(const char *target_apk, const char *overlay_apk, int idmap_fd)
{
//idmap的bin文件在/system/bin/idmap
static const char *IDMAP_BIN = "/system/bin/idmap";
static const size_t MAX_INT_LEN = 32;
char idmap_str[MAX_INT_LEN];
snprintf(idmap_str, sizeof(idmap_str), "%d", idmap_fd);
//执行bin文件
execl(IDMAP_BIN, IDMAP_BIN, "--fd", target_apk, overlay_apk, idmap_str, (char*)NULL);
ALOGE("execl(%s) failed: %s\n", IDMAP_BIN, strerror(errno));
}
installd的工作也很简单,做一系列的权限检查,然后fork出子进程,加载/system/bin/idmap并执行。
idmap也是一个native的bin文件,在/system/bin/下面,主要负责idmap文件的生成和dump。我们可以通过adb shell进去,然后输入idmap --help查看它的用法:
--fd 创建idmap,直接输出到对应的fd
--path 创建idmap,输出到指定的路径
--inspect dump idmap文件
--scan 用于一个target package 有多个overlay package的情况:
dir-to-scan 多个overlay package所在的目录
target-to-look-for target包的packageName
target target包的路径
dir-to-hold-idmaps 存放生成的idmap文件的目录
因此,我们可以通过idmap命令手动做idmap,直接把idmap文件生成到/data/resource-cache/目录下,效果跟通过PMS和installd来生成是一样的。另外,我们可以通过idmap --inspect来查看生成的idmap文件的内容,这里贴上一个我们可以先感受下:
IDMAP HEADER主要记录了target包和overlay包的路径:/system/priv-app/SystemUI/SystemUI.apk和/vendor/overlay/SystemUIOverlay.apk;
要覆盖的type的个数:types count=1,因为这里只覆盖了drawable一个type;
然后是每一个type的idmap信息了,这个例子中只有drawable一个type
target type和overlay type的值都是0x00000002,说明drawable在target包和overlay包的id都是0x00000002;
entry offset 0x00000039表示从target包的索引值为0x00000039的那个drawable开始做idmap;
entry count 0x00000143表示drawable类型的资源,做了0x00000143个;
这张图我截得不全,因为它太大了。其实这个overlay pacakge仅仅有四张图片,也就是说只需要覆盖4个drawable就可以了,但是为什么entry count 不是0x00000004而是0x00000143呢?
假设我们的target 包里有10个drawable资源,它们的名称为drawable0~drawable9,id为0x7f020000~0x7f020009;overlay包里有3个资源它们的名称分别是drawable4、drawable5、drawable8,id分别是0x7f030000、0x7f030001、0x7f030002。这样就表示overlay包要覆盖target包里的drawable4、drawable5、drawable8三个资源。那么它生成的idmap的主要内容应该如下:
target type 0x00000002
overlay type 0x00000003
entry offset 0x00000004
entry count 0x00000006
entry 0x00000000 drawable/drawable4
entry 0x00000001 drawable/drawable5
entry 0xffffffff drawable/drawable6
entry 0xffffffff drawable/drawable7
entry 0x00000002 drawable/drawable8
entry 0xffffffff drawable/drawable9
为什么会是这个样子呢?我们将在下一篇中介绍,这里我们知道是这个样子就可以了。下面我们看看idmap这个bin文件的部分代码吧,在frameworks/base/cmds/idmap/目录下:
//frameworks/base/cmds/idmap/idmap.cpp
int main(int argc, char **argv)
{
//.........省略无关代码
//接installd,这里走--fd
if (argc == 5 && !strcmp(argv[1], "--fd")) {
return maybe_create_fd(argv[2], argv[3], argv[4]);
}
//.........省略无关代码
}
int maybe_create_path(const char *target_apk_path, const char *overlay_apk_path,
const char *idmap_path)
{
//这个也没啥好说的,又是一堆检查。。。
if (!verify_root_or_system()) {
fprintf(stderr, "error: permission denied: not user root or user system\n");
return -1;
}
if (!verify_file_readable(target_apk_path)) {
ALOGD("error: failed to read apk %s: %s\n", target_apk_path, strerror(errno));
return -1;
}
if (!verify_file_readable(overlay_apk_path)) {
ALOGD("error: failed to read apk %s: %s\n", overlay_apk_path, strerror(errno));
return -1;
}
return idmap_create_path(target_apk_path, overlay_apk_path, idmap_path);
}
//frameworks/base/cmds/idmap/create.cpp
int idmap_create_path(const char *target_apk_path, const char *overlay_apk_path,
const char *idmap_path)
{
//已经是新的没必要做了
if (!is_idmap_stale_path(target_apk_path, overlay_apk_path, idmap_path)) {
// already up to date -- nothing to do
return EXIT_SUCCESS;
}
//打开文件
int fd = open_idmap(idmap_path);
if (fd == -1) {
return EXIT_FAILURE;
}
//做idmap,并写入文件
int r = create_and_write_idmap(target_apk_path, overlay_apk_path, fd, false);
close(fd);
if (r != 0) {
unlink(idmap_path);
}
return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
int create_and_write_idmap(const char *target_apk_path, const char *overlay_apk_path,
int fd, bool check_if_stale)
{
//又是检查。。。
if (check_if_stale) {
if (!is_idmap_stale_fd(target_apk_path, overlay_apk_path, fd)) {
// already up to date -- nothing to do
return 0;
}
}
uint32_t *data = NULL;
size_t size;
//在这里做idmap
if (create_idmap(target_apk_path, overlay_apk_path, &data, &size) == -1) {
return -1;
}
//标准的写文件操作,就不说了
if (write_idmap(fd, data, size) == -1) {
free(data);
return -1;
}
free(data);
return 0;
}
int create_idmap(const char *target_apk_path, const char *overlay_apk_path,
uint32_t **data, size_t *size)
{
//CRC校验
uint32_t target_crc, overlay_crc;
if (get_zip_entry_crc(target_apk_path, AssetManager::RESOURCES_FILENAME,
&target_crc) == -1) {
return -1;
}
if (get_zip_entry_crc(overlay_apk_path, AssetManager::RESOURCES_FILENAME,
&overlay_crc) == -1) {
return -1;
}
//绕了半天,这个活儿还是AssetManager来干的^_^
AssetManager am;
bool b = am.createIdmap(target_apk_path, overlay_apk_path, target_crc, overlay_crc,
data, size);
return b ? 0 : -1;
}
其实idmap进程还是围观者,只不过是做权限、访问、CRC等校验,真正的工作是由AssetManager来完成的。带着idmap文件那奇怪的数据形式,我们将在下期介绍idmap文件具体是怎么生成和加载的。