一、APP的安裝
1淑倾、常見(jiàn)安裝方式
系統(tǒng)應(yīng)用和預(yù)制應(yīng)用安裝――開(kāi)機(jī)時(shí)完成征椒,沒(méi)有安裝界面,在PKMS的構(gòu)造函數(shù)中完成安裝
網(wǎng)絡(luò)下載或第三方應(yīng)用安裝――調(diào)用PackageManager.installPackages()碍讨,有安裝界面蒙秒。
-
ADB工具安裝――沒(méi)有安裝界面,它通過(guò)啟動(dòng)pm腳本的形式税肪,然后調(diào)用com.android.commands.pm.Pm類榜田,之后調(diào)用到PMS.installStage()完成安裝箭券。
2辩块、APK的簽名校驗(yàn)理解
V1簽名apk-signature-v1-location.png只是校驗(yàn)了apk資源,并沒(méi)有約束zip,簽名信息存儲(chǔ)在zip/META-INF中荆永。
v2簽名是一個(gè)對(duì)全文件進(jìn)行簽名的方案,能提供更快的應(yīng)用安裝時(shí)間、對(duì)未授權(quán)APK文件的更改提供更多保護(hù).
3具钥、APK安裝過(guò)程
- 開(kāi)機(jī)后掃描應(yīng)用安裝目錄和系統(tǒng)App目錄,解析其中的apk文件將相關(guān)信息加載到PKMS中的數(shù)據(jù)結(jié)構(gòu)中掌动,同時(shí)對(duì)于沒(méi)有對(duì)應(yīng)數(shù)據(jù)目錄的App生成對(duì)應(yīng)的數(shù)據(jù)目錄
- 注冊(cè)包名App等信息宁玫、以及相關(guān)的四大組件到PMS中
- 將解析到的數(shù)據(jù)同步到/data/system/packages.xml中
4、App安裝涉及的目錄理解
- 系統(tǒng)App安裝目錄
1眷射、 /system/app: Android系統(tǒng)App路徑
2佛掖、/system/priv-app: 同上,但比/system/app權(quán)限優(yōu)先級(jí)更高嗅绸,可以拿到ApplicationInfo.PRIVATE_FLAG_PRIVILEGED特殊權(quán)限
3撕彤、/vendor/app: odm或者oem廠商預(yù)制系統(tǒng)App目錄
4、/vendor/priva-app: 同上
- 普通應(yīng)用App安裝目錄
/data/app:用戶App程序安裝的目錄蚀狰。安裝時(shí)Apk會(huì)被拷貝至此目錄
- 用戶數(shù)據(jù)目錄
/data/data:存放應(yīng)用程序的數(shù)據(jù),無(wú)論是系統(tǒng)App還是普通App,App產(chǎn)生的用戶數(shù)據(jù)都存放在/data/data/包名/目錄下职员。
- App注冊(cè)表目錄
/data/system
1、packages.xml:
記錄apk的permissions,,flags,ts,version,uesrid等信息焊切,這些信息主要通apk的AndroidManifest.xml解析獲取芳室,當(dāng)系統(tǒng)進(jìn)行程序安裝堪侯、卸載和更新等操作時(shí)荔仁,均會(huì)更新該文件。
2次洼、packages-backup.xml : 備份文件
3、packages-stopped.xml : 記錄被用戶強(qiáng)行停止的應(yīng)用的Package信息
4遇骑、packages-stopped-backup.xml : pakcages-stoped.xml文件的備份
5、packages.list : 記錄非系統(tǒng)自帶的APK的數(shù)據(jù)信息质蕉,這些APK有變化時(shí)會(huì)更新該文件
5模暗、package.xml文件解析
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<packages>
<version sdkVersion="xxx" databaseVersion="xxx" fingerprint="xxx" />
<version volumeUuid="xxx" sdkVersion="xxx" databaseVersion="xxx" fingerprint="xxx" />
<permissions>
<item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
...
</permissions>
<package name="com.android.providers.telephony" codePath="/system/priv-app/TelephonyProvider" nativeLibraryPath="/system/priv-app/TelephonyProvider/lib" publicFlags="1007402501" privateFlags="8" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="25" versionName="7.1.2" applicationName="電話和短信存儲(chǔ)" sharedUserId="1001" isOrphaned="true">
<sigs count="1">
<cert index="1" key="xxx" />
</sigs>
<perms>
<item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
...
</perms>
<proper-signing-keyset identifier="1" />
</package>
...
<updated-package name="xxx.xxx.xxx" codePath="/system/app/xxx" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="11" nativeLibraryPath="/system/app/xxx/lib" primaryCpuAbi="armeabi-v7a" sharedUserId="1000" />
<shared-user name="android.media" userId="10005">
<sigs count="1">
<cert index="2" />
</sigs>
<perms>
<item name="android.permission.ACCESS_CACHE_FILESYSTEM" granted="true" flags="0" />
...
</perms>
</shared-user>
...
</packages>
package.xml對(duì)應(yīng)的類圖關(guān)系
- BasePermission
BasePermission對(duì)應(yīng)packages.xml中permissions標(biāo)簽的子標(biāo)簽item兑宇,對(duì)于上述所定義的每一項(xiàng)權(quán)限都會(huì)生成一個(gè)BasePermission隶糕。
protection :等級(jí)分為四個(gè)
1站玄、普通權(quán)限(normal)
2、運(yùn)行時(shí)權(quán)限(dangerous)
3株旷、簽名權(quán)限(signature)
4、特殊權(quán)限(privileged)
<permissions>
<item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
...
<permissions/>
-
PermissionsState
PermissionState對(duì)應(yīng)的是<package>標(biāo)簽中的子標(biāo)簽<perms>標(biāo)簽中的內(nèi)容
<package name="com.android.providers.telephony" codePath="/system/priv-app/TelephonyProvider" nativeLibraryPath="/system/priv-app/TelephonyProvider/lib" publicFlags="1007402501" privateFlags="8" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="25" versionName="7.1.2" applicationName="電話和短信存儲(chǔ)" sharedUserId="1001" isOrphaned="true">
<perms>
<item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
...
</perms>
</package>
- PackageSignatures
PackageSignatures對(duì)應(yīng)的是<package>標(biāo)簽中的子標(biāo)簽<sigs>標(biāo)簽中的內(nèi)容
<package name="com.android.providers.telephony" codePath="/system/priv-app/TelephonyProvider" nativeLibraryPath="/system/priv-app/TelephonyProvider/lib" publicFlags="1007402501" privateFlags="8" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="25" versionName="7.1.2" applicationName="電話和短信存儲(chǔ)" sharedUserId="1001" isOrphaned="true">
<sigs count="1">
<cert index="1" key="xxx" />
</sigs>
</package>
- PackageSetting
PackageSetting這個(gè)數(shù)據(jù)結(jié)構(gòu)類是packages.xml里面記錄安裝包信息標(biāo)簽<package>相對(duì)應(yīng)的類锉矢,可以看到PackageSetting繼承了PackageSettingBase類,PackageSettingBase類繼承自SettingBase類沽损。應(yīng)用的基本信息保存在PackageSettingBase類的成員變量中循头,簽名則保存在PackageSignatures中炎疆,權(quán)限狀態(tài)保存在父類的SettingBase的PermissionsState中磷雇。
<package name="com.android.providers.telephony" codePath="/system/priv-app/TelephonyProvider" nativeLibraryPath="/system/priv-app/TelephonyProvider/lib" publicFlags="1007402501" privateFlags="8" ft="11e8dc5d800" it="11e8dc5d800" ut="11e8dc5d800" version="25" versionName="7.1.2" applicationName="電話和短信存儲(chǔ)" sharedUserId="1001" isOrphaned="true">
<sigs count="1">
<cert index="1" key="xxx" />
</sigs>
<perms>
<item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
...
</perms>
<proper-signing-keyset identifier="1" />
</package>
- SharedUserSetting
SharedUserSetting這個(gè)數(shù)據(jù)結(jié)構(gòu)類是packages.xml里面記錄安裝包信息標(biāo)簽<shared-user>相對(duì)應(yīng)的類躏救,它和PackageSetting有一個(gè)共同的父類即SettingBase,即都是通過(guò)父類的PermissionsState來(lái)保存權(quán)限信息崩掘。SharedUserSetting被設(shè)計(jì)的用途主要用來(lái)描述具有相同的sharedUserId的應(yīng)用信息少办,它的成員變量packages保存了所有具有相同sharedUserId的應(yīng)用信息引用,而成員變量userId則是記錄多個(gè)APK共享的UID英妓。共享用戶的應(yīng)用的簽名是相同的,簽名保存在成員變量signatures中(這里有一點(diǎn)需要注意辑畦,由于簽名相同腿倚,Android運(yùn)行時(shí)很容易檢索到某個(gè)應(yīng)用擁有相同的sharedUserId的其他應(yīng)用)。
<shared-user name="android.media" userId="10005">
<sigs count="1">
<cert index="2" />
</sigs>
<perms>
<item name="android.permission.ACCESS_CACHE_FILESYSTEM" granted="true" flags="0" />
...
</perms>
</shared-user>
-
Settings : package.xml 終極大管家類
二暂筝、APP安裝整體流程
代碼倉(cāng)庫(kù):http://androidxref.com/9.0.0_r3/xref/packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
1硬贯、安裝APP代碼入口
<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>
2饭豹、根據(jù)Uri的Scheme協(xié)議不同,跳轉(zhuǎn)到不同的界面
content協(xié)議跳轉(zhuǎn)到InstallStaging墨状,package協(xié)議跳轉(zhuǎn)到PackageInstallerActivity
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
......
Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
// The the installation source as the nextActivity thinks this activity is the source, hence
// set the originating UID and sourceInfo explicitly
nextActivity.putExtra(PackageInstallerActivity.EXTRA_CALLING_PACKAGE, callingPackage);
nextActivity.putExtra(PackageInstallerActivity.EXTRA_ORIGINAL_SOURCE_INFO, sourceInfo);
nextActivity.putExtra(Intent.EXTRA_ORIGINATING_UID, originatingUid);
//1肾砂、content的Uri協(xié)議 : InstallStaging
//2、package的Url協(xié)議:PackageInstallerActivity
if (PackageInstaller.ACTION_CONFIRM_PERMISSIONS.equals(intent.getAction())) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
if (packageUri != null && (packageUri.getScheme().equals(ContentResolver.SCHEME_FILE)
|| packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))) {
// Copy file to prevent it from being changed underneath this process
//1、content的Uri協(xié)議 : InstallStaging
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
//package的Url協(xié)議:PackageInstallerActivity
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Intent result = new Intent();
result.putExtra(Intent.EXTRA_INSTALL_RESULT,
PackageManager.INSTALL_FAILED_INVALID_URI);
setResult(RESULT_FIRST_USER, result);
nextActivity = null;
}
}
if (nextActivity != null) {
startActivity(nextActivity);
}
finish();
}
3饼煞、InstallStaging類的介紹
主要內(nèi)容:將content協(xié)議的Uri轉(zhuǎn)換為package協(xié)議的Uri,然后通過(guò)IO形式寫入到mStagedFile文件中
作用:主要起了轉(zhuǎn)換的作用砖瞧,將content協(xié)議的Uri轉(zhuǎn)換為package協(xié)議,然后跳轉(zhuǎn)到PackageInstallerActivity
@Override
protected void onResume() {
super.onResume();
if (mStagingTask == null) {
if (mStagedFile == null) {
try {
mStagedFile = TemporaryFileManager.getStagedFile(this);
} catch (IOException e) {
showError();
return;
}
}
mStagingTask = new StagingAsyncTask();
mStagingTask.execute(getIntent().getData());
}
}
private final class StagingAsyncTask extends AsyncTask<Uri, Void, Boolean> {
@Override
protected Boolean doInBackground(Uri... params) {
if (params == null || params.length <= 0) {
return false;
}
Uri packageUri = params[0];
try (InputStream in = getContentResolver().openInputStream(packageUri)) {
if (in == null) {
return false;
}
try (OutputStream out = new FileOutputStream(mStagedFile)) {
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
if (isCancelled()) {
return false;
}
out.write(buffer, 0, bytesRead);
}
}
} catch (IOException | SecurityException e) {
Log.w(LOG_TAG, "Error staging apk from content URI", e);
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (session != null) {
Intent broadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.setPackage(
getPackageManager().getPermissionControllerPackageName());
broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallInstalling.this,
mInstallId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
//APP安裝的啟動(dòng)入口
session.commit(pendingIntent.getIntentSender());
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
getPackageManager().getPackageInstaller().abandonSession(mSessionId);
if (!isCancelled()) {
launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
}
}
}
4、PackageInstallerActivity類的介紹
- 它就是在安裝應(yīng)用顯示彈窗的Activity
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
if (icicle != null) {
mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
}
mPm = getPackageManager();
mIpm = AppGlobals.getPackageManager();
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
mInstaller = mPm.getPackageInstaller();
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);
...
//根據(jù)Uri的Scheme進(jìn)行預(yù)處理
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}
bindUi(R.layout.install_confirm, false);
//判斷是否是未知來(lái)源的應(yīng)用床未,如果開(kāi)啟允許安裝未知來(lái)源選項(xiàng)則直接初始化安裝
checkIfAllowedAndInitiateInstall();
}
- 分別對(duì)content和package兩種不同協(xié)議處理
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();//1
switch (scheme) {
case SCHEME_PACKAGE: {
try {
...
} break;
case SCHEME_FILE: {
File sourceFile = new File(packageUri.getPath());
//得到sourceFile的包信息
PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);
if (parsed == null) {
Log.w(TAG, "Parse error when parsing manifest. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
//對(duì)parsed進(jìn)行進(jìn)一步處理得到包信息PackageInfo
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());//3
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;
default: {
Log.w(TAG, "Unsupported scheme " + scheme);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
finish();
return false;
}
}
return true;
}
- 彈窗上顯示是否是非法安裝的處理
private void checkIfAllowedAndInitiateInstall() {
//判斷如果允許安裝未知來(lái)源或者根據(jù)Intent判斷得出該APK不是未知來(lái)源
if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
//初始化安裝
initiateInstall();
return;
}
// 如果管理員限制來(lái)自未知源的安裝, 就彈出提示Dialog或者跳轉(zhuǎn)到設(shè)置界面
if (isUnknownSourcesDisallowed()) {
if ((mUserManager.getUserRestrictionSource(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES,
Process.myUserHandle()) & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
return;
} else {
startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
finish();
}
} else {
handleUnknownSources();
}
}
- InstallStaging.java session.commit() 去執(zhí)行系統(tǒng)framework層
protected void onPostExecute(Boolean success) {
if (session != null) {
Intent broadcastIntent = new Intent(BROADCAST_ACTION);
broadcastIntent.setPackage(
getPackageManager().getPermissionControllerPackageName());
broadcastIntent.putExtra(EventResultPersister.EXTRA_ID, mInstallId);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
InstallInstalling.this,
mInstallId,
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
//APP安裝的啟動(dòng)入口
session.commit(pendingIntent.getIntentSender());
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
getPackageManager().getPackageInstaller().abandonSession(mSessionId);
if (!isCancelled()) {
launchFailure(PackageManager.INSTALL_FAILED_INVALID_APK, null);
}
}
- PackageInstaller.java 類
public void commit(@NonNull IntentSender statusReceiver) {
try {
mSession.commit(statusReceiver);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
- PackageInstallerSession.java類
PackageInstallObserverAdapter繼承PackageInstallObserver : 監(jiān)聽(tīng)安裝APP的過(guò)程
mSessionId是安裝包的會(huì)話id斋扰,mInstallId是等待的安裝事件id
@Override
public void commit(IntentSender statusReceiver) {
Preconditions.checkNotNull(statusReceiver);
...
mActiveCount.incrementAndGet();
final PackageInstallObserverAdapter adapter = new PackageInstallObserverAdapter(mContext,
statusReceiver, sessionId, mIsInstallerDeviceOwner, userId);
//Handler發(fā)送一個(gè)類型為MSG_COMMIT的消息啃洋,通知PMS安裝應(yīng)用
mHandler.obtainMessage(MSG_COMMIT, adapter.getBinder()).sendToTarget();
}
private final Handler.Callback mHandlerCallback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
final PackageInfo pkgInfo = mPm.getPackageInfo(
params.appPackageName, PackageManager.GET_SIGNATURES
| PackageManager.MATCH_STATIC_SHARED_LIBRARIES /*flags*/, userId);
final ApplicationInfo appInfo = mPm.getApplicationInfo(
params.appPackageName, 0, userId);
synchronized (mLock) {
if (msg.obj != null) {
mRemoteObserver = (IPackageInstallObserver2) msg.obj;
}
try {
//PMS開(kāi)始安裝應(yīng)用
commitLocked(pkgInfo, appInfo);
} catch (PackageManagerException e) {
final String completeMsg = ExceptionUtils.getCompleteMessage(e);
Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
destroyInternal();
//安裝時(shí)候出現(xiàn)異常問(wèn)題
dispatchSessionFinished(e.error, completeMsg, null);
}
return true;
}
}
};
private void commitLocked(PackageInfo pkgInfo, ApplicationInfo appInfo)
throws PackageManagerException {
...
//通知 PMS開(kāi)始安裝應(yīng)用
mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
installerPackageName, installerUid, user, mCertificates);
}
總結(jié):
- 根據(jù)Uri的Scheme協(xié)議不同宏娄,跳轉(zhuǎn)到不同的界面,content協(xié)議跳轉(zhuǎn)到InstallStaging绝编,package跳轉(zhuǎn)到PackageInstallerActivity貌踏。
- InstallStaging將content協(xié)議的Uri轉(zhuǎn)換為File協(xié)議,然后跳轉(zhuǎn)到PackageInstallerActivity祖乳。
- PackageInstallerActivity會(huì)分別對(duì)package協(xié)議和file協(xié)議的Uri進(jìn)行處理,如果是file協(xié)議會(huì)解析APK文件得到包信息PackageInfo蜒秤。
- PackageInstallerActivity中會(huì)對(duì)未知來(lái)源進(jìn)行處理,如果允許安裝未知來(lái)源或者根據(jù)Intent判斷得出該APK不是未知來(lái)源作媚,就會(huì)初始化安裝確認(rèn)界面,如果管理員限制來(lái)自未知源的安裝, 就彈出提示Dialog或者跳轉(zhuǎn)到設(shè)置界面纸泡。