前面我們對RRO(Runtime Resources Overlay)的使用做了介紹,并且知道了它大概的原理。下面我們詳細介紹其實作過程,先說兩個名詞:
target包:就是要被覆寫的包
overlay包:就是我們的資源包或者主題包等
PMS的處理
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] = "<overlay> 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] = "<overlay> 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的處理
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的處理
idmap也是一個native的bin檔案,在/system/bin/下面,主要負責idmap檔案的生成和dump。我們可以通過adb shell進去,然後輸入idmap --help檢視它的用法:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL3AjM5EjM1YTM3EzNwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
--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檔案具體是怎麼生成和加載的。