Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> [gitbook] Android框架分析系列之Android PackageManager

[gitbook] Android框架分析系列之Android PackageManager

編輯:關於Android編程

本文章圍繞著Android的包管理機制,著重分析Android的包格式(包括簽名),以及應用程序的安裝,升級以及卸載過程。

1. Android APK文件

Android的應用程序以後綴名為apk的文件發布。APK文件中包含應用程序的代碼和資源數據,清單文件以及簽名信息。APK文件格式擴展於JAR格式,而JAR文件格式又是擴展自ZIP格式。所以,可以使用ZIP解壓工具,打開APK文件。

一個典型的APK文件內容如下:
├── AndroidManifest.xml    (1)
├── classes.dex            (2)
├── lib                    (3)
│ ├── armeabi
│ │   └── libhello-jni.so
│ ├── armeabi-v7a
│ │   └── libhello-jni.so
│ ├── mips
│ │   └── libhello-jni.so
│ └── x86
│     └── libhello-jni.so
├── META-INF               (4)
│   ├── CERT.RSA
│   ├── CERT.SF
│   └── MANIFEST.MF
└── resources.arsc         (5)

<1>:AndroidManifest.xml文件中包含了應用程序的組件,包名,版本等信息。

<2>:classes.dex是應用的可執行代碼。

<3>:對於使用了ndk編程的應用來說,APK中還有lib文件夾,這個文件夾下面存放的是適配各個平台的共享庫。

<4>:包含應用的簽名和清單文件,這部分內容後面還會講述。

<5>:對於可編譯的資源,最後都編譯好後,打包進了resources.arsc文件,比如string和style。對於編譯好的APK文件,可以使用aapt工具查看資源表。

An Example aapt 用法
  • aapt d resourcesxxx.apk可打印出資源表

  • aapt d xmltreexxx.apk res/layout/main.xml 打印布局文件main.xml的內容

更多aapt的用法,參考aapt -help。

1.1. APK簽名

與APK簽名相關的文件都位於META-INF目錄下。下面會對三個文件做一些簡單的分析,關於加密解密算法的實現,超出了本文的范圍,這裡不會涉及到。

1.1.1. MANIFEST.MF

後綴名為.MF的文件,裡面存放的是打包在APK中的文件的Message digest(消息摘要)。

MANIFEST.MF的內容
Manifest-Version: 1.0
Created-By: 1.0 (Android)

Name: AndroidManifest.xml
SHA1-Digest: bbFIUGA27QAiLBforTb0B+GeAk8=

Name: lib/armeabi-v7a/libhello-jni.so
SHA1-Digest: imJ95BzGT7rI57k7xMnRNBKysgo=

Name: lib/x86/libhello-jni.so
SHA1-Digest: /QM7n8FQYZ7gTjHAfsNzcoPZsnY=

Name: lib/mips/libhello-jni.so
SHA1-Digest: A2WeX1JI3csDX8v/PapWe97Gx9k=

Name: resources.arsc
SHA1-Digest: rzak75eaqS4baaz+03KawAATuNk=

Name: lib/armeabi/libhello-jni.so
SHA1-Digest: 3SGVYUG2+pMhNqB7K5m/T4iA+K8=

Name: classes.dex
SHA1-Digest: 8XJt/CggZUAQU6ULBavrzodpwok=

對於APK包中的文件先使用hash函數獲取Message Digest,然後采用base64編碼得到的結果作為文件的最終Message Digest。MANIFEST.MF中的每一條目都是由Name和SHA1-Digest組成。Name對應的是文件名,SHA1-Digest對應的文件的Message Digest。

可以使用如下的命令生成Message Digest:

$ openssl sha1 -binary HelloJni/classes.dex | openssl base64

1.1.2. CERT.SF文件

CERT.SF文件中存放的也是Message Digest。與上面MANIFEST.MF文件對應的CERT.SF文件內容如下:

CERT.SF 內容
Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA1-Digest-Manifest: PDEMo/mMNPiPsuYop2qQpb9VjX0=1

Name: AndroidManifest.xml
SHA1-Digest: sbIUZUy5AB2lb4RMZzyqlf+JEuw=

Name: lib/armeabi-v7a/libhello-jni.so
SHA1-Digest: cP7n4f23m5CWostVb5+C65095Oo=

Name: lib/x86/libhello-jni.so
SHA1-Digest: vFuCcAZFIQgcEkc6a6L395gkbdQ=

Name: lib/mips/libhello-jni.so
SHA1-Digest: 98f4yVRHgXH5cKKnufCTK7EbUVs=

Name: resources.arsc
SHA1-Digest: OynBh/Tw+yCvo6RPdC5subo2+HE=

Name: classes.dex
SHA1-Digest: NTB2dedbG357wxDmiiD7dHTFp6E=

Name: lib/armeabi/libhello-jni.so
SHA1-Digest: mjzjxE6Pt0Lrz4k5PeKTONgSh5A=

CERT.SF中的第一條內容來自對MANIFEST.MF文件Message Digest的結果;其他的條目分別是對MANIFEST.MF中對應的條目做Message Digest.下面的命令展示了這一過程:

An Example CERT.SF文件內容生成

openssl sha1 -binary HelloJni/META-INF/MANIFEST.MF | openssl base64

其結果為:PDEMo/mMNPiPsuYop2qQpb9VjX0=

echo -en "Name: lib/armeabi-v7a/libhello-jni.so\r\nSHA1-Digest: imJ95BzGT7rI57k7xMnRNBKysgo=\r\n\r\n" | openssl sha1 -binary | openssl base64

其結果為:cP7n4f23m5CWostVb5+C65095Oo=

通過上面例子的結果,可以知道,CEART.SF中的內容來自於對MANIEST.MF文件以及文件中的條目做Message Digest的結果。

1.1.3. CERT.RSA 文件

CERT.RSA文件中存放著CERT.SF的數字簽名,以及簽名證書。如果存在多個.SF和.RSA文件,那麼每一對的文件名都需要匹配。比如CERT1.SF對應著CERT1.RSA,CERT2.SF對應CERT2.RSA。

可以使用如下命令查看CERT.RAS文件中存放的證書.

openssl pkcs7 -inform DER -in HelloJni/META-INF/CERT.RSA -noout -print_certs -text

使用下面命令提取證書:

openssl pkcs7 -inform DER -print_certs -in HelloJni/META-INF/CERT.RSA -out cert.pem

使用如下命令打印證書內容:

openssl x509 -in cert.pem -noout -text

可以使用如下命令驗證SF文件的有效性:

openssl smime -verify -in HelloJni/META-INF/CERT.RSA -inform DER -content HelloJni/META-INF/CERT.SF -CAfile cert.pem

2. APK文件的安裝

APK的安裝主要有三種方式:

  • 對於在/system/app和/data/app目錄下的APK文件,在PackageManagerService的啟動過程中,會掃描安裝;

  • 通過PackageInstaller的方式安裝;

  • pm命令行的方式安裝。這種方式主要是用在開過過程中。

安裝成功的APK,PackageManagerService會生成”記賬簿“來保存必要的信息。主要有以下兩個文件:

  • /data/system/packages.list

  • /data/system/packages.xml

packages.list文件中保存著應用的包名,uid,所屬的用戶,以及“home”目錄.因為Android中每一個應用都類似linux系統中user的概念,它們都有自己的home目錄,用來保存應用特定的數據。

An Example packages.list文件

com.android.defcontainer 10002 0 /data/data/com.android.defcontainer

com.android.providers.contacts 10021 0 /data/data/com.android.providers.contacts

com.android.quicksearchbox 10027 0 /data/data/com.android.quicksearchbox

com.android.demo.notepad3 10011 0 /data/data/com.android.demo.notepad3

com.android.contacts 10021 0 /data/data/com.android.contacts

com.android.systemui 10013 0 /data/data/com.android.systemui

com.android.packageinstaller 10012 0 /data/data/com.android.packageinstaller

com.android.calendar 10023 0 /data/data/com.android.calendar

packages.xml文件中保存著應用程序的詳細信息,包括包名,代碼路徑,jni庫路徑,應用標記,簽名,權限等。

對於APK的三種安裝方式,接下來的章節會詳細的講到。

2.1. PackageManagerService

PackageManagerService由system_server啟動,它全面負責應用包的安裝,卸載,權限檢查等工作。在每次開機的時候,PackageManagerService都會在其構造函數中,對指定的目錄的APK進行掃描。對於沒有安裝的APK文件會觸發安裝過程。 下面我們對PackageManagerService構造函數中出現的重點函數和重點流程展開介紹。

2.1.1. readPermissions 方法簡介

PackageManagerService的readPermissions方法負責讀取/etc/permission目錄下面的配置文件。這些配置文件中保存的信息有:系統支持的硬件,比如是否支持wifi,gps等;權限映射關系。

描述系統支持的硬件特性的文件,一般滿足這樣的命名規范:android.hardware.XXX.xml,XXX代表硬件模塊名。下面是 samsung manta 的nfc特性文件——android.hardware.nfc.xml的內容:



    

讀取出來的feature保存在HashMap中:

// These are the features this devices supports that
// were read from the etc/permissions.xml file.
final HashMap mAvailableFeatures =
       new HashMap();

應用程序通過PackageManager類可以查詢指定的feature系統是否支持,以及獲得所有系統支持的feature.

frameworks/base/core/java/android/content/pm

public abstract FeatureInfo[] getSystemAvailableFeatures();
public abstract boolean hasSystemFeature(String name);

設備目錄/etc/permissions下面的特性文件來自於哪裡呢?它們實際上是在編譯的時候打包到system.image文件中。比如上面的samsung manta 的nfc特性文件就是在manta的device.mk文件中將frameworks/native/data/etc/android.hardware.nfc.xml文件copy到system/etc/permissions/android.hardware.nfc.xml。

/etc/permissions目錄下面還有一個非常重要的xml文件——platform.xml,這個文件中記錄了Android APP權限與gid,uid的對應關系。這個文件在源碼的位置:frameworks/base/data/etc。在這個目錄下面還有一個Android.mk文件,負責將platform.xml編譯到system鏡像中:

LOCAL_PATH := $(my-dir)

########################
include $(CLEAR_VARS)

LOCAL_MODULE := platform.xml

LOCAL_MODULE_CLASS := ETC

# This will install the file in /system/etc/permissions
#
LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/permissions

LOCAL_SRC_FILES := $(LOCAL_MODULE)

include $(BUILD_PREBUILT)

上面的例子也給我們提供了另一種參考:如何將配置文件編譯到system/ect/permissions中。

下面是platform.xml文件中的部分內容:



    
        
    

    
        
    

    …………
    

    

    …………


    

    

platform.xml中主要有三塊內容:

  • 將APP framework中的權限和底層的gid映射。當APP獲得某個權限之後,會獲得這個gid所具備的權限。

  • 將APP framework的權限賦予某個系統級別的進程。這樣這個進程就可以獲得操作APP framework資源的。

  • jar庫文件的映射。APP中通過指定鏈接的jar庫名,通過這層映射關系,可以在鏈接的找到正確的jar庫。
總結:

readPermissions方法讀取/ect/permissions目錄下的xml文件,並為讀取的結果生成相應的數據結構。

2.1.2. readLPw和writeLPr方法簡介

在讀取完權限文件之後,PackageManagerService會在其構造函數中調用Settings的readLPw方法,讀取應用包的設置文件。

  • /data/system/packages.xml

  • /data/system/packages-backup.xml

  • /data/system/packages.list

  • /data/system/users/userid/package-restrictions.xml

對於packages.xml和packages.list在之前已經簡單的介紹過了,packages-backup.xml是packages.xml的備份文件。在每次寫packages.xml文件的時候,都會將舊的packages.xml文件先備份,這樣做是為了防止寫文件過程中文件以外損壞,還能從舊的文件中恢復。package-restrictions.xml保存著受限制的APP的狀態,比如某個APP處於disable狀態,或者某個APP具有更高的優先級等。這裡舉一個例子:

$adb shell pm disable com.android.providers.drm

運行上述命令之後,package-restrictions.xml文件就會存在一條受限制的記錄:

關於enable的含義可以參考:PackageManager.java中定義的常量:

public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0;
public static final int COMPONENT_ENABLED_STATE_ENABLED = 1;
public static final int COMPONENT_ENABLED_STATE_DISABLED = 2;
public static final int COMPONENT_ENABLED_STATE_DISABLED_USER = 3;

readLPw方法負責讀取packages.xml文件。它的邏輯是如果存在packages-backup.xml,就認為packages.xml已經損壞,將之刪除。然後從packages-backup.xml中讀取信息,用讀取的信息構造一個PackageSetting對象,然後以包名為key,PackageSetting為value,保存在HashMap中。

final HashMap mPackages =
    new HashMap();

現在可以總結下readLPw的執行過程:

  • 如果系統為第一次開機,則調用readDefaultPreferredAppsLPw方法,讀取/etc/preferred-apps目錄下的xml文件。這個目錄下面的xml文件提供了默認的優先APP的設置;

  • 讀取packages.xml文件

  • 調用readPackageRestrictionsLPr方法,讀取package-restrictions.xml文件

2.1.3. scanDirLI

在PackageManagerService的構造函數中,會調用此方法,對以下目錄下的APK文件進行掃描:

  • /system/framework

  • /system/app

  • /data/app

掃描過程分為兩個步驟:

  1. 掃描APK安裝包的AndroidManifest.xml文件和提取證書信息,以此信息構建一個PackageParser.Package對象;

  2. 進一步處理第一個步驟獲得的PackageParser.Package對象:
  • 創建Package對應的PackageSetting對象,這個對象記錄了應用包的一些設置信息;

  • 驗證簽名信息

  • 安裝應用程序,包括創建應用程序數據目錄,動態庫的提取,對科執行代碼進行優化等

  • 最後一個步驟就是將Package的組件(ContentProvider,Activity,Service,BroadcastReceiver)發布到PackageManagerService中,這樣其他應用就可以通過使用公開的組件。比如通過Intent啟動Activity或者Service。

下面是一張流程圖,描述了系統第一次開機過程中對/data/app目錄掃描的過程。因為掃描是個很復雜的過程,所以在流程圖中省略了一些異常的case,主要集中在掃描安裝這條主線上,對於升級等流程的判斷,以及簽名信息不符合的判斷都進行了省略。\

上圖中標記紅色的部分是掃描安裝過程中幾個關鍵的步驟。

  • 在collectCertificate sLI方法中會對應用包的整數進行驗證,檢查應用包的完整性和合法性,防止應用程序包被篡改。
  • mSetting.getPackageLPw方法會為Package創建對應的PackageSetting對象。這個對象中保存的信息最後會通過writeLPr寫入到/data/system/packages.xml文件中去。

  • createDataDirsLI方法會給installd發送消息,為應用程序創建對應的數據目錄。

  • copyNativeLibrariesForInternalApp會將應用程序包的動態庫復制到對應的動態庫目錄。

  • performDexOptLI方法也是調用installd發送消息,優化可執行代碼。優化完畢的可執行代碼存放在目錄/data/dalvik-cache/目錄下面。

上面提到的installd,將在下一個小節講解。PackageManagerService掃描完APK之後,會在其內部建立復雜的數據結構。這裡我們只以簡單的一張類圖感受下兩個重要的類——PackageManagerService和Settings的關系。\

簡單的介紹下上面的圖:

  • Package對象中的數據是從APK文件中掃描而得到,主要來源於AndroidManifest.xml文件

  • PackageSetting的父類是PackageSettingBase,這個類中的數據除了第一次開機以外,都是從packages.xml或者packages-backup.xml

  • Settings中以包名為key保存著所有的Package對應的設置信息;同時也保存著SharedUser相關的信息。應用程序的“記賬簿”在開機過程中都會被讀入到內存中,而這就是由Settings負責。

2.2. installd

上面講到,PackageManagerService在掃描過程中會給installd發送消息,請求創建應用程序的數據目錄和執行代碼優化。那這個installd是什麼東西呢?為什麼需要創建這麼個模塊?

installd的代碼路徑:

frameworks/base/cmds/installd

在應用程序的管理工作中,有時候需要對存儲設備做一些操作,比如創建目錄修改目錄權限等,這些操作有的是需要特權級的權限。PackageManagerService存活在system_server進程中,這個進程的用戶為system,它是沒有特權級的權限的,所以我猜想,出於安全的角度考慮,Android單獨將一部分需要特權的工作,轉交給installd進程去完成。

在installd的入口函數中,首先是做一些初始化的工作,然後放棄自己的root用戶身份,改變了自己的用戶類型和組用戶類型,但同時它保留了設置目錄權限,以及修改目錄屬主的權限:

static void drop_privileges() {
    if (prctl(PR_SET_KEEPCAPS, 1) < 0) {
        ALOGE("prctl(PR_SET_KEEPCAPS) failed: %s\n", strerror(errno));
        exit(1);
    }

    if (setgid(AID_INSTALL) < 0) {
        ALOGE("setgid() can't drop privileges; exiting.\n");
        exit(1);
    }

    if (setuid(AID_INSTALL) < 0) {
        ALOGE("setuid() can't drop privileges; exiting.\n");
        exit(1);
    }

    struct __user_cap_header_struct capheader;
    struct __user_cap_data_struct capdata[2];
    memset(&capheader, 0, sizeof(capheader));
    memset(&capdata, 0, sizeof(capdata));
    capheader.version = _LINUX_CAPABILITY_VERSION_3;
    capheader.pid = 0;

    capdata[CAP_TO_INDEX(CAP_DAC_OVERRIDE)].permitted |= CAP_TO_MASK(CAP_DAC_OVERRIDE);
    capdata[CAP_TO_INDEX(CAP_CHOWN)].permitted        |= CAP_TO_MASK(CAP_CHOWN);
    capdata[CAP_TO_INDEX(CAP_SETUID)].permitted       |= CAP_TO_MASK(CAP_SETUID);
    capdata[CAP_TO_INDEX(CAP_SETGID)].permitted       |= CAP_TO_MASK(CAP_SETGID);

    capdata[0].effective = capdata[0].permitted;
    capdata[1].effective = capdata[1].permitted;
    capdata[0].inheritable = 0;
    capdata[1].inheritable = 0;

    if (capset(&capheader, &capdata[0]) < 0) {
        ALOGE("capset failed: %s\n", strerror(errno));
        exit(1);
    }
}

installd也算是個服務端了,它是通過socket來接收客戶端的消息,然後進行處理。在main函數中,通過for循環從socket中讀取消息,然後調用execute處理消息。

installd是通過init創建,在init.rc文件中有配置它的socket名:

service installd /system/bin/installd
    class main
    socket installd stream 600 system system

所以installd進程創建的socket名為:/dev/socket/installd, 600表示socket的用戶權限為可讀可寫,system表示用戶和用戶組。

installd可以處理的消息為:

struct cmdinfo {
    const char *name;
    unsigned numargs;
    int (*func)(char **arg, char reply[REPLY_MAX]);
};

struct cmdinfo cmds[] = {
    { "ping",                 0, do_ping },
    { "install",              3, do_install },
    { "dexopt",               3, do_dexopt },
    { "movedex",              2, do_move_dex },
    { "rmdex",                1, do_rm_dex },
    { "remove",               2, do_remove },
    { "rename",               2, do_rename },
    { "fixuid",               3, do_fixuid },
    { "freecache",            1, do_free_cache },
    { "rmcache",              2, do_rm_cache },
    { "getsize",              5, do_get_size },
    { "rmuserdata",           2, do_rm_user_data },
    { "movefiles",            0, do_movefiles },
    { "linklib",              3, do_linklib },
    { "mkuserdata",           3, do_mk_user_data },
    { "rmuser",               1, do_rm_user },
    { "cloneuserdata",        3, do_clone_user_data },
};

在PackageManagerService掃描安裝應用的過程中,給installd先後發送了如下消息:

  • install

  • dexopt

這三個消息分別由do_install,do_dexopt函數來處理。

do_install函數為應用創建了以下目錄,在install過程中,同時為創建的目錄設置uid和gid。

  • 數據目錄:/data/data/packageName/

  • lib目錄:/data/app-lib/packageName

  • lib符號鏈接:/data/data/packageName/lib → /data/app-lib/packageName

do_dexopt中調用dexopt函數處理apk文件。主要流程為:
  • 首先判斷apk所在目錄下面是否存在同名的odex文件,如果存在就直接返回;

  • 以apk文件的路徑為名創建dex路徑。比如在debug版本中,/system/app/Settings.apk的dex文件將會保存在: /data/dalvik-cache/system@[email protected]@classes.dex文件中。dex文件命名規則是/data/dalvik-cache字符串拼接apk的路徑,再拼“/classs.dex"。然後將”/data/dalvik-cache/“字符串後面的‘/’替換為@字符。

  • 最後調用系統工具/system/bin/dexopt來提取dex文件。

2.3. PackageInstaller

PackageInstaller是Android提供的默認應用程序安裝與卸載管理應用程序。應用包中有兩個Activity:PackageInstallerActivity和UninstallerActivity分別用來管理應用程序的安裝和卸載。這兩個Activity可以通過發送Intent的方式啟動。下面的代碼片段指出了兩個Activity能處理的Intent:


            
                
                
                
                
                
            
            
                
                
                
                
            
 


            
                
                
                
                
            

通過給PackageInstallerActivity發送"android.intent.action.VIEW"或者是"android.intent.action.INSTALL_PACKAGE" Intent就能啟動對一個APK文件的安裝過程。例如,我們假設有一個應用程序位於/mnt/sdcard/Test.apk,為了啟動對這個應用程序的安裝,可以通過文件浏覽器點擊這個應用包文件,同時也可以通過adb shell登陸機器,然後輸入以下命令:

am start -a android.intent.action.VIEW -t application/vnd.android.package-archive -c android.intent.category.DEFAULT -d file:///mnt/sdcard/Test.apk

當輸入上述命令後,首先是以Action等參數構造一個Intent,然後am調用ActivityManagerService的startActivity方法。最後的結果是啟動PackageInstallerActivity,並將構造的Intent傳遞給啟動的Activity。下面簡單的描述下,PackageInstallerActivity安裝應用的過程:

  • 首先PackageInstaller會檢查應用程序的完成性,嘗試著從應用包中提解析AndroidManifest.xml文件,構造ParckageParser.Package對象。如果成功,則繼續安裝;否則安裝退出

  • 然後對發起安裝請求的應用屬性進行判斷:如果是system應用,那麼就進入初始化安裝步驟;如果是非system應用,那麼存在一個驗證是否允許安裝“unknown source”的步驟。

  • 如果上面的步驟通過,就進入了初始化安裝過程。初始化過程主要是檢測應用是否安裝,以及提取相關的權限信息,用於界面顯示。這一過程主要是UI界面相關,略過不表

  • 當用戶在安裝界面上點擊“確定”按鈕,PackageInstallerActivity就會啟動InstallAppProgress,在此Activity會調用PackageManager的方法public abstract void installPackageWithVerificationAndEncryption(Uri packageURI, IPackageInstallObserver observer, int flags, String installerPackageName, VerificationParams verificationParams, ContainerEncryptionParams encryptionParams)真正進行安裝,並顯示安裝進度。

2.3.1. installPackageWithVerificationAndEncryption

PackageManager的installPackageWithVerificationAndEncryption最終是一個binder調用,是在PackageManagerService中負責實現。在此方法中,獲取一個Message對象,其id為INIT_COPY,其obj為InstallParams。其中InstallParams對象根據installPackageWithVerificationAndEncryption的方法參數構造。INIT_COPY消息是在PackageHandler中進行處理。對於此消息的處理流程為:

首先通過connectToService方法綁定IMediaContainerService服務;在綁定成功之後,發送MCS_BOUND消息;IMeidaContainerService的實現是在frameworks/base/packages/DefaultContainerService/src/com/android/def/DefaultContainerService.java中。它負責原始apk文件的解密工作。

在PackageHandler消息中再次對MCS_BOUND消息進行處理。這次就會調用InstallParams的startCopy方法,開啟復制過程。startcopy方法又會調用handleStartCopy進行復制工作。主要做了如下工作:
  • 確定APK的安裝位置

  • 檢查磁盤空間是否足夠

  • 以InstallParams構造一份InstallArgs對象。InstallArgs有兩個子類:AsecInstallArgs和FileInstallArgs,對於安裝於外部sd卡或者是有Forward Lock屬性的應用,將會構造AsecInstallArgs,否則構造的是FileInstallArgs對象。

  • 檢查APK是否需要驗證(一般是不需要)

  • 調用InstallArgs對象的copyApk方法。

所以,handleStartCopy最重要的工作就是由InstallParams的copyApk來完成。假設在這個InstallParams引用是一個FileInstallArgs對象,那麼其copy流程如下:

  • 創建臨時文件。對於最終安裝在/data/app目錄下面的應用,其臨時文件的共享名為:/data/app/vmdl-XXX.tmp。這個XXX是一個隨機數

  • 調用IMediaContainerService的服務接口,從原始APK文件中提取出解密過的APK數據,寫入到臨時文件中

  • 拷貝jni動態庫到相應的目錄

在InstallParams的startCopy處理完handleStartCopy之後,會調用handleRetureCode方法,此方法中將會進行觸發真正的安裝流程。

void handleReturnCode() {
    if (mArgs != null) {
        processPendingInstall(mArgs, mRet);

        if (mTempPackage != null) {
            if (!mTempPackage.delete()) {
                Slog.w(TAG, "Couldn't delete temporary file: " +
                        mTempPackage.getAbsolutePath());
            }
        }
    }
}

在handleReturnCode中又調用了processPendingInstall方法,這個方法主要做了以下幾件事情:

  • 調用FileInstallArgs對象的doPreInstall方法:如果之前的流程有錯誤,此方法將會執行清理動作

  • 調用PackageManagerService的installPackageLI方法,開始安裝過程

  • 調用FileInstallArgs對象的doPostInstall方法:如果之前的流程有錯誤,此方法將會執行清理動作

installPackgeLI執行步驟如下:

  • 從臨時文件中提取出Package對象:final PackageParser.Package pkg = pp.parsePackage(tmpPackageFile, null, mMetrics, parseFlags)

  • 提取證書信息,並驗證

  • 將臨時文件更名為正式apk文件,修改動態庫目錄名
  • 調用installNewPackageLI安裝正式apk文件
  • scanPackageLI方法負責安裝apk

  • updateSettingsLI負責更新“記賬簿”

2.4. pm 命令

APK的安裝,在eng版本的開發中,還可以通過pm命令。pm實際上是一個腳本文件,腳本代碼位於frameworks/base/cmds/pm/pm。

base=/system
export CLASSPATH=$base/framework/pm.jar
exec app_process $base/bin com.android.commands.pm.Pm "$@"

從中可以看出,利用app_process程序,加載pm.jar包來執行具體的動作。

pm.jar的源碼和pm腳本位於同一目錄下面。 pm命令安裝應用,實際上走的流程和PackageInstaller差不多,都是調用PackageManagerService的installPackageWithVerificationAndEncryption方法。

但是他們也有不同的地方,pm命令可以指定加密解密參數,而PackageInstaller無法指定。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved