APK文件安裝流程

概述:
android和你通常理解的程序路徑是不一樣的脱拼,android應用安裝完畢后,會存儲在/data/app或者/system/app目錄中,當程序運行時坦辟,所需要的layout文件,drawable文件等等需要從該目錄中的原文件中進行讀取声离。首先加載resource.asc芒炼,然后根據(jù)id值尋找相應的資源,而lib目錄等特殊文件會存放 /data/data/你的應用包名此路徑下术徊。

  • 安裝分類
  • 幾個相關目錄
  • APK安裝流程
  • APK解析流程
  • 調(diào)用PMS

安裝分類

根據(jù)觸發(fā)條件可分為三類:

分類 說明
系統(tǒng)應用安裝 沒有安裝界面本刽,在開機時自動完成
網(wǎng)絡下載應用安裝 沒有安裝界面,在應用市場完成
ADB命令安裝 沒有安裝界面赠涮,通過命令直接安裝
外部設備安裝 有安裝界面子寓,通過SD卡等外部設備安裝,由packageInstaller處理安裝邏輯

幾個相關目錄

以下是比較關鍵的四個目錄:
(1)system/app : 系統(tǒng)自帶的應用程序笋除,獲得root權限才能刪除
(2)data/app : 用戶程序安裝目錄斜友,安裝時會把apk文件復制到此目錄下
(3)data/data : 存放應用程序的數(shù)據(jù)
(4)data/dalvik-cache : 將apk中的dex文件安裝到該目錄下(dex文件是dalvik虛擬機的可執(zhí)行文件,大小約為原始apk的四分之一)

APK安裝流程

如下是比較關鍵的四個目錄:
(1)拷貝apk到指定的目錄
默認情況下垃它,用戶安裝的apk首先會拷貝到/data/app下鲜屏,用戶有訪問/data/app目錄的權限,但系統(tǒng)出廠的apk文件會被放到/system分區(qū)下国拇,包括/system/app洛史,/system/vendor/app,以及/system/priv-app等酱吝。該分區(qū)需要root權限的用戶才能訪問也殖。

(2)解壓apk、拷貝文件务热、創(chuàng)建應用的數(shù)據(jù)目錄
為了加快APP的啟動速度忆嗜,apk在安裝的時候己儒,會首先將APP的可執(zhí)行文件(dex)拷貝到/data/dalvik-cache目錄下,緩存起來霎褐。再在/data/data/目錄下創(chuàng)建應用程序的數(shù)據(jù)目錄(以應用包名命令)址愿,用來存放應用的數(shù)據(jù)庫、cache冻璃、二進制的so動態(tài)庫等响谓。資源文件還在原來的APK文件里,并沒有拷貝過來

(3)解析apk的AndroidManifest.xml文件
在安裝apk的過程中省艳,會解析apk的AndroidManifest.xml文件娘纷,將apk的權限、應用包名跋炕、apk的安裝位置赖晶、版本、userID等重要信息保存在/data/system/packages.xml文件中辐烂。這些操作都是在PackageManagerService中完成的遏插,并將解析出的四大組件信息注冊到PackageManagerService中。

(4)顯示icon圖標
應用程序經(jīng)過PMS中的邏輯處理后纠修,相當于已經(jīng)注冊好了胳嘲,如果想要在Android桌面上看到icon圖標,則需要Launcher將系統(tǒng)中已經(jīng)安裝的程序展現(xiàn)在桌面上扣草。

APK解析流程

解析AndroidManifest.xml文件
APK文件里包含了一個配置文件AndroidManifest.xml了牛,Android應用程序的解析過程就是解析這個xml文件的過程。APK解析是從PackageManagerService的scanPackageLI開始的辰妙,而該方法內(nèi)部又調(diào)用的是scanPackageDirtyLI()方法鹰祸,我們來看一下這個方法的實現(xiàn)。

public class PackageManagerService extends IPackageManager.Stub {
    
       private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg, int parseFlags,
               int scanFlags, long currentTime, UserHandle user) throws PackageManagerException {
           //...
           // writer
           synchronized (mPackages) {
               // 驗證已注冊的ContentProvider是否有其他同名密浑,做沖突檢測蛙婴。
               if ((scanFlags & SCAN_NEW_INSTALL) != 0) {
                   final int N = pkg.providers.size();
                   int i;
                   for (i=0; i<N; i++) {
                       PackageParser.Provider p = pkg.providers.get(i);
                       if (p.info.authority != null) {
                           String names[] = p.info.authority.split(";");
                           for (int j = 0; j < names.length; j++) {
                               if (mProvidersByAuthority.containsKey(names[j])) {
                                   PackageParser.Provider other = mProvidersByAuthority.get(names[j]);
                                   final String otherPackageName =
                                           ((other != null && other.getComponentName() != null) ?
                                                   other.getComponentName().getPackageName() : "?");
                                   throw new PackageManagerException(
                                           INSTALL_FAILED_CONFLICTING_PROVIDER,
                                                   "Can't install because provider name " + names[j]
                                                   + " (in package " + pkg.applicationInfo.packageName
                                                   + ") is already used by " + otherPackageName);
                               }
                           }
                       }
                   }
               }
           }
         
           if (mPlatformPackage == pkg) {
              //...
           } else {
               // This is a normal package, need to make its data directory.
               dataPath = getDataPathForPackage(pkg.packageName, 0);
               if (dataPath.exists()) {
                   //...
               } else {
                   //invoke installer to do the actual installation
                   //這里創(chuàng)建了應用數(shù)據(jù)目錄,用于存放用戶數(shù)據(jù)
                   int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid,
                                              pkg.applicationInfo.seinfo);
                   //...
               }
             
           }
         
           // We also need to dexopt any apps that are dependent on this library.  Note that
           // if these fail, we should abort the install since installing the library will
           // result in some apps being broken.
           if (clientLibPkgs != null) {
               if ((scanFlags & SCAN_NO_DEX) == 0) {
                   for (int i = 0; i < clientLibPkgs.size(); i++) {
                       PackageParser.Package clientPkg = clientLibPkgs.get(i);
                       if (performDexOptLI(clientPkg, null /* instruction sets */, forceDex,
                               (scanFlags & SCAN_DEFER_DEX) != 0, false) == DEX_OPT_FAILED) {
                           throw new PackageManagerException(INSTALL_FAILED_DEXOPT,
                                   "scanPackageLI failed to dexopt clientLibPkgs");
                       }
                   }
               }
           }
         
           // writer
           synchronized (mPackages) {
               //...
               // 以下對四大組件進行注冊
               int N = pkg.providers.size();
               StringBuilder r = null;
               int i;
               for (i=0; i<N; i++) {
                   PackageParser.Provider p = pkg.providers.get(i);
                   p.info.processName = fixProcessName(pkg.applicationInfo.processName,
                           p.info.processName, pkg.applicationInfo.uid);
                   //注冊Content Provider
                   mProviders.addProvider(p);
                   //...
               }
               //...
           }
       }
       //...
   }
}

scanPackageDirtyLI是一個上千行的函數(shù)尔破,它主要完成的工作如下所示:
(1)調(diào)用PackageParser的parsePackage()方法解析AndroidMainfest.xml文件敬锐,主要包括四大組件、權限信息呆瞻、用戶ID台夺,其他use-feature、shared-userId痴脾、use-library等 信息颤介,并保存到PackageManagerService相應的成員變量中。
(2)調(diào)用簽名驗證方法verifySignaturesLP()進行簽名驗證,驗證失敗的無法進行安裝滚朵。
(3)調(diào)用createDataDirsDirtyLI()方法創(chuàng)建應用目錄/data/data/package冤灾,同時將APK中提取的DEX文件保存到/data/dalvik-cache中。
(4)調(diào)用performDexOptLI()方法執(zhí)行dexopt操作辕近。

解析 APK 文件

Apk的解析是PackageParser的parsePackage()函數(shù)來完成的韵吨,我們來看看它的實現(xiàn)。

public class PackageParser {
    
     public Package parsePackage(File packageFile, int flags) throws PackageParserException {
         if (packageFile.isDirectory()) {
             return parseClusterPackage(packageFile, flags);
         } else {
             return parseMonolithicPackage(packageFile, flags);
         }
     }
     
     private Package parseClusterPackage(File packageDir, int flags) throws PackageParserException {
             //...
             
             //初始化AssetManager
             final AssetManager assets = new AssetManager();
             try {
                 //...
                 //解析Base APk移宅,解析AndroidManifest.xml
                 final Package pkg = parseBaseApk(baseApk, assets, flags);
                 if (pkg == null) {
                     throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
                             "Failed to parse base APK: " + baseApk);
                 }
     
                 //如果splitName不為空归粉,則循環(huán)解析Split Apk
                 if (!ArrayUtils.isEmpty(lite.splitNames)) {
                     final int num = lite.splitNames.length;
                     pkg.splitNames = lite.splitNames;
                     pkg.splitCodePaths = lite.splitCodePaths;
                     pkg.splitRevisionCodes = lite.splitRevisionCodes;
                     pkg.splitFlags = new int[num];
                     pkg.splitPrivateFlags = new int[num];
     
                     for (int i = 0; i < num; i++) {
                         //解析
                         parseSplitApk(pkg, i, assets, flags);
                     }
                 }
     
                 pkg.setCodePath(packageDir.getAbsolutePath());
                 pkg.setUse32bitAbi(lite.use32bitAbi);
                 return pkg;
             } finally {
                 IoUtils.closeQuietly(assets);
             }
         }
}

注:Split APK是Google為解決65535上限以及APK越來越大的問題而提出的一種方案,它可以將一個龐大的APK按照屏幕密度漏峰、ABI等形式拆分成多個獨立的APK糠悼,這些APK共享相同的data、cache目錄浅乔。 共享相同的進程倔喂,共享相同的包名。它們還可以使用各自的資源靖苇,并且繼承了Base APK里的資源席噩。

該方法調(diào)用parseBaseApk()去解析AndroidManifest.xml,AndroidManifest.xml也是xml文件贤壁,當然也使用XmlResourceParser來解析班挖。這個解析相應標簽并保存到PackageManagerService對應的成員變量中去。

調(diào)用PMS流程

在應用中,我們通常是調(diào)用Context的getPackageManager()方法返回PackageManager對象芯砸,這里返回的實際是ApplicationPackageManager對象,這個對象創(chuàng)建時使用IPackageManager對象作為參數(shù),IPackageManager是PackageManagerService實現(xiàn)的AIDL接口,通過binder來獲取PackageManagerService對象。

因此ApplicationPackageManager是PackageManagerService的代理對象给梅,ApplicationPackageManager繼承自PackageManager假丧,PackageManager中定義了可以操作PackageManagerService的接口,在PackageManager中,實現(xiàn)對apk安裝的方法是installExistingPackage和installPackageWithVerificationAndEncryption(前者是通過包名去升級應用,后者是安裝新的應用)

尊重作者动羽,尊重原創(chuàng)包帚,參考文章:
https://blog.csdn.net/wy391920778/article/details/79881142
https://blog.csdn.net/qq_25804863/article/details/48565569

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市运吓,隨后出現(xiàn)的幾起案子渴邦,更是在濱河造成了極大的恐慌,老刑警劉巖拘哨,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谋梭,死亡現(xiàn)場離奇詭異,居然都是意外死亡倦青,警方通過查閱死者的電腦和手機瓮床,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人隘庄,你說我怎么就攤上這事踢步。” “怎么了丑掺?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵获印,是天一觀的道長。 經(jīng)常有香客問我街州,道長兼丰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任菇肃,我火速辦了婚禮地粪,結果婚禮上,老公的妹妹穿的比我還像新娘琐谤。我一直安慰自己蟆技,他們只是感情好,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布斗忌。 她就那樣靜靜地躺著质礼,像睡著了一般。 火紅的嫁衣襯著肌膚如雪织阳。 梳的紋絲不亂的頭發(fā)上眶蕉,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機與錄音唧躲,去河邊找鬼造挽。 笑死,一個胖子當著我的面吹牛弄痹,可吹牛的內(nèi)容都是我干的饭入。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼肛真,長吁一口氣:“原來是場噩夢啊……” “哼谐丢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚓让,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤乾忱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后历极,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窄瘟,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年趟卸,在試婚紗的時候發(fā)現(xiàn)自己被綠了寞肖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片纲酗。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖新蟆,靈堂內(nèi)的尸體忽然破棺而出觅赊,到底是詐尸還是另有隱情,我是刑警寧澤琼稻,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布吮螺,位于F島的核電站,受9級特大地震影響帕翻,放射性物質(zhì)發(fā)生泄漏鸠补。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一嘀掸、第九天 我趴在偏房一處隱蔽的房頂上張望紫岩。 院中可真熱鬧,春花似錦睬塌、人聲如沸泉蝌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽勋陪。三九已至,卻和暖如春硫兰,著一層夾襖步出監(jiān)牢的瞬間诅愚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工劫映, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留违孝,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓泳赋,卻偏偏與公主長得像雌桑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子摹蘑,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

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