APK安裝流程詳解5——Installer棍苹、InstallerConnection和Installd守護進程

APK安裝流程系列文章整體內(nèi)容如下:

本片文章的主要內(nèi)容如下:

  • 1、Installer簡介
  • 2、InstallerConnection簡介
  • 3、Installd守護進程

一、Installer簡介

Installer.java

(一)柠横、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.java

(一)、先來看下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í)行程序包的安裝與卸載的。

如下圖


PackageManagerServcie與Installd.png

PackageManagerService是通過套接字方式訪問installd服務(wù)進程的嫁审,

(二)、為什么要用intalld

有人會問了赖晶,PackageManageService這么大的組件了律适,為什么還需要intalld這個守護進程?這是因為權(quán)限的問題辐烂,PackageManagerService只有system權(quán)限。installd卻是具有root權(quán)限

如下圖:


Installd權(quán)限.png

(三)捂贿、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上查看


image.png

(四)、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啟動前奏

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市倦青,隨后出現(xiàn)的幾起案子瓮床,更是在濱河造成了極大的恐慌,老刑警劉巖产镐,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隘庄,死亡現(xiàn)場離奇詭異,居然都是意外死亡癣亚,警方通過查閱死者的電腦和手機丑掺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來述雾,“玉大人街州,你說我怎么就攤上這事〔C希” “怎么了唆缴?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長黍翎。 經(jīng)常有香客問我面徽,道長,這世上最難降的妖魔是什么匣掸? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任斗忌,我火速辦了婚禮质礼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘织阳。我一直安慰自己,他們只是感情好砰粹,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布唧躲。 她就那樣靜靜地躺著,像睡著了一般碱璃。 火紅的嫁衣襯著肌膚如雪弄痹。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天嵌器,我揣著相機與錄音肛真,去河邊找鬼。 笑死爽航,一個胖子當(dāng)著我的面吹牛蚓让,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播讥珍,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼历极,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了衷佃?” 一聲冷哼從身側(cè)響起趟卸,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎氏义,沒想到半個月后锄列,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡惯悠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年邻邮,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吮螺。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡饶囚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸠补,到底是詐尸還是另有隱情萝风,我是刑警寧澤,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布紫岩,位于F島的核電站规惰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏泉蝌。R本人自食惡果不足惜歇万,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一揩晴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧贪磺,春花似錦硫兰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至刹前,卻和暖如春泳赋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背喇喉。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工祖今, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拣技。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓千诬,卻偏偏與公主長得像,于是被迫代替她去往敵國和親过咬。 傳聞我的和親對象是個殘疾皇子大渤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容