APK,全稱Android Application Package
,即Android應用程序包,是Android系統使用的一種應用程序包文件格式,它的作用是將Android程序
和資源
整合在一起,以便Android程序能在Android設備上正常運行。簡單地說,就是一個Android應用程序的代碼要想在Android設備上運行,必須先進行編譯,然後被打包成一個被Android系統所能識別的文件纔可以被運行,而這種能被Android系統識別並運行的文件格式就是"APK
",而文件後綴爲".apk
"。
1. APK打包過程
1.1 APK文件結構
APK文件本質上是一個壓縮文件,我們將一個APK文件進行解壓,它主要包含以下文件:
assets目錄
:存放原生的靜態資源文件,如圖片、JSON配置文件、二進制數據、HTML5等。系統在編譯時不會編譯該目錄下的資源,因此不會像res目錄樣能被直接通過R.xxx.xxx進行訪問,而是需要通過AssetManager以二進制流的形式來讀取資源。注意:res/raw目錄存儲的也是原生的資源文件,但是它能夠被Android映射成R.xxx.xxx資源,它們的不同之處在於assets目錄下的文件結構支持樹形結構目錄,而res/raw不支持。另外,assets目錄下的單個文件儘量在1MB以下,而res/raw目錄下的單個文件可以任意MB。
// 訪問assets目錄下的資源
AssetManager manager = this.getAssets();
InputStream in = manager.open("image/logo.jpg");//注意路徑
Bitmap bitmap = BitmapFactory.decodeStream(in);
// 訪問res/raw目錄下的資源
InputStream is = this.getResources().openRawResource(R.raw.logo);
Bitmap image = BitmapFactory.decodeStream(is);
lib目錄
:存放應用程序依賴的不同架構(ABI)的.so文件。res目錄
:存放應用的資源文件,包括圖片、字符串、顏色、尺寸、動畫文件、佈局文件等資源。這個目錄下的所有資源都會出現在資源清單文件R.java的索引中。MERA-INF目錄
:保存應用程序的簽名信息,簽名信息可以驗證APK文件的完整性。MANIFEST.MF
:該文件保存了APK包中每個文件得名字及其SHA1哈希值;CERT.SF
:該文件保存了MANIFEST.MF文件的哈希值及其文件中每一個哈希項的哈希值;CERT.RSA
:該文件保存了APK包的簽名和證書的公鑰信息;
AndroidManifest.xml
:Android 應用的配置文件,用於描述 Android 應用的整體情況。每個 Android 應用必須包含一個 AndroidManifest.xml 文件。AndroidManifest.xml 包含了 Android 四大組件的註冊信息,權限聲明以及 Android SDK 版本信息等,該文件將會被編譯成二進制文件。classes.dex
:應用程序可執行文件,即Dalvik字節碼文件。APK中可能包含多個dex文件,具體要看Android程序的所有方法數是否超過65535,如果超過就進行分包處理,就會出現多個dex文件的情況。Resources.arsc
:資源配置文件。該文件用於記錄資源文件位置和資源ID之間的映射關係,以便系統能夠根據ID去尋找對應的資源路徑。該ID實際上與R.java中存儲的是一樣的,但是R.java只是便於開發者調用資源且保證編譯程序不報錯,而實際上在程序運行時系統需要根據這個ID從Resources.arsc文件保存的對應的路徑中獲取資源文件。
1.2 APK打包過程
一個Android項目主要由Java代碼
、AIDL文件
、AndroidManifest.xml
、第三方庫.class文件
以及res目錄資源
文件(如圖片、字符串、尺寸、佈局…)等模塊組成,因此對於APK的打包過程,主要就是對這些內容進行編譯打包。下圖是Google官方提供的詳細的APK構建過程:
從上圖可知,APK的構建過程主要經歷七個階段,其中淺色方框表示每個階段的輸入或輸出內容,綠色橢圓框表示所用到的工具,這些工具大部分位於Android SDK目錄的build-tools\版本號
目錄下。下面我們對上述七個階段作詳細的解釋:
- (1)
aapt.exe
,打包資源
該階段的目的是打包res目錄資源文件
和AndroidManifest.xml
,使用的工具是aapt.exe
,最終生成的中間文件爲R.java類
、二進制的resource.arsc
、res文件夾
(包括二進制的xml、沒被改變的圖片和res/raw文件等)、二進制的AndroidManifest.xml文件
、沒有改變的assets文件夾
。另外,aapt
在打包資源文件之前會檢測AndroidManifest.xml、res目錄下資源文件名的合法性,如果這兩部分出現不符合要求情況話,Android Studio會直接報錯無法編譯。因此,如果我們遇到編譯錯誤帶有aapt信息時,基本上可以確定問題爲AndroidManifest.xml配置錯誤,或者res目錄下資源命名不合法或不存在。
- (2)
aidl.exe
,將.aidl文件轉換爲.java文件
該階段的目的是將Android工程src/main/aidl目錄下的所有.aidl
文件轉換爲.java
文件,生成的.java
文件位於build/generated/source/aidl目錄下,使用的工具是aidl.exe
。在從Android6.0源碼的角度剖析Binder工作原理一文中,我們曾介紹到AIDL
(全稱Android Interface Definition Language,Android接口定義語言),是Android系統爲了便於開發具備跨進程通信的應用,專門提供的且用於自動生成Java層Binder通信框架的技術。因此,轉換得到的Java代碼將用於進程間通信的C/S端。
- (3)
javac.exe
,編譯Java源文件爲字節碼文件
該階段的目的是將所有模塊src/main/java目錄下的java源文件、aapt生成的R.java文件、aidl生成的.java
文件以及BuildConfig.java文件編譯成對應的.class字節碼文件,使用的工具是jdk/bin/javac.exe。其中,BuildConfig.java文件是根據build.gradle配置自動生成的,我們可以使用它的DEBUG字段來控制日誌的輸出。BuildConfig.java源碼如下:
//..\app\build\generated\source\buildConfig\...\BuildConfig.java
public final class BuildConfig {
// 該在開發中可控制日誌輸出
// 不需要自己手動添加Debug開發,系統會依據BUILD_TYPE自動設定
// BUILD_TYPE="release"時,DEBUG=false
public static final boolean DEBUG = Boolean.parseBoolean("true");
// 包名
public static final String APPLICATION_ID = "com.jiangdg.jjusbcamera";
// 當前編譯類型,debug或release
public static final String BUILD_TYPE = "debug";
// 產品(渠道包的名稱)
public static final String FLAVOR = "";
// 版本號
public static final int VERSION_CODE = 2;
// 版本名稱
public static final String VERSION_NAME = "1.0.1.20191104";
}
- (4)
dx.bat
,將所有.class文件轉換爲.dex文件
該階段的目的是將所有的.class字節碼
文件(包括第三方庫)轉換爲一個.dex字節碼
文件,使用的工具是dx.bat。從Android性能優化(2)一文中可知,由於.class
文件存在較大的冗餘、各個類相互獨立且結構也不緊湊,不適合內存和處理器速度有限的系統,因此Android系統選用專門的虛擬機Dalvik或ART來執行專門的字節碼文件,這類字節碼文件即爲.dex
文件,它是在.class
字節碼文件的基礎上經過DEX工具
壓縮、優化後得到的,適用於內存和處理器速度有限的系統。.class
文件和.dex
文件結構對比圖:
- (5)
apkbuilder
,打包生成APK
該階段目的是將前幾步生成的.dex文件、resources.arsc文件、被編譯後的res目錄(res/raw目錄下的資源沒有被編譯)和AndroidManifest.xml以及其他資源文件(assets文件夾)打包生成一個.apk
文件,使用的工具是sdkpath/tools/sdklib-xxx.jar
。
- (6)
apksigner.bat
,對APK文件簽名
該階段是對打包生成的.apk
文件進行簽名,得到的是被簽名後的.apk
文件,使用的工具是apksigner.bat。簽名是一個apk身份的證明,Android系統在安裝apk的時候,首先會檢驗apk的簽名,如果發現簽名文件不存在或者校驗簽名失敗,就會拒絕安裝。對一個apk文件簽名後,apk文件根目錄下回增加META-INF目錄,該目錄下有三個文件:CERT.RSA、CERT.SF和MANIFEST.MF。
- (7)
zipalign.exe
,對齊優化
該階段的目的是對簽名後的.apk
文件進行對齊處理,得到的是對齊優化後的.apk
文件,使用的工具是zipalign.exe。需要注意的是,對齊優化存在於在release打包時,Zipalign是一個android平臺上整理APK文件的工具,它對apk中未壓縮的數據進行4字節對齊,對齊後就可以使用mmap函數讀取文件,可以像讀取內存一樣對普通文件進行操作。如果沒有4字節對齊,就必須顯式的讀取,這樣比較緩慢並且會耗費額外的內存。
2. APK安裝過程
在Android設備上安裝一個APK,是從執行以下一段程序開始的,該段程序採用隱式Intent的方式,通過指定intent的type屬性爲"application/vnd.android.package-archive"
來啓動Android系統中一個名爲PackageInstaller
的系統應用,PackageInstaller
是系統內置的應用程序,用於安裝和卸載應用。
// 啓動PackageInstaller系統應用
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setDataAndType(Uri.fromFile(new File(apkPath)),
"application/vnd.android.package-archive");
context.startActivity(intent);
// Android8.0\packages\apps\PackageInstaller\AndroidManifest.xml
...
<activity android:name=".InstallStart"
android:exported="true"
android:excludeFromRecents="true">
<intent-filter android:priority="1">
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:mimeType="application/vnd.android.package-archive" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.intent.action.INSTALL_PACKAGE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:scheme="package" />
<data android:scheme="content" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.content.pm.action.CONFIRM_PERMISSIONS" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
...
從PackageInstaller
的AndroidManifest.xml可知,在PackageInstaller
中能夠隱式匹配application/vnd.android.package-archive
的Activity爲InstallStart
,也就是說InstallStart
是PackageInstaller
應用的入口。InstallStart
並沒有顯示任何界面,而是直接啓動了一個名爲InstallStaging
的Activity,它首先會將content協議的Uri轉換爲File協議,然後再調起PackageInstallerActivity
進入安裝流程。因爲對於Android7.0以上系統來說,會使用FileProvider來處理URI ,FileProvider會隱藏共享文件的真實路徑,將路徑轉換成content://Uri路徑。PackageInstallerActivity
就是我們安裝APK時彈出來的詢問是否安裝對話框,當點擊安裝或"OK"時,它會啓動名爲InstallInstalling
的Activity,該Activity就是我們看到的安裝進度對話框。InstallInstalling
主要用於向包管理器發送安裝包的信息並處理包管理的回調,即通過IO流的形式將APK的信息寫入到PackageInstaller.Session中,PackageInstaller.Session可理解爲PackageInstallerSession代理對象,真正的實現位於PackageInstallerSession對象中,也就是說,這個發送APK信息的過程實質上是一個基於Binder機制的跨進程通信。至此,APK安裝執行流程進入進入Framework層,PackageInstaller進程的初始化工作就執行完畢了。在PackageInstallerSession中,它會最終將APK信息交給PackageManagerService(PMS)處理,PMS通過向PackageHandler發送消息來驅動APK的拷貝和安裝工作,其中APK的拷貝由DefaultContainerService
完成,而安裝由PackageManagerService
完成。PackageInstaller進程初始化過程時序圖如下:
這裏總結下PackageInstaller進程初始化工作有哪些:
- 向用戶提供APK安裝的交互界面,比如安裝確認、安裝進度;
- 將content協議的Uri轉換爲File協議,以獲取APK存儲的真實路徑;
- 將APK文件以IO流的形式提交給PMS,進入下一步處理;
2.1 拷貝APK
APK拷貝過程時序圖如下:
從上述時序圖可知,APK的拷貝過程是從PMS的installStage方法
開始的,該方法會向PackageHandler發送一個INIT_COPY
消息並傳輸一個InstallParams對象(這個對象後面有用
),PackageHandler是PMS的一個內部類,專門用於處理PMS中發送過來的消息,而接收處理消息的方法PackageHandler的handleMessage
,該方法會繼續調用doHandleMessage
方法實現具體的處理操作。PackageHandler接收並處理INIT_COPY消息源碼如下:
// PackageManagerService.installStage
void installStage(String packageName, File stagedDir, String stagedCid,
IPackageInstallObserver2 observer, PackageInstaller.SessionParams
sessionParams,String installerPackageName, int installerUid, UserHandle user,
Certificate[][] certificates) {
...
final Message msg = mHandler.obtainMessage(INIT_COPY);
final int installReason = fixUpInstallReason(installerPackageName, installerUid,
sessionParams.installReason);
// 創建InstallParams
final InstallParams params = new InstallParams(..);
msg.obj = params;
// 發送拷貝消息INIT_COPY
mHandler.sendMessage(msg);
}
// PackageManagerService.PackageHandler內部類
class PackageHandler extends Handler {
...
private boolean connectToService() {
if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to bind to" +
" DefaultContainerService");
// 綁定啓動com.android.defcontainer.DefaultContainerService服務
// 並註冊連接事件監聽器mDefContainerConn
// 該服務的目的是拷貝APK
Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
if (mContext.bindServiceAsUser(service, mDefContainerConn, // 註釋3
Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
mBound = true;
return true;
}
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
return false;
}
...
// 處理Handler消息
public void handleMessage(Message msg) {
try {
doHandleMessage(msg);
} finally {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
}
}
void doHandleMessage(Message msg) {
switch (msg.what) {
// INIT_COPY=5
// 處理拷貝消息
case INIT_COPY: {
HandlerParams params = (HandlerParams) msg.obj;
int idx = mPendingInstalls.size();
// mBound服務綁定狀態,默認爲false
if (!mBound) { // 註釋1
// 連接到服務
if (!connectToService()) { // 註釋2
...
}
}
}
...
}
}
在PackageHandler的doHandleMessage方法中,當INIT_COPY消息到來時,會直接進入INIT_COPY消息處理流程。首先,註釋1
處會判斷標誌位mBound的狀態,這個標誌位代表綁定到DefaultContainerService服務的狀態,默認值爲false表示未綁定。如果綁定成功,mBound會被置true,這裏我們只考慮未綁定的情況。然後,註釋2
處爲當mBound爲false時,會去PackageHandler的connectToService方法,該方法會去綁定啓動DefaultContainerService服務並將mBound置true,如註釋3
處。由於APK拷貝是一個比較耗時的操作,因此這個DefaultContainerService運行在一個單獨的進程之中,將由它來完成APK的檢查與拷貝工作。另外,PMS爲了監聽綁定DefaultContainerService服務的啓動狀態,在調用bindServiceAsUser時註冊了一個DefaultContainerConnection對象,該對象繼承於ServiceConnection,當綁定服務成功後,其onServiceConnected方法會被調用。DefaultContainerConnection源碼如下:
// PackageManagerService.DefaultContainerConnection
class DefaultContainerConnection implements ServiceConnection {
// 與DefaultContainerService建立連接後被調用
// 向mHandler發送MCS_BOUND消息
public void onServiceConnected(ComponentName name, IBinder service) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceConnected");
// DefaultContainerService的Binder代理對象
final IMediaContainerService imcs = IMediaContainerService.Stub
.asInterface(Binder.allowBlocking(service));
mHandler.sendMessage(mHandler.obtainMessage(MCS_BOUND, imcs));
}
public void onServiceDisconnected(ComponentName name) {
if (DEBUG_SD_INSTALL) Log.i(TAG, "onServiceDisconnected");
}
}
當PMS綁定啓動DefaultContainerService服務後,DefaultContainerConnection連接事件監聽器的onServiceConnected方法會被回調,這就意味着SystemManager進程(PMS服務由此進程啓動
)和DefaultContainerService進程之間基於Binder機制的跨進程通信鏈路建立完畢。在onServiceConnected方法中,它首先獲取IMediaContainerService.Stub這個實體Binder代理對象imcs,然後再向PackageHandler發送一個MCS_BOUND
消息。接下來,我們看PackageHandler接收到MCS_BOUND
消息後,做了些什麼。
// PackageManagerService.PackageHandler.doHandleMessage
void doHandleMessage(Message msg) {
switch (msg.what) {
case MCS_BOUND: {
// 取出IMediaContainerService.Stub的Binder代理對象
if (msg.obj != null) {
mContainerService = (IMediaContainerService) msg.obj;
}
// mContainerService不爲null
// 發起遠程拷貝
if (mContainerService == null) {
...
} else if (mPendingInstalls.size() > 0) {
HandlerParams params = mPendingInstalls.get(0);
if (params != null) {
// 調用HandlerParams的startCopy方法
if (params.startCopy()) {
...
}
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
break;
}
}
}
// PackageManagerService.HandlerParams.startCopy
final boolean startCopy() {
// InstallParams繼承於HandlerParams
// 調用InstallParams的handleStartCopy
handleStartCopy();
// 調用InstallParams的handleReturnCode,開始解析APK
handleReturnCode();
return res;
}
從上述源碼可知,當PackageHandler接收到MCS_BOUND
消息後,會首先緩存IMediaContainerService.Stub實體Binder代理對象mContainerService,然後調用HandlerParams的startCopy方法,該方法主要執行兩步操作,一是調用InstallParams的handleStartCopy,二是調用InstallParams的handleReturnCode繼續拷貝完畢後的下一步工作,即解析APK。這裏爲什麼是InstallParams?在前面我們提到過這個類,是之前傳進來的且繼承於HandlerParams。handleStartCopy等部分源碼如下:
// InstallParams.handleStartCopy
public void handleStartCopy() throws RemoteException {
...
final InstallArgs args = createInstallArgs(this);
mArgs = args;
...
ret = args.copyApk(mContainerService, true);
}
// PackageManagerService.createInstallArgs
private InstallArgs createInstallArgs(InstallParams params) {
if (params.move != null) {
return new MoveInstallArgs(params);
} else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
// 安裝到SDCard
return new AsecInstallArgs(params);
} else {
// 安裝到data分區(內部存儲器)
return new FileInstallArgs(params);
}
}
// PackageManagerService.FileInstallArgs.copyAPK
int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "copyApk");
try {
return doCopyApk(imcs, temp);
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
// PackageManagerService.FileInstallArgs.doCopyAPK
private int doCopyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
try {
final boolean isEphemeral = (installFlags &
PackageManager.INSTALL_INSTANT_APP) != 0;
//創建臨時文件存儲目錄
// /data/app/vmdl1223.tmp
final File tempDir =
mInstallerService.allocateStageDirLegacy(volumeUuid, isEphemeral);
codeFile = tempDir;
resourceFile = tempDir;
} catch (IOException e) {
Slog.w(TAG, "Failed to create copy file: " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
...
// 調用遠程DefaultContainerService實體Binder的copyPackage
// 執行真正的拷貝操作
ret = imcs.copyPackage(origin.file.getAbsolutePath(), target);
if (ret != PackageManager.INSTALL_SUCCEEDED) {
Slog.e(TAG, "Failed to copy package");
return ret;
}
...
}
首先,InstallParams.handleStartCopy會獲取一個InstallArgs對象,這個InstallArgs是一個抽象類,通過查看PMS的createInstallArgs方法得值,它有三個子類,即MoveInstallArgs、AsecInstallArgs、FileInstallArgs,其中,FileInstallArgs用於處理安裝到非ASEC的存儲空間的APK,也就是內部存儲空間(Data分區),AsecInstallArgs用於處理安裝到ASEC中(mnt/asec)即SD卡中的APK。由於現在大部分手機都直接將APK安裝到內部存儲器中,這裏我們以分析FileInstallArgs爲例。然後,InstallParams.handleStartCopy接着會調用FileInstallArgs的copyApk方法,該方法繼續調用doCopyApk方法並在這個方法中完成臨時文件存儲目錄的創建,和調用遠程Binder(DefaultContainerService.mBinder
)的copyPackage方法,最終將拷貝任務交給遠程DefaultContainerService服務進程處理。DefaultContainerService部分源碼如下:
// DefaultContainerService.mBinder
private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
...
@Override
public int copyPackage(String packagePath, IParcelFileDescriptorFactory target) {
if (packagePath == null || target == null) {
return PackageManager.INSTALL_FAILED_INVALID_URI;
}
PackageLite pkg = null;
try {
final File packageFile = new File(packagePath);
// 輕度解析APK
// 首先,判斷是單個APK還是APK目錄;
// 然後,將一個APkLite(輕量級包信息)或多個ApkLite封裝爲一個PackageLite返回
pkg = PackageParser.parsePackageLite(packageFile, 0);
// 拷貝APK到指定目錄
// /data/app/com.example/base.apk
// /data/app/com.example/split_foo.apk
return copyPackageInner(pkg, target);
} catch (PackageParserException | IOException | RemoteException e) {
Slog.w(TAG, "Failed to copy package at " + packagePath + ": " + e);
return PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
}
}
}
最終,在DefaultContainerService服務中完成以下兩個任務:
1)輕度解析APK,得到一個輕量級包信息PackageLite,爲後續解析APK做準備工作。
2)拷貝APK到/data/app目錄下。這裏需要注意的是,爲了解決65536上線以及APK安裝包越來越大的問題,Android 5.0引入了Split APK機制,該機制允許將一個APK拆分成多個獨立的APK。因此,拷貝APK需要分兩種類型,即安裝的是一個完整的APK,即base APK,Android稱其爲Monolithic;安裝文件在一個文件目錄中,這些APK由一個base APK和一個或多個split APK組成,Android稱其爲Cluster。比如:
/data/app/com.example/base.apk
/data/app/com.example/split_foo.apk
Android不同的目錄存放不同類型的應用,如下所示:
/system/framwork:保存的是資源型的應用程序,它們用來打包資源文件。
/system/app:保存系統自帶的應用程序。
/data/app:保存用戶安裝的應用程序。
/data/app-private:保存受DRM保護的私有應用程序。
/vendor/app:保存設備廠商提供的應用程序。
2.2 解析APK
APK解析過程時序圖如下:
在1.3.1中我們談到,當APK拷貝操作完成後會調用PackageManagerService.InstallParams的handleReturnCode方法進入APK解析流程。handleReturnCode方法很簡單,只是調用了PMS的processPendingInstall方法,從該方法源碼可知,由於APK安裝解析是一個耗時的操作,因此它通過異步任務的形式將首先在安裝前刪除一些殘留文件,然後開始解析APK,再然後刪除安裝後殘留文件,最後向PackageHandler發送一個POST_INSTALL
消息。PackageHandler接收到POST_INSTALL
會發送一個removed廣播,並請求運行授權。PMS.processPendingInstall方法部分源碼如下:
private void processPendingInstall(final InstallArgs args, final int currentStatus) {
...
mHandler.post(new Runnable() {
public void run() {
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
// 1. 安裝前操作,刪除安裝殘留文件
args.doPreInstall(res.returnCode);
// 2 .對APK進行解析
synchronized (mInstallLock) {
installPackageTracedLI(args, res);
}
// 3. 刪除安裝後的殘餘文件
args.doPostInstall(res.returnCode, res.uid);
}
...
// 4. 發送信息POST_INSTALL
Message msg = mHandler.obtainMessage(POST_INSTALL, token, 0);
mHandler.sendMessage(msg);
}
}
}
接下來,我們通過PMS的installPackageTracedLI詳細分析APK的解析安裝過程。在PMS的installPackageTracedLI方法中,它的實現很簡單,只是調用了PMS的installPackageLI方法,該方法很長很長,但主要做了如下工作:
- 解析AndroidManifest.xml文件得到應用信息、各組件信息和權限等,創建PackageParser.Package對象;
- 檢查APK是否已經安裝,並進行簽名校驗;
- 對dex文件進行優化,需要注意的是,不同的虛擬機操作會有所不同,如果是Davlik虛擬機,那麼dex文件將被優化生成odex.dex文件,保存在/data/dalvik-cache目錄下;如果是ART虛擬機,將生成oat文件;
- 將複製的APK文件目錄,重命名以包名爲目錄的文件;
- 替換APK或者安裝新的APK;
PMS.installPackageTracedLI源碼如下:
private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {
...
// 1. 解析package信息,主要是AndroidManifest.xml
// 最終調用的是PackageParser.ParseBaseApkCommon方法
final PackageParser.Package pkg;
try {
pkg = pp.parsePackage(tmpPackageFile, parseFlags);
} catch (PackageParserException e) {
res.setError("Failed parse during installPackageLI", e);
return;
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
// 2. 簽名校驗
try {
verifySignaturesLP(signatureCheckPs, pkg);
} catch (PackageManagerException e) {
res.setError(e.error, e.getMessage());
return;
}
// 3. 對dex進行優化
mPackageDexOptimizer.performDexOpt(pkg, pkg.usesLibraryFiles,
null , false ,
getCompilerFilterForReason(REASON_INSTALL),
getOrCreateCompilerPackageStats(pkg),
mDexManager.isUsedByOtherApps(pkg.packageName));
// 4. 將/data/app/vmdl18300388.tmp/base.apk,重命名爲/data/app/包名-1/base.apk。
if (!args.doRename(res.returnCode, pkg, oldCodePath)) {
res.setError(INSTALL_FAILED_INSUFFICIENT_STORAGE, "Failed rename");
return;
}
// 5. 替換或新安裝APK
if (replace) {
replacePackageLIF(pkg, parseFlags, scanFlags, args.user,
installerPackageName, res, args.installReason);
} else {
installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
args.user, installerPackageName, volumeUuid, res, args.installReason);
}
}
在PMS.installNewPackageLIF方法中,主要做了如下工作:
- 調用PMS.scanPackageTracedLI方法重新掃描apk文件,該方法繼續調用PMS的scanPackageLI方法,而scanPackageLI又調用了PMS的scanPackageDityLI,由它來完成向PSM註冊四大組件信息等操作;
- 更新PackageSettings,該類記錄了APK配置的動態信息;
- 安裝完成後,爲APP準備數據;
PMS.installNewPackageLIF方法部分源碼如下:
private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags,
int scanFlags, UserHandle user, String
installerPackageName, String volumeUuid,
PackageInstalledInfo res, int installReason) {
String pkgName = pkg.packageName;
...
// 1. 調用scanPackageTracedLI掃描APK
PackageParser.Package newPackage = scanPackageTracedLI(pkg,policyFlags,
scanFlags,System.currentTimeMillis(), user);
// 2. 更新PackageSettings
updateSettingsLI(newPackage, installerPackageName, null,
res, user, installReason);
// 3.執行到這裏說明APP已經安裝完畢
// 準備APP數據
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
prepareAppDataAfterInstallLIF(newPackage);
}
...
}
APK安裝大致流程圖: