APK安裝流程系列文章整體內(nèi)容如下:
- APK安裝流程詳解0——前言
- APK安裝流程詳解1——有關(guān)"安裝ing"的實體類概述
- APK安裝流程詳解2——PackageManager簡介
- APK安裝流程詳解3——PackageManager與PackageManagerService
- APK安裝流程詳解4——安裝中關(guān)于so庫的那些事
- APK安裝流程詳解5——PackageInstallerService和Installer
- APK安裝流程詳解6——PackageManagerService啟動前奏
- APK安裝流程詳解7——PackageManagerService的啟動流程(上)
- APK安裝流程詳解8——PackageManagerService的啟動流程(下)
- APK安裝流程詳解9——PackageParser解析APK(上)
- APK安裝流程詳解10——PackageParser解析APK(下)
- APK安裝流程詳解11——普通應(yīng)用安裝簡介
- APK安裝流程詳解12——PackageManagerService中的新安裝流程上(拷貝)
- APK安裝流程詳解13——PackageManagerService中的新安裝流程下(裝載)
- APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充
- APK安裝流程詳解15——PMS中的新安裝流程下(裝載)補充
本片文章的主要內(nèi)容如下:
- 1、Installer簡介
- 2、InstallerConnection簡介
- 3、Installd守護進程
一、Installer簡介
(一)柠横、Installer類簡介
public final class Installer extends SystemService {
}
我們知道Installer繼承自SystemService,在Android系統(tǒng)中有兩個SystemServer课兄,一個是os/SystemService.java牍氛,另一個是server/SystemService.java,這里Installer繼承的是server/SystemService.java烟阐,所以我們可以說Installer其實是一個系統(tǒng)服務(wù)搬俊。
(二)、Installer類的構(gòu)造函數(shù)
private final InstallerConnection mInstaller;
public Installer(Context context) {
super(context);
mInstaller = new InstallerConnection();
}
Installer就一個有參的構(gòu)造函數(shù)蜒茄,并且傳入一個Context唉擂,而在構(gòu)造函數(shù)里面什么都沒做,就是初始化了mInstaller檀葛,這里mInstaller其實是一個InstallerConnection對象玩祟,關(guān)于InstallerConnection類我會在后面單獨講解。
(三)屿聋、Installer類的啟動
1空扎、Installer的啟動
代碼在SystemServer.java 326行
private void startBootstrapServices() {
// Wait for installd to finish starting up so that it has a chance to
// create critical directories such as /data/user with the appropriate
// permissions. We need this to complete before we initialize other services.
Installer installer = mSystemServiceManager.startService(Installer.class);
...
}
先來看下翻譯
等待intalld完成啟動,這樣它就可以創(chuàng)建需要權(quán)限的關(guān)鍵目錄胜臊,比如/data/user勺卢。在初始化其他服務(wù)之前,我們必須先做此操作
等待installd完成啟動象对,以便它有機會創(chuàng)建具有適當(dāng)權(quán)限的關(guān)鍵目錄,如/ data / user宴抚。 在初始化其他服務(wù)之前勒魔,我們需要完成此操作。
2菇曲、onStart()方法
因為Installer繼承自SystemService冠绢,所以我們看下Installer的onStart方法
代碼在Installer.java 396行
@Override
public void onStart() {
Slog.i(TAG, "Waiting for installd to be ready.");
mInstaller.waitForConnection();
}
我們發(fā)現(xiàn)這個方法里面什么都沒做,就是調(diào)用了mInstaller.waitForConnection(String)方法常潮。
3弟胀、小結(jié)
先創(chuàng)建Installer對象,再調(diào)用onStart()方法,該方法中主要工作是等待socket通道建立完成孵户。
(四)萧朝、Installer類的其他方法
上面一篇文章我們在講解PackageManagerService初始化的時候,涉及到了很多關(guān)于Installer的操作夏哭,我們就來看下
- 1检柬、PackageManagerService.java 構(gòu)造函數(shù)里面
1985行調(diào)用mInstaller.dexopt(lib, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded, false);- 2、PackageManagerService.java 構(gòu)造函數(shù)里面 2034行調(diào)用
mInstaller.dexopt(path, Process.SYSTEM_UID, true, dexCodeInstructionSet, dexoptNeeded, false);- 3竖配、 PackageManagerService.java 構(gòu)造函數(shù)里面 2103行調(diào)用
mInstaller.moveFiles();
由于上面1和2調(diào)用都是 dexopt(String, int, boolean,String, int, boolean)方法何址,那我們就來看下這個方法
1、dexopt(String, int, boolean,String, int, boolean)方法
代碼在Installer.java 83行
public int dexopt(String apkPath, int uid, boolean isPublic,
String instructionSet, int dexoptNeeded, boolean bootComplete) {
// 校驗是否是非法的instructionSet
if (!isValidInstructionSet(instructionSet)) {
Slog.e(TAG, "Invalid instruction set: " + instructionSet);
return -1;
}
// 最終調(diào)用了mInstaller的dexopt方法
return mInstaller.dexopt(apkPath, uid, isPublic, instructionSet, dexoptNeeded,
bootComplete);
}
我們看到了這個方法本質(zhì)其實是通過mInstaller的dexopt方法來進行的进胯,這個方法我們先記下來用爪,一會講解
2、mInstaller.moveFiles()方法
代碼在Installer.java 396行
public int moveFiles() {
return mInstaller.execute("movefiles");
}
我們發(fā)現(xiàn)這個方法里面什么都沒做胁镐,就是調(diào)用了mInstaller.execute(String)方法偎血。
3、總結(jié)
大家發(fā)現(xiàn)什么概率沒希停,是的烁巫,貌似Installer的很多方法的具體實現(xiàn)最后都是調(diào)用了mInstaller的方法,其中大部分的方法其最后宠能,都是調(diào)用的mInstaller.execute(String)方法如下:
為了方便查閱亚隙,我把行數(shù)也加上了
63 public int install(String uuid, String name, int uid, int gid, String seinfo) {
64 StringBuilder builder = new StringBuilder("install");
65 builder.append(' ');
66 builder.append(escapeNull(uuid));
67 builder.append(' ');
68 builder.append(name);
69 builder.append(' ');
70 builder.append(uid);
71 builder.append(' ');
72 builder.append(gid);
73 builder.append(' ');
74 builder.append(seinfo != null ? seinfo : "!");
75 return mInstaller.execute(builder.toString());
76 }
101 public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
102 String instructionSet, int dexoptNeeded, boolean vmSafeMode,
103 boolean debuggable, @Nullable String outputPath, boolean bootComplete) {
104 if (!isValidInstructionSet(instructionSet)) {
105 Slog.e(TAG, "Invalid instruction set: " + instructionSet);
106 return -1;
107 }
108 return mInstaller.dexopt(apkPath, uid, isPublic, pkgName,
109 instructionSet, dexoptNeeded, vmSafeMode,
110 debuggable, outputPath, bootComplete);
111 }
112
113 public int idmap(String targetApkPath, String overlayApkPath, int uid) {
114 StringBuilder builder = new StringBuilder("idmap");
115 builder.append(' ');
116 builder.append(targetApkPath);
117 builder.append(' ');
118 builder.append(overlayApkPath);
119 builder.append(' ');
120 builder.append(uid);
121 return mInstaller.execute(builder.toString());
122 }
123
124 public int movedex(String srcPath, String dstPath, String instructionSet) {
125 if (!isValidInstructionSet(instructionSet)) {
126 Slog.e(TAG, "Invalid instruction set: " + instructionSet);
127 return -1;
128 }
129
130 StringBuilder builder = new StringBuilder("movedex");
131 builder.append(' ');
132 builder.append(srcPath);
133 builder.append(' ');
134 builder.append(dstPath);
135 builder.append(' ');
136 builder.append(instructionSet);
137 return mInstaller.execute(builder.toString());
138 }
139
140 public int rmdex(String codePath, String instructionSet) {
141 if (!isValidInstructionSet(instructionSet)) {
142 Slog.e(TAG, "Invalid instruction set: " + instructionSet);
143 return -1;
144 }
145
146 StringBuilder builder = new StringBuilder("rmdex");
147 builder.append(' ');
148 builder.append(codePath);
149 builder.append(' ');
150 builder.append(instructionSet);
151 return mInstaller.execute(builder.toString());
152 }
153
154 /**
155 * Removes packageDir or its subdirectory
156 */
157 public int rmPackageDir(String packageDir) {
158 StringBuilder builder = new StringBuilder("rmpackagedir");
159 builder.append(' ');
160 builder.append(packageDir);
161 return mInstaller.execute(builder.toString());
162 }
169 public int remove(String uuid, String name, int userId) {
170 StringBuilder builder = new StringBuilder("remove");
171 builder.append(' ');
172 builder.append(escapeNull(uuid));
173 builder.append(' ');
174 builder.append(name);
175 builder.append(' ');
176 builder.append(userId);
177 return mInstaller.execute(builder.toString());
178 }
180 public int rename(String oldname, String newname) {
181 StringBuilder builder = new StringBuilder("rename");
182 builder.append(' ');
183 builder.append(oldname);
184 builder.append(' ');
185 builder.append(newname);
186 return mInstaller.execute(builder.toString());
187 }
194 public int fixUid(String uuid, String name, int uid, int gid) {
195 StringBuilder builder = new StringBuilder("fixuid");
196 builder.append(' ');
197 builder.append(escapeNull(uuid));
198 builder.append(' ');
199 builder.append(name);
200 builder.append(' ');
201 builder.append(uid);
202 builder.append(' ');
203 builder.append(gid);
204 return mInstaller.execute(builder.toString());
205 }
212 public int deleteCacheFiles(String uuid, String name, int userId) {
213 StringBuilder builder = new StringBuilder("rmcache");
214 builder.append(' ');
215 builder.append(escapeNull(uuid));
216 builder.append(' ');
217 builder.append(name);
218 builder.append(' ');
219 builder.append(userId);
220 return mInstaller.execute(builder.toString());
221 }
228 public int deleteCodeCacheFiles(String uuid, String name, int userId) {
229 StringBuilder builder = new StringBuilder("rmcodecache");
230 builder.append(' ');
231 builder.append(escapeNull(uuid));
232 builder.append(' ');
233 builder.append(name);
234 builder.append(' ');
235 builder.append(userId);
236 return mInstaller.execute(builder.toString());
237 }
238
244 public int createUserData(String uuid, String name, int uid, int userId, String seinfo) {
245 StringBuilder builder = new StringBuilder("mkuserdata");
246 builder.append(' ');
247 builder.append(escapeNull(uuid));
248 builder.append(' ');
249 builder.append(name);
250 builder.append(' ');
251 builder.append(uid);
252 builder.append(' ');
253 builder.append(userId);
254 builder.append(' ');
255 builder.append(seinfo != null ? seinfo : "!");
256 return mInstaller.execute(builder.toString());
257 }
259 public int createUserConfig(int userId) {
260 StringBuilder builder = new StringBuilder("mkuserconfig");
261 builder.append(' ');
262 builder.append(userId);
263 return mInstaller.execute(builder.toString());
264 }
265
271 public int removeUserDataDirs(String uuid, int userId) {
272 StringBuilder builder = new StringBuilder("rmuser");
273 builder.append(' ');
274 builder.append(escapeNull(uuid));
275 builder.append(' ');
276 builder.append(userId);
277 return mInstaller.execute(builder.toString());
278 }
280 public int copyCompleteApp(String fromUuid, String toUuid, String packageName,
281 String dataAppName, int appId, String seinfo) {
282 StringBuilder builder = new StringBuilder("cpcompleteapp");
283 builder.append(' ');
284 builder.append(escapeNull(fromUuid));
285 builder.append(' ');
286 builder.append(escapeNull(toUuid));
287 builder.append(' ');
288 builder.append(packageName);
289 builder.append(' ');
290 builder.append(dataAppName);
291 builder.append(' ');
292 builder.append(appId);
293 builder.append(' ');
294 builder.append(seinfo);
295 return mInstaller.execute(builder.toString());
296 }
303 public int clearUserData(String uuid, String name, int userId) {
304 StringBuilder builder = new StringBuilder("rmuserdata");
305 builder.append(' ');
306 builder.append(escapeNull(uuid));
307 builder.append(' ');
308 builder.append(name);
309 builder.append(' ');
310 builder.append(userId);
311 return mInstaller.execute(builder.toString());
312 }
314 public int markBootComplete(String instructionSet) {
315 if (!isValidInstructionSet(instructionSet)) {
316 Slog.e(TAG, "Invalid instruction set: " + instructionSet);
317 return -1;
318 }
320 StringBuilder builder = new StringBuilder("markbootcomplete");
321 builder.append(' ');
322 builder.append(instructionSet);
323 return mInstaller.execute(builder.toString());
324 }
331 public int freeCache(String uuid, long freeStorageSize) {
332 StringBuilder builder = new StringBuilder("freecache");
333 builder.append(' ');
334 builder.append(escapeNull(uuid));
335 builder.append(' ');
336 builder.append(String.valueOf(freeStorageSize));
337 return mInstaller.execute(builder.toString());
338 }
405 /**
406 * Links the 32 bit native library directory in an application's data directory to the
407 * real location for backward compatibility. Note that no such symlink is created for
408 * 64 bit shared libraries.
409 *
410 * @return -1 on error
411 */
412 public int linkNativeLibraryDirectory(String uuid, String dataPath, String nativeLibPath32,
413 int userId) {
414 if (dataPath == null) {
415 Slog.e(TAG, "linkNativeLibraryDirectory dataPath is null");
416 return -1;
417 } else if (nativeLibPath32 == null) {
418 Slog.e(TAG, "linkNativeLibraryDirectory nativeLibPath is null");
419 return -1;
420 }
421
422 StringBuilder builder = new StringBuilder("linklib");
423 builder.append(' ');
424 builder.append(escapeNull(uuid));
425 builder.append(' ');
426 builder.append(dataPath);
427 builder.append(' ');
428 builder.append(nativeLibPath32);
429 builder.append(' ');
430 builder.append(userId);
431
432 return mInstaller.execute(builder.toString());
433 }
440 public boolean restoreconData(String uuid, String pkgName, String seinfo, int uid) {
441 StringBuilder builder = new StringBuilder("restorecondata");
442 builder.append(' ');
443 builder.append(escapeNull(uuid));
444 builder.append(' ');
445 builder.append(pkgName);
446 builder.append(' ');
447 builder.append(seinfo != null ? seinfo : "!");
448 builder.append(' ');
449 builder.append(uid);
450 return (mInstaller.execute(builder.toString()) == 0);
451 }
453 public int createOatDir(String oatDir, String dexInstructionSet) {
454 StringBuilder builder = new StringBuilder("createoatdir");
455 builder.append(' ');
456 builder.append(oatDir);
457 builder.append(' ');
458 builder.append(dexInstructionSet);
459 return mInstaller.execute(builder.toString());
460 }
463 public int linkFile(String relativePath, String fromBase, String toBase) {
464 StringBuilder builder = new StringBuilder("linkfile");
465 builder.append(' ');
466 builder.append(relativePath);
467 builder.append(' ');
468 builder.append(fromBase);
469 builder.append(' ');
470 builder.append(toBase);
471 return mInstaller.execute(builder.toString());
472 }
說了半天,所有Installer很多方法的具體實現(xiàn)都是mInstaller(即InstallerConnection對象)來實現(xiàn)的违崇。那下面就讓我們來看下這個類
二阿弃、InstallerConnection簡介
(一)、先來看下InstallerConnection類
/**
* Represents a connection to {@code installd}. Allows multiple connect and
* disconnect cycles.
*
* @hide for internal use only
*/
public class InstallerConnection {
...
}
翻譯一下注釋:
代表與installd的連接羞延,允許多個連接和斷開連接
可見渣淳,這個類其實是一個"連接的"包裝類
(二)、先來看下InstallerConnection類的構(gòu)造函數(shù)
public InstallerConnection() {
}
InstallerConnection的就一個構(gòu)造函數(shù)伴箩。里面什么都沒有做
那我們就來看下被Install調(diào)用的幾個方法
(三)入愧、先來看下InstallerConnection類的常用方法
4、dexopt(String , int, boolean,String, int, boolean) 方法
代碼在InstallerConnection.java 94行
public int dexopt(String apkPath, int uid, boolean isPublic,
String instructionSet, int dexoptNeeded, boolean bootComplete) {
return dexopt(apkPath, uid, isPublic, "*", instructionSet, dexoptNeeded,
false, false, null, bootComplete);
}
我們看到嗤谚,這個方法什么都沒做棺蛛,直接調(diào)用了dexopt(String apkPath, int uid, boolean isPublic, String pkgName,String instructionSet, int dexoptNeeded, boolean vmSafeMode,boolean debuggable, String outputPath, boolean bootComplete) 方法
那我們來看下
public int dexopt(String apkPath, int uid, boolean isPublic, String pkgName,
String instructionSet, int dexoptNeeded, boolean vmSafeMode,
boolean debuggable, String outputPath, boolean bootComplete) {
StringBuilder builder = new StringBuilder("dexopt");
builder.append(' ');
builder.append(apkPath);
builder.append(' ');
builder.append(uid);
builder.append(isPublic ? " 1" : " 0");
builder.append(' ');
builder.append(pkgName);
builder.append(' ');
builder.append(instructionSet);
builder.append(' ');
builder.append(dexoptNeeded);
builder.append(vmSafeMode ? " 1" : " 0");
builder.append(debuggable ? " 1" : " 0");
builder.append(' ');
builder.append(outputPath != null ? outputPath : "!");
builder.append(bootComplete ? " 1" : " 0");
return execute(builder.toString());
}
可見我們這個dexopt方法其實也是調(diào)用的execute(String),再結(jié)合上面的解析巩步,我們知道Install類調(diào)用InstallerConnection的方法基本上最后都是執(zhí)行execute(String) 方法旁赊,那么我們就來看下
2、execute(String cmd)方法
代碼在InstallerConnection.java 85行
public int execute(String cmd) {
String res = transact(cmd);
try {
return Integer.parseInt(res);
} catch (NumberFormatException ex) {
return -1;
}
}
我們看到這方法 主要就是調(diào)用transact(String)方法椅野,然后把String類型返回值轉(zhuǎn)化為int型返回终畅,那我們就來看下transact(String)方法
3籍胯、transact(String cmd) 方法
代碼在InstallerConnection.java 49行
public synchronized String transact(String cmd) {
// 第一步
if (!connect()) {
Slog.e(TAG, "connection failed");
return "-1";
}
// 第二步
if (!writeCommand(cmd)) {
/*
* If installd died and restarted in the background (unlikely but
* possible) we'll fail on the next write (this one). Try to
* reconnect and write the command one more time before giving up.
*/
Slog.e(TAG, "write command failed? reconnect!");
if (!connect() || !writeCommand(cmd)) {
return "-1";
}
}
if (LOCAL_DEBUG) {
Slog.i(TAG, "send: '" + cmd + "'");
}
// 第三步
final int replyLength = readReply();
if (replyLength > 0) {
String s = new String(buf, 0, replyLength);
if (LOCAL_DEBUG) {
Slog.i(TAG, "recv: '" + s + "'");
}
return s;
} else {
if (LOCAL_DEBUG) {
Slog.i(TAG, "fail");
}
return "-1";
}
}
就像一般的請求一樣,我將上面的代碼分為3部分
- 1离福、建立連接:connect()方法
- 2杖狼、發(fā)出請求:writeCommand(String)方法
- 3、收到回復(fù):readReply()方法
下面我們就詳細(xì)看下其對應(yīng)的幾個方法
3.1术徊、connect()方法簡介
代碼在InstallerConnection.java 123行
private boolean connect() {
// 第一次才需要進行實際連接本刽,之后就不需要了
if (mSocket != null) {
return true;
}
Slog.i(TAG, "connecting...");
try {
mSocket = new LocalSocket();
// 得到"installd"目的端地址
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;
}
通過上面代碼我們知道,在connect()方法內(nèi)部通過LocalSocketAddress與installd建立連接赠涮,其中mIn和mOut分別對應(yīng)輸入流和輸出流
3.2子寓、writeCommand(String)方法簡介
代碼在InstallerConnection.java 192行
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;
}
這個方法很簡單,把cmdString轉(zhuǎn)化byte[] 笋除,這里面涉及到一個buf斜友,buf是一個size為1024的byte數(shù)組。然后把cmdString對應(yīng)的byte寫入到輸入流中垃它。
3.3鲜屏、readReply()方法簡介
代碼在InstallerConnection.java 173行
private int readReply() {
if (!readFully(buf, 2)) {
return -1;
}
final int len = (((int) buf[0]) & 0xff) | ((((int) buf[1]) & 0xff) << 8);
if ((len < 1) || (len > buf.length)) {
Slog.e(TAG, "invalid reply length (" + len + ")");
disconnect();
return -1;
}
if (!readFully(buf, len)) {
return -1;
}
return len;
}
這個方法內(nèi)部很簡單就是調(diào)用readFully(byte[] buffer, int len)讀取輸入流而已
private boolean readFully(byte[] buffer, int len) {
try {
Streams.readFully(mIn, buffer, 0, len);
} catch (IOException ioe) {
disconnect();
return false;
}
return true;
}
3.4、小結(jié)
可見国拇,一次transct過程就是先connect()來判斷是否建立socket連接洛史,如果已經(jīng)連接則調(diào)用writeCommand()將命令寫入socket的mOut管道,等待從管道的mIn中readFully()讀取應(yīng)答消息酱吝。
4也殖、execute(String cmd)方法
代碼在InstallerConnection.java 85行
public void waitForConnection() {
for (;;) {
if (execute("ping") >= 0) {
return;
}
Slog.w(TAG, "installd not ready");
SystemClock.sleep(1000);
}
}
通過循環(huán)地方式,每次休眠1s
5务热、總結(jié)
InstallerConnection就是一個連接類忆嗜,負(fù)責(zé)連接installd
三、Installd守護進程
(一)崎岂、概述
我們知道PackageManagerServcie負(fù)責(zé)應(yīng)用的安裝捆毫,卸載等相關(guān)工作,但是大家注意冲甘,里面主要是"Manager"绩卤,那具體負(fù)責(zé)這一塊的是什么?就是我們要講解的installd江醇,installd才是真正的干活的省艳。是通過PackageManagerService來訪問的installd服務(wù)來執(zhí)行程序包的安裝與卸載的。
如下圖
PackageManagerService是通過套接字方式訪問installd服務(wù)進程的嫁审,
(二)、為什么要用intalld
有人會問了赖晶,PackageManageService這么大的組件了律适,為什么還需要intalld這個守護進程?這是因為權(quán)限的問題辐烂,PackageManagerService只有system權(quán)限。installd卻是具有root權(quán)限
如下圖:
(三)捂贿、intalld支持的命令
struct cmdinfo cmds[] = {
{ "ping", 0, do_ping }, // 用于測試的空操作
{ "install", 5, do_install }, // 安裝應(yīng)用
{ "dexopt", 9, do_dexopt }, //將dex轉(zhuǎn)換為oat或者patchoat oat文件
{ "markbootcomplete", 1, do_mark_boot_complete },
{ "movedex", 3, do_move_dex }, //把apk文件從一個目錄移動到另一個目錄
{ "rmdex", 2, do_rm_dex }, // 刪除apk文件
{ "remove", 3, do_remove }, // 卸載應(yīng)用
{ "rename", 2, do_rename }, // 更改應(yīng)用數(shù)據(jù)目錄的名稱
{ "fixuid", 4, do_fixuid }, // 更改應(yīng)用數(shù)據(jù)目錄的uid
{ "freecache", 2, do_free_cache }, // 清除/cache目錄下的文件
{ "rmcache", 3, do_rm_cache }, // 刪除/cache下某個應(yīng)用的目錄
{ "rmcodecache", 3, do_rm_code_cache }, // 刪除數(shù)據(jù)目錄中code_cache文件夾
{ "getsize", 8, do_get_size }, // 計算一個應(yīng)用占用的空間大小纠修,包括apk大小,數(shù)據(jù)目錄厂僧,cache目錄等
{ "rmuserdata", 3, do_rm_user_data },// 刪除一個用戶中某個app的應(yīng)用數(shù)據(jù)
{ "cpcompleteapp", 6, do_cp_complete_app },
{ "movefiles", 0, do_movefiles },//執(zhí)行/system/etc/updatecmds/中的腳本
{ "linklib", 4, do_linklib }, // 建立 jib連接
{ "mkuserdata", 5, do_mk_user_data },// 為某個用戶創(chuàng)建應(yīng)用數(shù)據(jù)目錄
{ "mkuserconfig", 1, do_mk_user_config },// 創(chuàng)建/data/misc/user/userid/
{ "rmuser", 2, do_rm_user },// 刪除一個user的所有文件
{ "idmap", 3, do_idmap },
{ "restorecondata", 4, do_restorecon_data },// 恢復(fù)目錄的SEAndroid安全上下文
{ "createoatdir", 2, do_create_oat_dir }, // 創(chuàng)建 /data/app/包名/oat/<inst>文件夾
{ "rmpackagedir", 1, do_rm_package_dir },// 刪除/data/app/包名
{ "linkfile", 3, do_link_file } // 創(chuàng)建軟連接
};
此命令表總共有25條命令扣草,該表中第二列是指命令所需的參數(shù)個數(shù),第三列是指命令所指向的函數(shù)颜屠。不同的Android版本該表格都會有所不同
不同Android版本中installd命令列表如下圖辰妙,建議下載到PC上查看
(四)、intalld啟動流程
1甫窟、啟動
installd是由Android系統(tǒng)init進程(pid=1)密浑,在解析init.rc文件的代碼時,通過fork創(chuàng)建用戶空間的守護進程intalld
代碼在init.rc 687行
service installd /system/bin/installd
class main
socket installd stream 600 system system
installd是隨著系統(tǒng)啟動過程中的main class而啟動的粗井,并且會創(chuàng)建一個socket套接字尔破,用于跟上層的PackageManagerService進行交互。installd的啟動入口是frameworks/base/cmds/installd/installd.c的main()方法浇衬,接下來從這里開始說
2懒构、installd的main方法
代碼在installd.cpp 660行
int main(const int argc __unused, char *argv[]) {
char buf[BUFFER_MAX];
struct sockaddr addr;
socklen_t alen;
int lsocket, s;
int selinux_enabled = (is_selinux_enabled() > 0);
setenv("ANDROID_LOG_TAGS", "*:v", 1);
android::base::InitLogging(argv);
ALOGI("installd firing up\n");
union selinux_callback cb;
cb.func_log = log_callback;
selinux_set_callback(SELINUX_CB_LOG, cb);
// 初始化全局變量
if (initialize_globals() < 0) {
ALOGE("Could not initialize globals; exiting.\n");
exit(1);
}
// 初始化安裝目錄
if (initialize_directories() < 0) {
ALOGE("Could not create directories; exiting.\n");
exit(1);
}
if (selinux_enabled && selinux_status_open(true) < 0) {
ALOGE("Could not open selinux status; exiting.\n");
exit(1);
}
// 取得installd套接字,系統(tǒng)中所有的socket以ANDROID_SOCKET_[name]為key耘擂,
socket為為value的方式保存在 環(huán)境變量中
lsocket = android_get_control_socket(SOCKET_PATH);
if (lsocket < 0) {
ALOGE("Failed to get socket from environment: %s\n", strerror(errno));
exit(1);
}
// 監(jiān)聽socket消息
if (listen(lsocket, 5)) {
ALOGE("Listen on socket failed: %s\n", strerror(errno));
exit(1);
}
// 修改該socket的屬性
fcntl(lsocket, F_SETFD, FD_CLOEXEC);
for (;;) {
alen = sizeof(addr);
//接受socket客戶端請求
s = accept(lsocket, &addr, &alen);
if (s < 0) {
ALOGE("Accept failed: %s\n", strerror(errno));
continue;
}
// 接收到客戶端的請求后胆剧,修改客戶端請求socket客戶端
fcntl(s, F_SETFD, FD_CLOEXEC);
ALOGI("new connection\n");
// 循環(huán)讀取客戶端socket中內(nèi)容,直到讀取內(nèi)容為空為止
// 客戶端 發(fā)送的數(shù)據(jù)格式:數(shù)據(jù)長度 | 數(shù)據(jù)內(nèi)容
for (;;) {
unsigned short count;
// 讀取數(shù)據(jù)長度梳星,讀取成功返回0赞赖,反之返回-1
if (readx(s, &count, sizeof(count))) {
ALOGE("failed to read size\n");
break;
}
//如果讀取成功,但是讀取的數(shù)據(jù)長度超出1024字節(jié)冤灾,同樣停止讀取
if ((count < 1) || (count >= BUFFER_MAX)) {
ALOGE("invalid size %d\n", count);
break;
}
// 讀取指令內(nèi)容前域,讀取成功返回0,反之返回-1
if (readx(s, buf, count)) {
ALOGE("failed to read command\n");
break;
}
buf[count] = 0;
if (selinux_enabled && selinux_status_updated() > 0) {
selinux_android_seapp_context_reload();
}
// 執(zhí)行指令
if (execute(s, buf)) break;
}
ALOGI("closing connection\n");
//執(zhí)行完客戶端的請求后韵吨,關(guān)閉socket連接匿垄,繼續(xù)進入接手請求模式
close(s);
}
return 0;
}
該方法首先初始化一些變量就安裝目錄,然后從環(huán)境變量中取得installd套接字的句柄值归粉,然后進入監(jiān)聽此socket椿疗,當(dāng)客戶端發(fā)送過來請求時,接收客戶端的請求糠悼,并讀取客戶端發(fā)送過來的命令數(shù)據(jù)届榄,并根據(jù)讀取客戶端命令來執(zhí)行命令操作。這里面涉及到3個關(guān)鍵方法:
- initialize_globals()方法:初始化全局信息
- initialize_directories()方法:初始化相關(guān)目錄
- static int execute(int s, char cmd[BUFFER_MAX])方法:執(zhí)行指令
2.1倔喂、 initialize_globals()方法
代碼在installd.cpp 349行
int initialize_globals() {
// Get the android data directory.
// 從環(huán)境變量中讀取數(shù)據(jù)存儲目錄铝条,在Android啟動腳本init.rc中配置了ANDROID_DATA
// 環(huán)境變量靖苇,export ANDORID_DATA /data ,因此變量android_data_dir=/data/
if (get_path_from_env(&android_data_dir, "ANDROID_DATA") < 0) {
return -1;
}
// Get the android app directory.
// app目錄/data/app/
if (copy_and_append(&android_app_dir, &android_data_dir, APP_SUBDIR) < 0) {
return -1;
}
// Get the android protected app directory.
// 得到應(yīng)用程序私有目錄 android_app_private_dir=/data/app-private/
if (copy_and_append(&android_app_private_dir, &android_data_dir, PRIVATE_APP_SUBDIR) < 0) {
return -1;
}
// Get the android app native library directory.
// app 本地庫目錄 /data/app-lib/
if (copy_and_append(&android_app_lib_dir, &android_data_dir, APP_LIB_SUBDIR) < 0) {
return -1;
}
// Get the sd-card ASEC mount point.
// 從環(huán)境變量中取得sd-card ASEC 掛載點班缰,在啟動腳本init.rc中也有配置:
// export ASEC_MOUNTPOINT /mnt/asec/ 因此android_asec_dir=/mnt/asec/
if (get_path_from_env(&android_asec_dir, "ASEC_MOUNTPOINT") < 0) {
return -1;
}
// Get the android media directory.
// 多媒體目錄 /data/media
if (copy_and_append(&android_media_dir, &android_data_dir, MEDIA_SUBDIR) < 0) {
return -1;
}
// Get the android external app directory.
// 外部app 目錄/mnt/expand
if (get_path_from_string(&android_mnt_expand_dir, "/mnt/expand/") < 0) {
return -1;
}
// Take note of the system and vendor directories.
// 系統(tǒng)和廠商目錄
android_system_dirs.count = 4;
android_system_dirs.dirs = (dir_rec_t*) calloc(android_system_dirs.count, sizeof(dir_rec_t));
if (android_system_dirs.dirs == NULL) {
ALOGE("Couldn't allocate array for dirs; aborting\n");
return -1;
}
dir_rec_t android_root_dir;
// 目錄 /system/app
if (get_path_from_env(&android_root_dir, "ANDROID_ROOT") < 0) {
ALOGE("Missing ANDROID_ROOT; aborting\n");
return -1;
}
// 目錄 /system/app
android_system_dirs.dirs[0].path = build_string2(android_root_dir.path, APP_SUBDIR);
android_system_dirs.dirs[0].len = strlen(android_system_dirs.dirs[0].path);
// 目錄 /system/app-lib
android_system_dirs.dirs[1].path = build_string2(android_root_dir.path, PRIV_APP_SUBDIR);
android_system_dirs.dirs[1].len = strlen(android_system_dirs.dirs[1].path);
// 目錄 /vendor/app/
android_system_dirs.dirs[2].path = strdup("/vendor/app/");
android_system_dirs.dirs[2].len = strlen(android_system_dirs.dirs[2].path);
// 目錄 /oem/app/
android_system_dirs.dirs[3].path = strdup("/oem/app/");
android_system_dirs.dirs[3].len = strlen(android_system_dirs.dirs[3].path);
return 0;
}
2.2贤壁、 initialize_directories()方法
代碼在installd.cpp 406行
int initialize_directories() {
int res = -1;
// Read current filesystem layout version to handle upgrade paths
// 讀取當(dāng)前文件系統(tǒng)版本
char version_path[PATH_MAX];
snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path);
int oldVersion;
if (fs_read_atomic_int(version_path, &oldVersion) == -1) {
oldVersion = 0;
}
int version = oldVersion;
// /data/user
// 目錄 /data/user
char *user_data_dir = build_string2(android_data_dir.path, SECONDARY_USER_PREFIX);
// /data/data
// 目錄 /data/data
char *legacy_data_dir = build_string2(android_data_dir.path, PRIMARY_USER_PREFIX);
// /data/user/0
// 目錄/data/user/0
char *primary_data_dir = build_string3(android_data_dir.path, SECONDARY_USER_PREFIX, "0");
if (!user_data_dir || !legacy_data_dir || !primary_data_dir) {
goto fail;
}
// Make the /data/user directory if necessary
// 如果 /data/user 目錄不存在,則創(chuàng)建目錄
if (access(user_data_dir, R_OK) < 0) {
if (mkdir(user_data_dir, 0711) < 0) {
goto fail;
}
// 修改目錄權(quán)限及所有屬性
if (chown(user_data_dir, AID_SYSTEM, AID_SYSTEM) < 0) {
goto fail;
}
if (chmod(user_data_dir, 0711) < 0) {
goto fail;
}
}
// Make the /data/user/0 symlink to /data/data if necessary
// 將/data/user/0 鏈接到 /data/data
if (access(primary_data_dir, R_OK) < 0) {
if (symlink(legacy_data_dir, primary_data_dir)) {
goto fail;
}
}
if (version == 0) {
// Introducing multi-user, so migrate /data/media contents into /data/media/0
ALOGD("Upgrading /data/media for multi-user");
// Ensure /data/media
if (fs_prepare_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
goto fail;
}
// /data/media.tmp
char media_tmp_dir[PATH_MAX];
snprintf(media_tmp_dir, PATH_MAX, "%smedia.tmp", android_data_dir.path);
// Only copy when upgrade not already in progress
if (access(media_tmp_dir, F_OK) == -1) {
if (rename(android_media_dir.path, media_tmp_dir) == -1) {
ALOGE("Failed to move legacy media path: %s", strerror(errno));
goto fail;
}
}
// Create /data/media again
if (fs_prepare_dir(android_media_dir.path, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
goto fail;
}
if (selinux_android_restorecon(android_media_dir.path, 0)) {
goto fail;
}
// /data/media/0
char owner_media_dir[PATH_MAX];
snprintf(owner_media_dir, PATH_MAX, "%s0", android_media_dir.path);
// Move any owner data into place
if (access(media_tmp_dir, F_OK) == 0) {
if (rename(media_tmp_dir, owner_media_dir) == -1) {
ALOGE("Failed to move owner media path: %s", strerror(errno));
goto fail;
}
}
// Ensure media directories for any existing users
DIR *dir;
struct dirent *dirent;
char user_media_dir[PATH_MAX];
dir = opendir(user_data_dir);
if (dir != NULL) {
while ((dirent = readdir(dir))) {
if (dirent->d_type == DT_DIR) {
const char *name = dirent->d_name;
// skip "." and ".."
if (name[0] == '.') {
if (name[1] == 0) continue;
if ((name[1] == '.') && (name[2] == 0)) continue;
}
// /data/media/<user_id>
snprintf(user_media_dir, PATH_MAX, "%s%s", android_media_dir.path, name);
if (fs_prepare_dir(user_media_dir, 0770, AID_MEDIA_RW, AID_MEDIA_RW) == -1) {
goto fail;
}
}
}
closedir(dir);
}
version = 1;
}
2.3埠忘、execute(int s, char cmd[BUFFER_MAX])方法方法
代碼在installd.cpp 265行
/* Tokenize the command buffer, locate a matching command,
* ensure that the required number of arguments are provided,
* call the function(), return the result.
*/
static int execute(int s, char cmd[BUFFER_MAX])
{
char reply[REPLY_MAX];
char *arg[TOKEN_MAX+1];
unsigned i;
unsigned n = 0;
unsigned short count;
int ret = -1;
// ALOGI("execute('%s')\n", cmd);
/* default reply is "" */
reply[0] = 0;
/* n is number of args (not counting arg[0]) */
// arg[0] 為命令名稱脾拆,命令格式:[name arg1 arg2 arg3 arg4]
arg[0] = cmd;
// 計算命令參數(shù)個數(shù)
while (*cmd) {
if (isspace(*cmd)) {
*cmd++ = 0;
n++;
arg[n] = cmd;
if (n == TOKEN_MAX) {
ALOGE("too many arguments\n");
goto done;
}
}
if (*cmd) {
// 計算參數(shù)個數(shù)
cmd++;
}
}
// 根據(jù)命令名稱匹配命令數(shù)組cmds中命令
for (i = 0; i < sizeof(cmds) / sizeof(cmds[0]); i++) {
// 命令名稱比較
if (!strcmp(cmds[i].name,arg[0])) {
// 判斷該命令的參數(shù)個數(shù)是否滿足要求
if (n != cmds[i].numargs) {
// 參數(shù)不匹配,直接返回
ALOGE("%s requires %d arguments (%d given)\n",
cmds[i].name, cmds[i].numargs, n);
} else {
// 執(zhí)行相應(yīng)的命令
ret = cmds[i].func(arg + 1, reply);
}
goto done;
}
}
ALOGE("unsupported command '%s'\n", arg[0]);
done:
// 格式化返回結(jié)果
if (reply[0]) {
n = snprintf(cmd, BUFFER_MAX, "%d %s", ret, reply);
} else {
n = snprintf(cmd, BUFFER_MAX, "%d", ret);
}
if (n > BUFFER_MAX) n = BUFFER_MAX;
// 返回結(jié)果數(shù)據(jù)長度
count = n;
// ALOGI("reply: '%s'\n", cmd);
// 寫結(jié)果數(shù)據(jù)長度
if (writex(s, &count, sizeof(count))) return -1;
// 寫結(jié)果數(shù)據(jù)
if (writex(s, cmd, count)) return -1;
return 0;
}
(五)莹妒、總結(jié)
PMS啟動過程中使用了Installer的多個方法名船。Android APK的安裝和卸載主要是由Installer和Installd完成的。Installer是Java層提供的Java API接口动羽,Installd則是init進程啟動的Daemon Service包帚。Installer與Installd通過Socket通信,Installer是Socket的Client端运吓,Installd則是Socket的Server端渴邦。通過Socket通信,將Installer的API調(diào)用轉(zhuǎn)化為Installd中具體命令拘哨,這種轉(zhuǎn)化關(guān)系通過cmds[]數(shù)組配置和映射谋梭。Installer和Installd的關(guān)系如圖所示:
image.png
上一篇文章 APK安裝流程詳解4——安裝中關(guān)于so庫的哪些事
下一篇文章 APK安裝流程詳解6——PackageManagerService啟動前奏