APK安裝流程系列文章整體內(nèi)容如下:
- APK安裝流程詳解0——前言
- APK安裝流程詳解1——有關(guān)"安裝ing"的實體類概述
- APK安裝流程詳解2——PackageManager簡介
- APK安裝流程詳解3——PackageManager與PackageManagerService
- APK安裝流程詳解4——安裝中關(guān)于so庫的哪些事
- APK安裝流程詳解5——PackageInstallerService和Installer
- APK安裝流程詳解6——PackageManagerService啟動前奏
- APK安裝流程詳解7——PackageManagerService的啟動流程(上)
- APK安裝流程詳解8——PackageManagerService的啟動流程(下)
- APK安裝流程詳解9——PackageParser解析APK(上)
- APK安裝流程詳解10——PackageParser解析APK(下)
- APK安裝流程詳解11——普通應(yīng)用安裝簡介
- APK安裝流程詳解12——PackageManagerService中的新安裝流程上(拷貝)
- APK安裝流程詳解13——PackageManagerService中的新安裝流程下(裝載)
- APK安裝流程詳解14——PMS中的新安裝流程上(拷貝)補充
- APK安裝流程詳解15——PMS中的新安裝流程下(裝載)補充
- APK安裝流程詳解16——Android包管理總結(jié)(尚未完結(jié)請期待)
本片文章的主要內(nèi)容如下:
- 1、概述
- 2、Android應(yīng)用程序的幾種安裝方式
- 3、應(yīng)用安裝涉及到的目錄
- 4、安裝流程概述
- 5、PackageInstaller.apk與PackageManger
- 6、普通的APK安裝方式的界面
- 7、PackageInstallerActivity類的安裝流程
- 8、InstallAppProgress類的安裝流程
- 9、InstallAppProgress中涉及到PackageManager的三個方法
一、 概述
眾所周知,Android應(yīng)用最終是打包成.apk格式(其實就是一個壓縮包),然后安裝至手機并運行的。其中APK是Android Package的縮寫。
Android系統(tǒng)在啟動的過程中,會啟動一個引用程序管理服務(wù)PackageManagerService,這個服務(wù)負責掃描系統(tǒng)中特定的目錄,找到里面的應(yīng)用程序文件,以.apk為后綴的文件,然后對這些文件進行解析,得到引用程序的相關(guān)信息,完成應(yīng)用程序的安裝過程。應(yīng)用程序管理服務(wù)PackageManagerService安裝應(yīng)用程序的過程,其實就是解析應(yīng)用程序配置文件的AndroidManifest.xml的過程,并從里面得到應(yīng)用程序的相關(guān)信息,例如得到引用程序的組件Activity、Service、Receiver和Content Provider等信息,有了這些信息后,通過ActivityManagerService這個服務(wù),我們就可以在系統(tǒng)中正常地使用這些應(yīng)用程序了。
二、Android應(yīng)用程序的幾種安裝方式
Android上應(yīng)用安裝可以分為以下幾種方式:
- 1、系統(tǒng)安裝:開機的時候,沒有安裝界面
- 2、adb 命令安裝:通過abd命令行安裝,沒有安裝界面
- 3、應(yīng)用市場安裝,這個要視應(yīng)用的權(quán)限,有系統(tǒng)的權(quán)限無安裝界面(例如MUI的小米應(yīng)用商店)
- 4、第三方安裝,有安裝界面,通過packageinstaller.apk來處理安裝及卸載的過程的界面
三、應(yīng)用安裝涉及到的目錄
- /system/app:系統(tǒng)自帶的應(yīng)用程序,獲得adb root 權(quán)限才能刪除
- /data/app:用戶程序安裝的目錄。安裝時把apk文件復(fù)制到此目錄
- /data/data:存放應(yīng)用程序的數(shù)據(jù)
- /data/dalvik-cache:將apk中的dex文件安裝到dalvik-cache目錄下(dex文件是dalvik虛擬機的可執(zhí)行文件,當然,ART-Android Runtime的可執(zhí)行文件格式為.oat,啟動ART時,系統(tǒng)會執(zhí)行dex文件轉(zhuǎn)換至oat文件)
- /data/system:該目錄下的packages.xml文件。類似于Window的注冊表,這個文件是解析apk時由writeLP()創(chuàng)建的,里面記錄了系統(tǒng)的permissons,以及每個apk的name,codePath,flag,ts,version,userid等信息,這些信息主要通過apk的AndroidManifest解析獲取,解析完apk后將更新信息寫入這個文件并保存到flash,下次開機的時候直接從里面讀取相關(guān)信息并添加到內(nèi)存相關(guān)列表中。當有apk升級,安裝或刪除時會更新這個文件。
-/data/system/package.xml與/data/system/package.list:packages.list指定了應(yīng)用的默認存儲位置/data/data/com.xxx.xxx;package.xml中包含了該應(yīng)用申請的權(quán)限、簽名和代碼所在的位置等信息系,并且兩者都有同一個userld。之所以每個應(yīng)用都要一個userId,是因為Android在系統(tǒng)設(shè)計上把每個應(yīng)用當做Linux系統(tǒng)上的一個用戶對待,這樣就可以利用已有的Linux用戶管理機制來設(shè)計Android應(yīng)用,比如應(yīng)用目錄,應(yīng)用權(quán)限,應(yīng)用進程管理等。
四、安裝流程概述
apk的大體流程如下:
- 第一步:拷貝文件到指定的目錄:
在Android系統(tǒng)中,apk安裝文件是會被保存起來的,默認情況下,用戶安裝的apk首先會被拷貝到/data/app目錄下,/data/app目錄是用戶有權(quán)限訪問的目錄,在安裝apk的時候會自動選擇該目錄存放用戶安裝的文件,而系統(tǒng)出場的apk文件則被放到了/system分區(qū)下,包括/system/app,/system/vendor/app,以及/system/priv-app等等,該分區(qū)只有ROOT權(quán)限的用戶才能訪問,這也就是為什么在沒有Root手機之前,我們沒法刪除系統(tǒng)出場的app的原因了。- 第二步:解壓縮apk,寶貝文件,創(chuàng)建應(yīng)用的數(shù)據(jù)目錄
為了加快app的啟動速度,apk在安裝的時候,會首先將app的可執(zhí)行文件dex拷貝到/data/dalvik-cache目錄,緩存起來。然后,在/data/data/目錄下創(chuàng)建應(yīng)用程序的數(shù)據(jù)目錄(以應(yīng)用的包名命名),存放在應(yīng)用的相關(guān)數(shù)據(jù),如數(shù)據(jù)庫、xml文件、cache、二進制的so動態(tài)庫等。- 第三步:解析apk的AndroidManifest.xml文件
Android系統(tǒng)中,也有一個類似注冊表的東西,用來記錄當前所有安裝的應(yīng)用的基本信息,每次系統(tǒng)安裝或者卸載了任何apk文件,都會更新這個文件。這個文件位于如下目錄:/data/system/packages.xml。系統(tǒng)在安裝這個apk的過程中,會解析apk的AndroidManifest.xml文件,提取出這個apk的重要信息寫入到packages.xml文件中,這些信息包括:權(quán)限、應(yīng)用包名、APK的安裝位置、版本、userID等等。由此,我們就知道了為什么一些應(yīng)用市場和軟件管理類的app能夠很清楚地知道當前手機所安裝的所有app,以及這些app的詳細信息了。另外一件事就是Linux的用戶Id和用戶組Id,以便他們可以獲得合適的運行權(quán)限。以上都是由PackageServcieManager完成的,后面我們會重點介紹PackageServiceManager。
- 第四步:顯示快捷方式
如果這些應(yīng)用程序在PackageManagerService服務(wù)注冊好了,如果我們想要在Android桌米上看到這些應(yīng)用程序,還需要有一個Home應(yīng)用程序,負責從PackageManagerService服務(wù)中把這些安裝好的應(yīng)用程序取出來,并以友好的方式在桌面上展現(xiàn)出來,例如以快捷圖標的形式。在Android系統(tǒng)中,負責把系統(tǒng)中已經(jīng)安裝的應(yīng)用程序在桌面中展現(xiàn)出來的Home應(yīng)用就是Launcher了。
五、PackageInstaller.apk與PackageManger
(一)、PackageInstaller概述
PackagInstaller是安卓上默認的應(yīng)用程序,用它來安裝普通文件。PackageInstaller提供了用戶界面來管理應(yīng)用或者包文件。PackageInstaller調(diào)用一個叫做InstallAppProgress的activity來獲取用戶發(fā)出的指令。InstallAppProgress會請求Package Manager服務(wù),然后通過installed來安裝包文件。
installed這個守護進程的首要角色就是獲取來自Package Manager服務(wù)的請求,而該請求是通過Linux套接字/dev/socket/installed獲得的。installed使用管理員權(quán)限執(zhí)行一系列步驟來安裝APK。
(二)、PackageInstaller內(nèi)容解析
PackageInstaller的結(jié)構(gòu)如下:
這里面重點介紹以下兩個類
- PackageInstallerActivity:主要是檢查各種權(quán)限,展示被安裝應(yīng)用的向相關(guān)信息,最后跳轉(zhuǎn)到實際安裝應(yīng)用的InstallAppProgress
- InstallAppProgress:也是進行了一系列的操作,最終把安裝交給了PackageManager.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags, installerPackageName, verificationParams, null);
下面我們就來看看
六、普通的APK安裝方式的界面
普通的APK安裝方式 一般是經(jīng)過下面的兩個界面的
上面的兩個界面分別是PackageInstallerActivity和InstallAppProgress
七、PackageInstallerActivity類的安裝流程
(一)、PackageInstallerActivity類
/*
* This activity is launched when a new application is installed via side loading
* The package is first parsed and the user is notified of parse errors via a dialog.
* If the package is successfully parsed, the user is notified to turn on the install unknown
* applications setting. A memory check is made at this point and the user is notified of out
* of memory conditions if any. If the package is already existing on the device,
* a confirmation dialog (to replace the existing package) is presented to the user.
* Based on the user response the package is then installed by launching InstallAppConfirm
* sub activity. All state transitions are handled in this activity
*/
public class PackageInstallerActivity extends Activity implements OnCancelListener, OnClickListener {
...
...
}
我們是知道PackageInstallerActivity是一個Activity并且實現(xiàn)了OnCancelListener和OnClickListener接口,下面我們來看一下注釋。
當通過渠道安裝一個應(yīng)用程序的時候,會啟動這個Activity。如果在首次解析這個安裝包的時候出現(xiàn)解析錯誤,會通過對話框的形式告訴用戶。如果首次解析安裝包的時候,成功解析了,則會通知用戶去打開"安裝未知應(yīng)用程序設(shè)置"。在啟動Activity的時候會進行內(nèi)存檢查,如果內(nèi)存不足會通知用戶。如果這個應(yīng)用程序已經(jīng)在這個設(shè)備安裝過了,則會向用戶彈出一個對話框詢問用戶是否"替換現(xiàn)有應(yīng)用程序的安裝包"。基于用戶的回應(yīng),然后通過InstallAppConfirm的子Activity來安裝應(yīng)用程序。在這Activity中處理所有狀態(tài)的轉(zhuǎn)換。
大家平時寫Activity一般都是先在onCreate方法里面做一些初始化的操作,那我們來看下PackageInstallerActivity的onCreate里面都做了什么?
(二)、PackageInstallerActivity類的onCreate()方法
代碼在PackageInstallerActivity.java 439行
PackageManager mPm;
UserManager mUserManager;
PackageInstaller mInstaller;
PackageInfo mPkgInfo;
ApplicationInfo mSourceInfo;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
//第一步
// 一個PackageManager對象,具體用來執(zhí)行安裝操作
mPm = getPackageManager();
// PackageInstaller對象,在該對象中包含了安裝APK的基本信息
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
// 第二步
final Intent intent = getIntent();
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
final int sessionId = intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1);
final PackageInstaller.SessionInfo info = mInstaller.getSessionInfo(sessionId);
if (info == null || !info.sealed || info.resolvedBaseCodePath == null) {
Log.w(TAG, "Session " + mSessionId + " in funky state; ignoring");
finish();
return;
}
mSessionId = sessionId;
mPackageURI = Uri.fromFile(new File(info.resolvedBaseCodePath));
mOriginatingURI = null;
mReferrerURI = null;
} else {
mSessionId = -1;
mPackageURI = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
// 第三步
final boolean unknownSourcesAllowedByAdmin = isUnknownSourcesAllowedByAdmin();
final boolean unknownSourcesAllowedByUser = isUnknownSourcesEnabled();
boolean requestFromUnknownSource = isInstallRequestFromUnknownSource(intent);
//第四步
mInstallFlowAnalytics = new InstallFlowAnalytics();
mInstallFlowAnalytics.setContext(this);
mInstallFlowAnalytics.setStartTimestampMillis(SystemClock.elapsedRealtime());
mInstallFlowAnalytics.setInstallsFromUnknownSourcesPermitted(unknownSourcesAllowedByAdmin
&& unknownSourcesAllowedByUser);
mInstallFlowAnalytics.setInstallRequestFromUnknownSource(requestFromUnknownSource);
mInstallFlowAnalytics.setVerifyAppsEnabled(isVerifyAppsEnabled());
mInstallFlowAnalytics.setAppVerifierInstalled(isAppVerifierInstalled());
mInstallFlowAnalytics.setPackageUri(mPackageURI.toString());
//第五步
final String scheme = mPackageURI.getScheme();
if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
Log.w(TAG, "Unsupported scheme " + scheme);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
finish();
return;
}
final PackageUtil.AppSnippet as;
//處理scheme為package的情況
if ("package".equals(mPackageURI.getScheme())) {
mInstallFlowAnalytics.setFileUri(false);
try {
//獲取package對應(yīng)的Android應(yīng)用信息PackageInfo如果應(yīng)用名稱,權(quán)限列表等...
mPkgInfo = mPm.getPackageInfo(mPackageURI.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS | PackageManager.GET_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
}
//如果無法獲取PackageInfo,彈出一個錯誤的對話框,然后直接退出安裝
if (mPkgInfo == null) {
Log.w(TAG, "Requested package " + mPackageURI.getScheme()
+ " not available. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
mInstallFlowAnalytics.setPackageInfoObtained();
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_FAILED_PACKAGE_MISSING);
return;
}
//創(chuàng)建AppSnipet對象,該對象封裝了待安裝Android應(yīng)用的標題和圖標
as = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
mPm.getApplicationIcon(mPkgInfo.applicationInfo));
} else {
// 處理scheme為file的情況
mInstallFlowAnalytics.setFileUri(true);
// 獲取APK文件的實際路徑
final File sourceFile = new File(mPackageURI.getPath());
// 創(chuàng)建APK文件的分析器 parsed。同時分析安裝包,后面會單獨講解
PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);
// Check for parse errors
if (parsed == null) {
//如果parsed == null,則說明解析出錯,則彈出對話框,并退出安裝
Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
mInstallFlowAnalytics.setPackageInfoObtained();
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_FAILED_TO_GET_PACKAGE_INFO);
return;
}
//解析沒出錯,生成PackageInfo,這里面包含APK文件的相關(guān)信息
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
//manifest校驗
mPkgDigest = parsed.manifestDigest;
// 設(shè)置apk的程序名稱和圖標,這是另一種創(chuàng)建AppSnippet的方式
as = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
}
mInstallFlowAnalytics.setPackageInfoObtained();
//set view
// 第六步
setContentView(R.layout.install_start);
mInstallConfirm = findViewById(R.id.install_confirm_panel);
mInstallConfirm.setVisibility(View.INVISIBLE);
PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
mOriginatingUid = getOriginatingUid(intent);
// 第七步
// Block the install attempt on the Unknown Sources setting if necessary.
// 如果必須要禁止來自未知來源的安裝
if (!requestFromUnknownSource) {
//初始化操作
initiateInstall();
return;
}
// 第八步
// If the admin prohibits it, or we're running in a managed profile, just show error
// and exit. Otherwise show an option to take the user to Settings to change the setting.
// 未知來源檢查,如果admin禁止則直接提示錯誤退出。否則顯示選項提示用戶去設(shè)置修改上修改設(shè)置
final boolean isManagedProfile = mUserManager.isManagedProfile();
if (!unknownSourcesAllowedByAdmin
|| (!unknownSourcesAllowedByUser && isManagedProfile)) {
showDialogInner(DLG_ADMIN_RESTRICTS_UNKNOWN_SOURCES);
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
} else if (!unknownSourcesAllowedByUser) {
// Ask user to enable setting first
showDialogInner(DLG_UNKNOWN_SOURCES);
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_BLOCKED_BY_UNKNOWN_SOURCES_SETTING);
} else {
initiateInstall();
}
}
代碼很多,那我們來看重點
- 第一步: 給mPm、mInstaller、mUserManager進行初始化。
- 第二步: 獲取mSessionId、mPackageURI、mOriginatingURI、mReferrerURI 這四個是重要參數(shù)
- 第三步: 判斷是否是來自未知來源的包,看是否是非官網(wǎng)下載的app,這里面有三個判斷依次為:
- isUnknownSourcesAllowedByAdmin():設(shè)備管理員是否限制來自未知來源的安裝
- isUnknownSourcesEnabled():在“設(shè)置”中用戶是否啟用未知來源
- isInstallRequestFromUnknownSource(Intent):安裝請求是否來自一個未知的源
- 第四步: 創(chuàng)建mInstallFlowAnalytics對象,并進行一些字段的賦值InstallFlowAnalytics.java是用來安裝軟件包的分析工具。
- 第五步: 檢查scheme是否支持,如果不支持則直接結(jié)束,如果支持scheme,這里面又分為兩種情況
- 處理scheme為package的情況
- 處理scheme為file的情況
無論是上面的哪種情況,都是要首先獲取PackageInfo對象,如果scheme是package的情況下是直接調(diào)用PackageManager. getPackageInfo()方法獲取的;如果scheme是file則是通過APK的實際路徑即mPackageURI.getPath()來構(gòu)造一個File,然后通過 PackageParser.generatePackageInfo()方法來獲取的。然后創(chuàng)建AppSnippet對象,AppSnippet是PackageUtil的靜態(tài)內(nèi)部類,內(nèi)部封裝了icon和label。注意不同sheme- 第六步: 設(shè)置activity的主界面
- 第七步: 判斷是限制未知來源的安裝包
- 第八步: 進行未知來源的檢查
大家如果仔細閱讀源碼的話,就會知道,無論是否限制未知來源的安裝包,如果沒有問題都會調(diào)用initiateInstall();來進行初始化操作
OK,PackageInstallerActivity類的onCreate()方法分析完畢,為了讓大家更好的理解,下面講解下這里面涉及到幾個核心方法:
- PackageUtil.getPackageInfo(sourceFile)
- initiateInstall()
1、PackageUtil.getPackageInfo(sourceFile)方法解析
/**
* Utility method to get package information for a given {@link File}
*/
public static PackageParser.Package getPackageInfo(File sourceFile) {
final PackageParser parser = new PackageParser();
try {
PackageParser.Package pkg = parser.parseMonolithicPackage(sourceFile, 0);
parser.collectManifestDigest(pkg);
return pkg;
} catch (PackageParserException e) {
return null;
}
}
我們看到這個方法里面主要就是new了一個PackageParser對象
然后調(diào)用parser.parseMonolithicPackage(sourceFile, 0);方法和 parser.collectManifestDigest(pkg);方法,如果不拋異常就直接返回 PackageParser.Package對象,如果拋異常則直接返回null。看來上面這兩個方法很重,關(guān)于parser.parseMonolithicPackage(sourceFile, 0);方法請參考APK安裝流程詳解6——PackageParser解析APK(上)中四、PackageParse#parseMonolithicPackage(File, int)方法解析。
2、initiateInstall()方法解析
代碼在PackageInstallerActivity.java 400行
private void initiateInstall() {
String pkgName = mPkgInfo.packageName;
// Check if there is already a package on the device with this name
// but it has been renamed to something else.
//第一步
String[] oldName = mPm.canonicalToCurrentPackageNames(new String[] { pkgName });
if (oldName != null && oldName.length > 0 && oldName[0] != null) {
pkgName = oldName[0];
mPkgInfo.packageName = pkgName;
mPkgInfo.applicationInfo.packageName = pkgName;
}
// Check if package is already installed. display confirmation dialog if replacing pkg
// 第二步
try {
// This is a little convoluted because we want to get all uninstalled
// apps, but this may include apps with just data, and if it is just
// data we still want to count it as "installed".
// 獲取設(shè)備上的殘存數(shù)據(jù),并且標記為“installed”,實際上已經(jīng)被卸載的應(yīng)用
mAppInfo = mPm.getApplicationInfo(pkgName,
PackageManager.GET_UNINSTALLED_PACKAGES);
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
如果應(yīng)用是被卸載的,但是又是被標識成安裝過的,則認為是新安裝
// 如果應(yīng)用是被卸載的,但是又是被標識成安裝過的,則認為是新安裝
mAppInfo = null;
}
} catch (NameNotFoundException e) {
mAppInfo = null;
}
mInstallFlowAnalytics.setReplace(mAppInfo != null);
mInstallFlowAnalytics.setSystemApp(
(mAppInfo != null) && ((mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0));
// 第三步
startInstallConfirm();
}
initiateInstall()方法里面主要做了三件事
- 第一步:檢查設(shè)備是否有一個現(xiàn)在不同名,但是曾經(jīng)是相同的包名,即是否是同名安裝,如果是則后續(xù)是替換安裝。
- 第二步:檢查設(shè)備上是否已經(jīng)安裝了這個安裝包,如果是,后面是替換安裝
- 第三步:調(diào)用startInstallConfirm()這個方法是安裝的核心代碼。
下面我們就來看下startInstallConfirm()方法里面的具體實現(xiàn)
①、startInstallConfirm()方法解析
代碼在PackageInstallerActivity.java 114行
private void startInstallConfirm() {
TabHost tabHost = (TabHost)findViewById(android.R.id.tabhost);
tabHost.setup();
ViewPager viewPager = (ViewPager)findViewById(R.id.pager);
TabsAdapter adapter = new TabsAdapter(this, tabHost, viewPager);
adapter.setOnTabChangedListener(new TabHost.OnTabChangeListener() {
@Override
public void onTabChanged(String tabId) {
if (TAB_ID_ALL.equals(tabId)) {
mInstallFlowAnalytics.setAllPermissionsDisplayed(true);
} else if (TAB_ID_NEW.equals(tabId)) {
mInstallFlowAnalytics.setNewPermissionsDisplayed(true);
}
}
});
// If the app supports runtime permissions the new permissions will
// be requested at runtime, hence we do not show them at install.
// 根據(jù)sdk版本來判斷app是否支持運行時權(quán)限,這里會顯示運行時權(quán)限
boolean supportsRuntimePermissions = mPkgInfo.applicationInfo.targetSdkVersion
>= Build.VERSION_CODES.M;
//顯示權(quán)限列表的變量,true顯示權(quán)限列表,false 未顯示權(quán)限列表
boolean permVisible = false;
mScrollView = null;
mOkCanInstall = false;
int msg = 0;
//perms這個對象包括了該應(yīng)用的用戶的uid以及相應(yīng)的一些權(quán)限,以及權(quán)限組信息
// perms這個對象包括了該應(yīng)用的用戶的uid以及相應(yīng)的一些權(quán)限,以及權(quán)限組的信息
AppSecurityPermissions perms = new AppSecurityPermissions(this, mPkgInfo);
// 獲取隱私相關(guān)權(quán)限的數(shù)量
final int N = perms.getPermissionCount(AppSecurityPermissions.WHICH_ALL);
//判斷是否為已經(jīng)安裝過的應(yīng)用
if (mAppInfo != null) {
// 如果已經(jīng)安裝過則繼續(xù)判斷是否為系統(tǒng)應(yīng)用
msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
? R.string.install_confirm_question_update_system
: R.string.install_confirm_question_update;
// 用來顯示權(quán)限列表的scrollview
mScrollView = new CaffeinatedScrollView(this);
// 如果顯示的內(nèi)容超過了mScrollView,則就會折疊可以滾動
mScrollView.setFillViewport(true);
boolean newPermissionsFound = false;
if (!supportsRuntimePermissions) {
//針對更新應(yīng)用程序相對于舊版本而判斷是否加入新的權(quán)限
newPermissionsFound =
(perms.getPermissionCount(AppSecurityPermissions.WHICH_NEW) > 0);
mInstallFlowAnalytics.setNewPermissionsFound(newPermissionsFound);
if (newPermissionsFound) {
//將新的權(quán)限列表視頻添加到滾動視圖中
permVisible = true;
mScrollView.addView(perms.getPermissionsView(
AppSecurityPermissions.WHICH_NEW));
}
}
if (!supportsRuntimePermissions && !newPermissionsFound) {
// 沒有設(shè)置任何權(quán)限,只顯示應(yīng)用程序名稱和圖標
LayoutInflater inflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
TextView label = (TextView)inflater.inflate(R.layout.label, null);
label.setText(R.string.no_new_perms);
mScrollView.addView(label);
}
adapter.addTab(tabHost.newTabSpec(TAB_ID_NEW).setIndicator(
getText(R.string.newPerms)), mScrollView);
} else {
// 應(yīng)用沒有被安裝過,則將相應(yīng)的控件隱藏
findViewById(R.id.tabscontainer).setVisibility(View.GONE);
findViewById(R.id.divider).setVisibility(View.VISIBLE);
}
// 如果至少設(shè)置了一個權(quán)限
if (!supportsRuntimePermissions && N > 0) {
permVisible = true;
LayoutInflater inflater = (LayoutInflater)getSystemService(
Context.LAYOUT_INFLATER_SERVICE);
//解析權(quán)限列表的視圖
View root = inflater.inflate(R.layout.permissions_list, null);
if (mScrollView == null) {
mScrollView = (CaffeinatedScrollView)root.findViewById(R.id.scrollview);
}
// 添加到權(quán)限列表的視圖
((ViewGroup)root.findViewById(R.id.permission_list)).addView(
perms.getPermissionsView(AppSecurityPermissions.WHICH_ALL));
adapter.addTab(tabHost.newTabSpec(TAB_ID_ALL).setIndicator(
getText(R.string.allPerms)), root);
}
mInstallFlowAnalytics.setPermissionsDisplayed(permVisible);
if (!permVisible) {
// 如果不顯示權(quán)限列表
if (mAppInfo != null) {
// 如果是更新安裝包,并且沒有任何權(quán)限要求
// This is an update to an application, but there are no
// permissions at all.
//判斷是否是系統(tǒng)應(yīng)用來設(shè)置布局文件
msg = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
? R.string.install_confirm_question_update_system_no_perms
: R.string.install_confirm_question_update_no_perms;
} else {
// 是新安裝的app并且沒有權(quán)限列表
// This is a new application with no permissions.
msg = R.string.install_confirm_question_no_perms;
}
//設(shè)置相應(yīng)的UI
tabHost.setVisibility(View.GONE);
mInstallFlowAnalytics.setAllPermissionsDisplayed(false);
mInstallFlowAnalytics.setNewPermissionsDisplayed(false);
findViewById(R.id.filler).setVisibility(View.VISIBLE);
findViewById(R.id.divider).setVisibility(View.GONE);
mScrollView = null;
}
if (msg != 0) {
((TextView)findViewById(R.id.install_confirm_question)).setText(msg);
}
mInstallConfirm.setVisibility(View.VISIBLE);
//這個是關(guān)鍵的控件,即點擊安裝button
mOk = (Button)findViewById(R.id.ok_button);
//這個是關(guān)鍵的控件,即點擊取消button
mCancel = (Button)findViewById(R.id.cancel_button);
mOk.setOnClickListener(this);
mCancel.setOnClickListener(this);
if (mScrollView == null) {
// There is nothing to scroll view, so the ok button is immediately
// set to install.
mOk.setText(R.string.install);
mOkCanInstall = true;
} else {
mScrollView.setFullScrollAction(new Runnable() {
@Override
public void run() {
mOk.setText(R.string.install);
mOkCanInstall = true;
}
});
}
}
這個方法其實主要是根據(jù)不同的情況來設(shè)置相應(yīng)的UI,主要是將安裝包分為新安裝和更新安裝,在更新安裝里面又分為系統(tǒng)應(yīng)用和非系統(tǒng)應(yīng)用,然后根據(jù)不同的情況來顯示不同的UI,UI這塊主要是通過getPermissionsView方法來獲取不同的權(quán)限View。
PS:AppSecurityPermissions.WHICH_NEW:新加入的權(quán)限
這個重點說下mOk這個Button,因為后面咱們點擊"安裝"按鈕的流程就是從這個按鈕開始的。
(三)、PackageInstallerActivity類中點擊"安裝"的流程詳解
由于PackageInstallerActivity實現(xiàn)了OnClickListener接口,所以點擊事件我們直接找onClick(View)方法即可
1、onClick(View v)方法解析
代碼在PackageInstallerActivity.java 114行
public void onClick(View v) {
if (v == mOk) {
if (mOkCanInstall || mScrollView == null) {
mInstallFlowAnalytics.setInstallButtonClicked();
if (mSessionId != -1) {
//如果原來是確認權(quán)限請求則賦予安裝權(quán)限則退出
mInstaller.setPermissionsResult(mSessionId, true);
// We're only confirming permissions, so we don't really know how the
// story ends; assume success.
mInstallFlowAnalytics.setFlowFinishedWithPackageManagerResult(
PackageManager.INSTALL_SUCCEEDED);
finish();
} else {
startInstall();
}
} else {
mScrollView.pageScroll(View.FOCUS_DOWN);
}
} else if(v == mCancel) {
// Cancel and finish
setResult(RESULT_CANCELED);
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_CANCELLED_BY_USER);
finish();
}
}
我們重點看v == mOk的情況,里面其實就是做了兩件事
- 1、判斷是否可以安裝,mScrollVieww是否為空
- 2、如果可以安裝,那么調(diào)用startInstall()方法
那下面我們來看下startInstall()的具體實現(xiàn)
2、startInstall()方法解析
代碼在PackageInstallerActivity.java 683行
private void startInstall() {
// Start subactivity to actually install the application
Intent newIntent = new Intent();
//帶上安裝包的applicationInfo
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
// 帶上安裝包的URI
newIntent.setData(mPackageURI);
//設(shè)置目標類
newIntent.setClass(this, InstallAppProgress.class);
//帶上安裝包的mPkgDigest
newIntent.putExtra(InstallAppProgress.EXTRA_MANIFEST_DIGEST, mPkgDigest);
// 帶上mInstallFlowAnalytics
newIntent.putExtra(
InstallAppProgress.EXTRA_INSTALL_FLOW_ANALYTICS, mInstallFlowAnalytics);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
//帶上安裝包的mOriginatingURI
newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
}
if (mReferrerURI != null) {
//帶上安裝包的mReferrerURI
newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
}
if (mOriginatingUid != VerificationParams.NO_UID) {
//帶上安裝包的mOriginatingUid 這個uid不是安裝應(yīng)用的uid
newIntent.putExtra(Intent.EXTRA_ORIGINATING_UID, mOriginatingUid);
}
if (installerPackageName != null) {
newIntent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME,
installerPackageName);
}
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
newIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
newIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
}
if(localLOGV) Log.i(TAG, "downloaded app uri="+mPackageURI);
startActivity(newIntent);
finish();
}
上面代碼很簡單,主要是就是構(gòu)造在一個Intent,并且傳遞必須要的數(shù)據(jù),
可以看到在startInstall方法中,主要構(gòu)造一個intent,并且將安裝包信息封裝到intent中,然后跳轉(zhuǎn)到InstallAppProgress類
下面我們就來看下InstallAppProgress這個類的安裝流程
八、InstallAppProgress類的安裝流程
(一)、InstallAppProgress類簡介
/**
* This activity corresponds to a download progress screen that is displayed
* when the user tries
* to install an application bundled as an apk file. The result of the application install
* is indicated in the result code that gets set to the corresponding installation status
* codes defined in PackageManager. If the package being installed already exists,
* the existing package is replaced with the new one.
*/
public class InstallAppProgress extends Activity implements View.OnClickListener, OnCancelListener {
...
...
}
通過上面代碼我們知道InstallAppProgress其實是一個Activity并且實現(xiàn)了OnClickListener和OnCancelListener接口
為了讓大家更好的理解這個類,我們還是看一下這個類的注釋,我的翻譯如下(不喜勿噴):
這個Activity是負責當用戶嘗試安裝APK應(yīng)用程序時的進度顯示的Activity。關(guān)于這個應(yīng)用程序的安裝結(jié)果的狀態(tài)碼是和PackageManager里面定義的安裝狀態(tài)碼一一映射的。如果正在安裝的應(yīng)用已經(jīng)在設(shè)備上存在了,則新的應(yīng)用程序?qū)鎿Q掉老的應(yīng)用程序。
既然它是一個Activity,那么我們先看下他的onCreate()方法
(二)、InstallAppProgress的onCreate(Bundle)方法
代碼在InstallAppProgress.java 167行
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
//第一步
Intent intent = getIntent();
mAppInfo = intent.getParcelableExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO);
mInstallFlowAnalytics = intent.getParcelableExtra(EXTRA_INSTALL_FLOW_ANALYTICS);
mInstallFlowAnalytics.setContext(this);
mPackageURI = intent.getData();
//第二步
final String scheme = mPackageURI.getScheme();
if (scheme != null && !"file".equals(scheme) && !"package".equals(scheme)) {
mInstallFlowAnalytics.setFlowFinished(
InstallFlowAnalytics.RESULT_FAILED_UNSUPPORTED_SCHEME);
throw new IllegalArgumentException("unexpected scheme " + scheme);
}
// 第三步
initView();
}
通過上面代碼我們知道onCreate方法里面主要做了3三件事
- 首先:初始化數(shù)據(jù),把Intent里面的數(shù)據(jù)取出
- 其次:做scheme數(shù)據(jù)過濾,只支持scheme為file或者package格式的
- 最后:最后調(diào)用initView()方法
1、initView()方法詳解
代碼在InstallAppProgress.java 226行
public void initView() {
// 第一步
setContentView(R.layout.op_progress);
// 第二步
// 安裝模式 分為安裝和更新
int installFlags = 0;
PackageManager pm = getPackageManager();
// 第三步
try {
PackageInfo pi = pm.getPackageInfo(mAppInfo.packageName,
PackageManager.GET_UNINSTALLED_PACKAGES);
if(pi != null) {
installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
}
} catch (NameNotFoundException e) {
}
// 第四步
if((installFlags & PackageManager.INSTALL_REPLACE_EXISTING )!= 0) {
Log.w(TAG, "Replacing package:" + mAppInfo.packageName);
}
// 第五步
final PackageUtil.AppSnippet as;
if ("package".equals(mPackageURI.getScheme())) {
as = new PackageUtil.AppSnippet(pm.getApplicationLabel(mAppInfo),
pm.getApplicationIcon(mAppInfo));
} else {
final File sourceFile = new File(mPackageURI.getPath());
as = PackageUtil.getAppSnippet(this, mAppInfo, sourceFile);
}
// 第六步
mLabel = as.label;
PackageUtil.initSnippetForNewApp(this, as, R.id.app_snippet);
mStatusTextView = (TextView)findViewById(R.id.center_text);
mStatusTextView.setText(R.string.installing);
mExplanationTextView = (TextView) findViewById(R.id.center_explanation);
mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);
mProgressBar.setIndeterminate(true);
// Hide button till progress is being displayed
mOkPanel = (View)findViewById(R.id.buttons_panel);
mDoneButton = (Button)findViewById(R.id.done_button);
mLaunchButton = (Button)findViewById(R.id.launch_button);
mOkPanel.setVisibility(View.INVISIBLE);
// 第七步
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
Uri originatingURI = getIntent().getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
Uri referrer = getIntent().getParcelableExtra(Intent.EXTRA_REFERRER);
int originatingUid = getIntent().getIntExtra(Intent.EXTRA_ORIGINATING_UID,
VerificationParams.NO_UID);
ManifestDigest manifestDigest = getIntent().getParcelableExtra(EXTRA_MANIFEST_DIGEST);
VerificationParams verificationParams = new VerificationParams(null, originatingURI,
referrer, originatingUid, manifestDigest);
// 第八步
PackageInstallObserver observer = new PackageInstallObserver();
// 第九步
if ("package".equals(mPackageURI.getScheme())) {
try {
pm.installExistingPackage(mAppInfo.packageName);
observer.packageInstalled(mAppInfo.packageName,
PackageManager.INSTALL_SUCCEEDED);
} catch (PackageManager.NameNotFoundException e) {
observer.packageInstalled(mAppInfo.packageName,
PackageManager.INSTALL_FAILED_INVALID_APK);
}
} else {
pm.installPackageWithVerificationAndEncryption(mPackageURI, observer, installFlags,
installerPackageName, verificationParams, null);
}
}
上面代碼還是比較簡潔的,大體上將initView()分為九個步驟,如下:
- 第一步:設(shè)置當前Activity的布局文件
- 第二步:獲取PackageManager對象
- 第三步:獲取PackgeInfo對象,這里使用pm.getPackageInfo()方法來獲取,主要是判斷待安裝的應(yīng)用程序是否已經(jīng)安裝,因為如果已經(jīng)安裝了,則返回PackgeInfo對象,則安裝模式設(shè)為更新模式,如果沒有安裝,則返回null
- 第四步:如果是替換安裝則打印日志
- 第五步:根據(jù)不同的scheme來給AppSnippet進行賦值,如果scheme為package則意味著更新應(yīng)用程序;scheme為file則意味著是新安裝應(yīng)用程序。
- 第六步:獲取布局文件中的控件
- 第七步:從Intent中獲取相應(yīng)的數(shù)據(jù)信息,為下一步做準備
- 第八步:創(chuàng)建安裝的監(jiān)聽器對象
- 第九步:根據(jù)不用的scheme來進行不同安裝模式下的安裝操作
這個方法里面涉及到三個重要內(nèi)容如下
- 1、PackageInstallObserver
- 2、PackageManager的installExistingPackage(String)方法
- 3、PackageManager的installPackageWithVerificationAndEncryption(PackageInstallObserver , int , String ,VerificationParams , ContainerEncryptionParams )方法
(1)、PackageInstallObserver類詳解
(1.1)、PackageInstallObserver類源碼
代碼在InstallAppProgress.java 218行
class PackageInstallObserver extends IPackageInstallObserver.Stub {
public void packageInstalled(String packageName, int returnCode) {
Message msg = mHandler.obtainMessage(INSTALL_COMPLETE);
msg.arg1 = returnCode;
mHandler.sendMessage(msg);
}
}
通過上面代碼我們知道
PackageInstallObserver其實是繼承IPackageInstallObserver.Stub 類的,在packageInstalled(String, int)方法里面其實向mHandler發(fā)送了一個Message
PS:注意這個Message的what值是INSTALL_COMPLETE,Message的arg1是安裝結(jié)果的code。
那我們看下這個mHandler的里面是怎么操作的
(1.2)、InstallAppProgress類中的Handler對象mHandler解析
代碼在InstallAppProgress.java 76行
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case INSTALL_COMPLETE:
//記錄安裝結(jié)果
mInstallFlowAnalytics.setFlowFinishedWithPackageManagerResult(msg.arg1);
// 第一步
// 判斷是否需要安裝結(jié)束后立即結(jié)束當前界面
if (getIntent().getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT, msg.arg1);
setResult(msg.arg1 == PackageManager.INSTALL_SUCCEEDED
? Activity.RESULT_OK : Activity.RESULT_FIRST_USER,
result);
finish();
return;
}
// 第二步
// Update the status text
mProgressBar.setVisibility(View.INVISIBLE);
// Show the ok button
int centerTextLabel;
int centerExplanationLabel = -1;
LevelListDrawable centerTextDrawable =
(LevelListDrawable) getDrawable(R.drawable.ic_result_status);
// 第三步
if (msg.arg1 == PackageManager.INSTALL_SUCCEEDED) {
// 安裝成功
mLaunchButton.setVisibility(View.VISIBLE);
centerTextDrawable.setLevel(0);
centerTextLabel = R.string.install_done;
// Enable or disable launch button
// 獲取應(yīng)用程序啟動的Intent
mLaunchIntent = getPackageManager().getLaunchIntentForPackage(
mAppInfo.packageName);
boolean enabled = false;
// 判斷應(yīng)用程序啟動Intent是否可用
if(mLaunchIntent != null) {
List<ResolveInfo> list = getPackageManager().
queryIntentActivities(mLaunchIntent, 0);
if (list != null && list.size() > 0) {
enabled = true;
}
}
if (enabled) {
mLaunchButton.setOnClickListener(InstallAppProgress.this);
} else {
mLaunchButton.setEnabled(false);
}
} else if (msg.arg1 == PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE){
// 由于剩余空間不足導(dǎo)致安裝失敗
showDialogInner(DLG_OUT_OF_SPACE);
return;
} else {
// 安裝失敗
// Generic error handling for all other error codes.
centerTextDrawable.setLevel(1);
centerExplanationLabel = getExplanationFromErrorCode(msg.arg1);
centerTextLabel = R.string.install_failed;
mLaunchButton.setVisibility(View.INVISIBLE);
}
// 第四步
if (centerTextDrawable != null) {
centerTextDrawable.setBounds(0, 0,
centerTextDrawable.getIntrinsicWidth(),
centerTextDrawable.getIntrinsicHeight());
mStatusTextView.setCompoundDrawablesRelative(centerTextDrawable, null,
null, null);
}
mStatusTextView.setText(centerTextLabel);
if (centerExplanationLabel != -1) {
mExplanationTextView.setText(centerExplanationLabel);
mExplanationTextView.setVisibility(View.VISIBLE);
} else {
mExplanationTextView.setVisibility(View.GONE);
}
mDoneButton.setOnClickListener(InstallAppProgress.this);
mOkPanel.setVisibility(View.VISIBLE);
break;
default:
break;
}
}
};
由于Message的what值為INSTALL_COMPLETE,我們只需要關(guān)心case值為INSTALL_COMPLETE的情況(不過貌似沒有別的case),case的值為INSTALL_COMPLETE的內(nèi)容主要分為四個步驟,如下:
- 第一步:判斷是否有安裝結(jié)束后(不論成功或者失敗),立即離開當前的需求,如果有這個要求,則完成結(jié)束后,立即返回,如果是安裝成功則resultCode為PackageManager.INSTALL_SUCCEEDED,如果失敗resultCode為Activity.RESULT_FIRST_USER。
- 第二步:更變UI,并且給centerTextDrawable賦值
- 第三步:根據(jù)安裝結(jié)果的code(是msg.arg1)來更新UI及后續(xù)的操作,這里面分為三種情況:
- 安裝結(jié)果code為PackageManager.INSTALL_SUCCEEDED:
表示安裝成功,首先進行UI更新,然后通過調(diào)用getPackageManager().getLaunchIntentForPackage(mAppInfo.packageName);來獲取這個應(yīng)用程序的啟動Intent,接著判斷這個Intent是否可能用,如果可用最后設(shè)置mLaunchButton的監(jiān)聽事件。- 安裝結(jié)果code為PackageManager. INSTALL_FAILED_INSUFFICIENT_STORAGE:
表示由于設(shè)備沒有足夠的存儲空間來安裝該應(yīng)用程序- 安裝結(jié)果code為其他值:
如果不是上面兩個code值則表示安裝失敗,通過調(diào)用getExplanationFromErrorCode(int) 方法來獲取失敗的原因文案提示并更新UI- 第四步:根據(jù)上面的安裝結(jié)果,更新UI。
這里面涉及到兩個重要方法:
- 1、getExplanationFromErrorCode(int)方法
- 2、getPackageManager().getLaunchIntentForPackage(mAppInfo.packageName)方法
下面我們就簡單介紹這兩個方法:
(1.3)、 getExplanationFromErrorCode(int)方法
代碼在InstallAppProgress.java 76行
private int getExplanationFromErrorCode(int errCode) {
Log.d(TAG, "Installation error code: " + errCode);
switch (errCode) {
case PackageManager.INSTALL_FAILED_INVALID_APK:
return R.string.install_failed_invalid_apk;
case PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES:
return R.string.install_failed_inconsistent_certificates;
case PackageManager.INSTALL_FAILED_OLDER_SDK:
return R.string.install_failed_older_sdk;
case PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE:
return R.string.install_failed_cpu_abi_incompatible;
default:
return -1;
}
}
通過上面代碼我知道getExplanationFromErrorCode(int) 主要是根據(jù)不同的code返回不同的String用以提醒用戶。那我們就把上面四個case依次說下
- PackageManager.INSTALL_FAILED_INVALID_APK:他表示無效的APK文件,一般是APK文件有問題
- PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES:表示簽名問題,一般是簽名沖突
- PackageManager.INSTALL_FAILED_OLDER_SDK:表示SDK版本和APK的要求的版本沖突
- PackageManager.INSTALL_FAILED_CPU_ABI_INCOMPATIBLE:由于native的代碼與設(shè)備上的CPU_ABI不兼容
這里先不講解PackageManager的getLaunchIntentForPackage,大家現(xiàn)在這里打個斷點,一會回來看這里
(2)、PackageManager的installExistingPackage(String)方法簡介
代碼在PackageManager.java 3755行
/**
* If there is already an application with the given package name installed
* on the system for other users, also install it for the calling user.
* @hide
*/
// @SystemApi
public abstract int installExistingPackage(String packageName)
throws NameNotFoundException;
咦,它也是一個抽象方法啊?先看下注釋:
如果系統(tǒng)上已經(jīng)有其他用戶安裝了相同包名的應(yīng)用程序,則讓用戶繼續(xù)安裝。
同上,這里先不講解installExistingPackage(String)方法,我們一會詳細講解。
(3)、PackageManager的installPackageWithVerificationAndEncryption(PackageInstallObserver , int , String ,VerificationParams , ContainerEncryptionParams )方法類簡介
代碼在PackageManager.java 3660行
/**
* Similar to
* {@link #installPackage(Uri, IPackageInstallObserver, int, String)} but
* with an extra verification file provided.
*
* @param packageURI The location of the package file to install. This can
* be a 'file:' or a 'content:' URI.
* @param observer An observer callback to get notified when the package installation is
* complete. {@link PackageInstallObserver#packageInstalled(String, Bundle, int)} will be
* called when that happens. This parameter must not be null.
* @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
* {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
* @param installerPackageName Optional package name of the application that
* is performing the installation. This identifies which market
* the package came from.
* @param verificationURI The location of the supplementary verification
* file. This can be a 'file:' or a 'content:' URI. May be
* {@code null}.
* @param manifestDigest an object that holds the digest of the package
* which can be used to verify ownership. May be {@code null}.
* @param encryptionParams if the package to be installed is encrypted,
* these parameters describing the encryption and authentication
* used. May be {@code null}.
* @hide
*/
public abstract void installPackageWithVerification(Uri packageURI,
PackageInstallObserver observer, int flags, String installerPackageName,
Uri verificationURI, ManifestDigest manifestDigest,
ContainerEncryptionParams encryptionParams);
既然它也是一個抽象方法,那我們先來看下注釋:
和installPackage(Uri, IPackageInstallObserver, int, String)有點類似,但是多了一個額外的驗證文件。
這里先簡單的說下我們上面涉及到監(jiān)聽器PackageInstallObserver,因為PackageInstallObserver還是蠻重要的,
(三)、PackageInstallObserver簡介
1、PackageInstallObserver
PackageInstallObserver.java源碼地址
代碼如下:
public class PackageInstallObserver {
private final IPackageInstallObserver2.Stub mBinder = new IPackageInstallObserver2.Stub() {
@Override
public void onUserActionRequired(Intent intent) {
PackageInstallObserver.this.onUserActionRequired(intent);
}
@Override
public void onPackageInstalled(String basePackageName, int returnCode,
String msg, Bundle extras) {
PackageInstallObserver.this.onPackageInstalled(basePackageName, returnCode, msg,
extras);
}
};
/** {@hide} */
public IPackageInstallObserver2 getBinder() {
return mBinder;
}
public void onUserActionRequired(Intent intent) {
}
/**
* This method will be called to report the result of the package
* installation attempt.
*
* @param basePackageName Name of the package whose installation was
* attempted
* @param extras If non-null, this Bundle contains extras providing
* additional information about an install failure. See
* {@link android.content.pm.PackageManager} for documentation
* about which extras apply to various failures; in particular
* the strings named EXTRA_FAILURE_*.
* @param returnCode The numeric success or failure code indicating the
* basic outcome
* @hide
*/
public void onPackageInstalled(String basePackageName, int returnCode, String msg,
Bundle extras) {
}
}
我們看到PackageInstallObserver的getBinder()方法返回的是本地成員變量mBinder,而mBinder又是IPackageInstallObserver2.Stub。所以我們可以說mBinder也是一個AIDL的通信的服務(wù)端。
我們看來下onPackageInstalled(String,int,String,Bundle)方法的注釋:
調(diào)用這個方法來獲取程序安裝的結(jié)果
- 入?yún)?basePackageName:表示安裝包的包名
- 入?yún)?extras,可能為空,如果非空,則在里面包含導(dǎo)致安裝失敗的附加信息。可以參考android.content.pm.PackageManage類中以EXTRA_FAILURE_開頭的字段
- 入?yún)?returnCode:表示安裝成功或者失敗的狀態(tài)碼
這時候就要看一下IPackageInstallObserver2.aidl
2、IPackageInstallObserver2.aidl簡介
IPackageInstallObserver2.aidl源碼地址
/**
* API for installation callbacks from the Package Manager. In certain result cases
* additional information will be provided.
* @hide
*/
oneway interface IPackageInstallObserver2 {
void onUserActionRequired(in Intent intent);
/**
* The install operation has completed. {@code returnCode} holds a numeric code
* indicating success or failure. In certain cases the {@code extras} Bundle will
* contain additional details:
*
* <p><table>
* <tr>
* <td>INSTALL_FAILED_DUPLICATE_PERMISSION</td>
* <td>Two strings are provided in the extras bundle: EXTRA_EXISTING_PERMISSION
* is the name of the permission that the app is attempting to define, and
* EXTRA_EXISTING_PACKAGE is the package name of the app which has already
* defined the permission.</td>
* </tr>
* </table>
*/
void onPackageInstalled(String basePackageName, int returnCode, String msg, in Bundle extras);
}
(1)、"oneway"關(guān)鍵字
首先說下AIDL的關(guān)鍵字oneway"",AIDL可以用關(guān)鍵字"oneway"來表明遠程調(diào)用的行為屬性,使用了該關(guān)鍵字,那么遠程調(diào)用將緊緊是調(diào)用所有的數(shù)據(jù)傳輸過來并立即返回,而不會等待結(jié)果的返回,也是說不會阻塞遠程線程的運行。AIDL接口將最終獲得一個從Binder線程池中產(chǎn)生的調(diào)用(和普通的遠程調(diào)用類似)。如果關(guān)鍵字oneway在本地調(diào)用中被使用,將不會對函數(shù)調(diào)用有任何影響
(2)、理解"注釋"
為了更好的理解設(shè)計者最初的設(shè)想,我們來看下"類"的注釋
包管理其用于安裝的的回調(diào)API。在某些情況下,它可以提供一些必要的信息。
那我們來看下onPackageInstalled的注釋
安裝操作完成后,會包含一個code,由這個code標識成功或者失敗。在特定的情形下它也會包含一些附加信息:
比如INSTALL_FAILED_DUPLICATE_PERMISSION這個安裝失敗:
它的extras 的Bundle 里面可能會存在EXTRA_EXISTING_PERMISSION這個字符串,表示定義app向定義權(quán)限的內(nèi)容,和EXTRA_EXISTING_PACKAGE這個字符串,表示應(yīng)定義了權(quán)限的應(yīng)用程序包名稱。
(3)、為什么要設(shè)計成AIDL
因為你在InstallAppProgress是一個單獨的進程,而PackageManagerService也是一個單獨的進程,假設(shè)InstallAppProgress所在的進程名稱為"InstallAppProgress",你想在"InstallAppProgress"監(jiān)聽"PackageManagerService"進程的結(jié)果,這里面涉及到兩個進程的調(diào)用,是屬于跨進程調(diào)用,所以需要使用Binder來進行進程間通信。
(三)、總結(jié)
PackageInstallObserver類內(nèi)部含有一個AIDL的Binder跨進程通信,當在PackageManagerService中安裝完成,會調(diào)用IPackageInstallObserver2.Stub的onPackageInstalled方法,然后在IPackageInstallObserver2.Stub 的onPackageInstalled方法里面調(diào)用PackageInstallObserver的onPackageInstalled。這樣就通知到了InstallAppProgress的observer對象了
九、InstallAppProgress中涉及到PackageManager的三個方法
大體流程如下,如果如果已經(jīng)存在安裝包了,則更新(調(diào)用PackageManager的installExistingPackage(String)方法),如果沒有新安裝,則進行安裝(PackageManager的installPackageWithVerificationAndEncryption方法),最后無論是更新還是新安裝成功都會調(diào)用PackageManager的getLaunchIntentForPackage方法
因為咱們主要是會講解新安裝,所有后面主要是跟蹤PackageManager的installPackageWithVerificationAndEncryption方法,大家可以自己去跟蹤PackageManager的installExistingPackage(String)方法,其實原理大差不差,很多方法都是公用的。
上一篇文章 APK安裝流程詳解10——PackageParser解析APK(下)
下一篇文章 APK安裝流程詳解12——PackageManagerService中的新安裝流程上(拷貝)