LintCode 每日一題https://github.com/Jensenczx/...java
寫的越多發現的問題也就越多,不能否認,以前的博客如今看來有些東西本身理解的仍是頗有出入的,在後續過程當中,也是須要本身進一步的去改進。前幾篇寫了Android手機啓動流程還有Binder,以及服務的註冊和使用問題,如今要寫的是一個手機安裝包的生成過程和手機如何安裝一個安裝包的。android
從上圖咱們能夠看出APK生成和安裝的一個大體的流程,編譯,打包生成APK以後再簽名,而後能夠安裝到咱們的設備上,上圖是一個總體的歸納,咱們接下來須要對其進行一個更深刻的學習。git
重點在於apk的生成過程,整個打包的過程,能夠歸結爲下圖。
github
檢查AndroidManifest.xml,主要作一些檢查並使用parsePackage初始化並設置一些attribute,好比package, minSdkVersion, uses-sdk。服務器
添加被引用資源包
使用table.addIncludedResources(bundle, assets)添加被引用資源包,好比系統的那些android:命名空間下的資源。app
收集資源文件,處理overlay(重疊包,若是指定的重疊包有和當前編譯包重名的資源,則使用重疊包的):socket
將收集到的資源文件加到資源表(ResourceTable)
對res目錄下的各個資源子目錄進行處理,函數爲makeFileResources:makeFileResources會對資源文件名作合法性檢查,並將其添加到ResourceTable內。ide
編譯values資源並添加到資源表,在上一步添加過程當中,其實並無對values資源進行處理,由於values比較特殊,須要通過編譯以後,才能添加到資源表中。函數
給bag資源分配id,在繼續編譯其餘資源以前,咱們須要先給bag資源(attrs,好比orientation這種屬性的取值範圍定義的子元素)分配id,由於其餘資源可能對它們有引用。學習
編譯xml資源文件,最後咱們終於能夠編譯xml文件了,由於咱們已經爲它準備好了一切可能引用到的東西(value, drawable等)。程序會對layouts, anims, animators等逐一調用ResourceTable.cpp的,
進行編譯,內部流程又能夠分爲:解析xml文件,賦予屬性名稱資源id,解析屬性值,扁平化爲二進制文件。
編譯AndroidManifest.xml文,拿到AndroidManifest.xml文件,清空原來的數據,從新解,處理package name重載,把各類相對路徑的名字改成絕對路徑,編譯manifest xml文件,生成最終資源表.
9.生成R.java文件
生成咱們解壓後看到的那個resources.arsc:
aidl,全名Android Interface Definition Language,即Android接口定義語言。
輸入:aidl後綴的文件。
輸出:可用於進程通訊的C/S端java代碼,位於build/generated/source/aidl。
咱們有了R.java和aidl生成的Java文件,再加上工程的源代碼,如今可使用javac進行正常的java編譯生成class文件了。
輸入:java source的文件夾(另外還包括了build/generated下的:R.java, aidl生成的java文件,以及BuildConfig.java)。
輸出:對於gradle編譯,能夠在build/intermediates/classes裏,看到輸出的class文件。
源碼編譯以後,咱們可能還會對其進行代碼的混淆,混淆的做用是增長反編譯的難度,同時也將一些代碼的命名進行了縮短,減小代碼佔用的空間。混淆完成以後,會生成一個混淆先後的映射表,這個是用來在反應咱們的應用執行的時候的一些堆棧信息,能夠將混淆後的信息轉化爲咱們混淆前實際代碼中的內容。
調用dx.bat將全部的class文件(上一步生成的以及第三方庫的)轉化爲classes.dex文件,dx會將class轉換爲Dalvik字節碼,生成常量池,消除冗餘數據等。
打包生成APK文件。舊的apkbuilder腳本已經廢棄,如今都已經經過sdklib.jar的ApkBuilder類進行打包了。輸入爲咱們以前生成的包含resources.arcs的.ap_文件,上一步生成的dex文件,以及其餘資源如jni、jar包內的資源。
大體步驟爲
以包含resources.arcs的.ap_文件爲基礎,new一個ApkBuilder,設置debugMode
apkBuilder.addZipFile(f);
apkBuilder.addSourceFolder(f);
apkBuilder.addResourcesFromJar(f);
apkBuilder.addNativeLibraries(nativeFileList);
apkBuilder.sealApk(); // 關閉apk文件
generateDependencyFile(depFile, inputPaths, outputFile.getAbsolutePath());
對apk文件進行簽名。APK須要簽名才能在設備上進行安裝
不少時候咱們在逆向改完後,會由於沒有簽名文件致使最後的apk沒法正常使用,又細分爲本地驗證和服務器驗證。
調用buildtoolszipalign,對簽名後的apk文件進行對齊處理,使apk中全部資源文件距離文件起始偏移爲4字節的整數倍,從而在經過內存映射訪問apk文件時會更快。同時也減小了在設備上運行時的內存消耗。
這樣咱們的最終apk就生成完畢了。
安裝方式
系統程序安裝,開機時安裝,沒有安裝界面。
第一步,將apk文件解壓複製到程序目錄下(/data/app/);第二步,爲應用建立數據目錄(/data/data/package name/)、提取dex文件到指定目錄(/data/dalvik-cache/)、修改系統包管理信息。
由開機時啓動的PackageManagerService服務完成,會在啓動時掃描/system/app, vender/app, /data/app, /data/app-private並安裝。
PackageInstallerActivity
當Android系統請求安裝apk程序時,會啓動這個Activity,並經過Intent讀取傳來的apk信息。下面是apk安裝的具體過程。
解析過程會首先讀取AndroidManifest.xml獲取程序包名以構建Package對象,而後再處理manifest的其餘標籤包括四大組件,並把信息全都存到Package對象裏面。
首先檢測該程序是否已安裝,是則彈框提示是否替換程序,不然直接調用startInstallConfirm(),作UI初始化和事件綁定,因而當咱們點擊安裝的時候則會觸發onClick下的OK按鈕事件:
不管是替換仍是新安裝,都會調用scanPackageLI(),而後跑去scanPackageDirtyLI,它會判斷是否爲系統程序,解析apk程序包,檢查依賴庫,驗證簽名,檢查sharedUser簽名、權限衝突、ContentProvider衝突,更新native庫目錄文件(檢測abi),進行dexopt,殺掉現有進程(僅對覆蓋安裝的場景)等等,最後調用createDataDirsLI()進行實際安裝:
4.執行完畢後,經過socket回傳結果,而PackageInstaller根據返回結果作對應處理並顯示給用戶,至此爲止,整個apk安裝過程結束。
包名簽名相關
android系統使用包名(package name)來斷定應用程序的同一性,可是因爲包名能夠由開發者自由設置,爲了保護應用程序不被其餘開發者開發的同包名應用覆蓋,用於發佈的Android應用程序須要加上開發者簽名。在應用程序被升級的時候,Android系統將會驗證被升級的應用程序包與升級後的應用程序包是否使用了一樣的開發者簽名,若是一致,該應用程序能夠被升級;若是不一致,那麼將被視爲非同一開發者開發的應用程序,用戶須要先卸載已經安裝的應用而後再安裝新應用,在卸載的過程當中,應用在android系統中所保存的設置信息(SavedPreferences)將被刪除,以保護應用本地保存的資料不被盜取。綜上,應用是否能夠覆蓋的方式是對於包名的判斷,而後是對於該APK簽名的判斷。
上述爲本身在看了兩個博客後,根據博客內容,本身梳理,回顧的一個過程。省略了其中的代碼細節。