從Android 8.0源碼的角度剖析APK打包、安裝過程

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.arscres文件夾(包括二進制的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,也就是說InstallStartPackageInstaller應用的入口。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安裝大致流程圖:

img

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章