一、APP的安裝
1、常見安裝方式
系統應用和預制應用安裝――開機時完成,沒有安裝界面,在PKMS的構造函數中完成安裝
網絡下載或第三方應用安裝――調用PackageManager.installPackages(),有安裝界面。
-
ADB工具安裝――沒有安裝界面,它通過啟動pm腳本的形式,然后調用com.android.commands.pm.Pm類,之后調用到PMS.installStage()完成安裝。
image.png
2、APK的簽名校驗理解
V1簽名apk-signature-v1-location.png只是校驗了apk資源,并沒有約束zip,簽名信息存儲在zip/META-INF中。
v2簽名是一個對全文件進行簽名的方案,能提供更快的應用安裝時間、對未授權APK文件的更改提供更多保護.
3、APK安裝過程
- 開機后掃描應用安裝目錄和系統App目錄,解析其中的apk文件將相關信息加載到PKMS中的數據結構中,同時對于沒有對應數據目錄的App生成對應的數據目錄
- 注冊包名App等信息、以及相關的四大組件到PMS中
- 將解析到的數據同步到/data/system/packages.xml中
4、App安裝涉及的目錄理解
- 系統App安裝目錄
1、 /system/app: Android系統App路徑
2、/system/priv-app: 同上,但比/system/app權限優先級更高,可以拿到ApplicationInfo.PRIVATE_FLAG_PRIVILEGED特殊權限
3、/vendor/app: odm或者oem廠商預制系統App目錄
4、/vendor/priva-app: 同上
- 普通應用App安裝目錄
/data/app:用戶App程序安裝的目錄。安裝時Apk會被拷貝至此目錄
- 用戶數據目錄
/data/data:存放應用程序的數據,無論是系統App還是普通App,App產生的用戶數據都存放在/data/data/包名/目錄下。
- App注冊表目錄
/data/system
1、packages.xml:
記錄apk的permissions,,flags,ts,version,uesrid等信息,這些信息主要通apk的AndroidManifest.xml解析獲取,當系統進行程序安裝、卸載和更新等操作時,均會更新該文件。
2、packages-backup.xml : 備份文件
3、packages-stopped.xml : 記錄被用戶強行停止的應用的Package信息
4、packages-stopped-backup.xml : pakcages-stoped.xml文件的備份
5、packages.list : 記錄非系統自帶的APK的數據信息,這些APK有變化時會更新該文件
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="電話和短信存儲" 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對應的類圖關系
- BasePermission
BasePermission對應packages.xml中permissions標簽的子標簽item,對于上述所定義的每一項權限都會生成一個BasePermission。
protection :等級分為四個
1、普通權限(normal)
2、運行時權限(dangerous)
3、簽名權限(signature)
4、特殊權限(privileged)
<permissions>
<item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
...
<permissions/>
-
PermissionsState
image.png
PermissionState對應的是<package>標簽中的子標簽<perms>標簽中的內容
<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="電話和短信存儲" sharedUserId="1001" isOrphaned="true">
<perms>
<item name="android.permission.WRITE_SETTINGS" granted="true" flags="0" />
...
</perms>
</package>
- PackageSignatures
PackageSignatures對應的是<package>標簽中的子標簽<sigs>標簽中的內容
<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="電話和短信存儲" sharedUserId="1001" isOrphaned="true">
<sigs count="1">
<cert index="1" key="xxx" />
</sigs>
</package>
- PackageSetting
PackageSetting這個數據結構類是packages.xml里面記錄安裝包信息標簽<package>相對應的類,可以看到PackageSetting繼承了PackageSettingBase類,PackageSettingBase類繼承自SettingBase類。應用的基本信息保存在PackageSettingBase類的成員變量中,簽名則保存在PackageSignatures中,權限狀態保存在父類的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="電話和短信存儲" 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這個數據結構類是packages.xml里面記錄安裝包信息標簽<shared-user>相對應的類,它和PackageSetting有一個共同的父類即SettingBase,即都是通過父類的PermissionsState來保存權限信息。SharedUserSetting被設計的用途主要用來描述具有相同的sharedUserId的應用信息,它的成員變量packages保存了所有具有相同sharedUserId的應用信息引用,而成員變量userId則是記錄多個APK共享的UID。共享用戶的應用的簽名是相同的,簽名保存在成員變量signatures中(這里有一點需要注意,由于簽名相同,Android運行時很容易檢索到某個應用擁有相同的sharedUserId的其他應用)。
image.png
<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 終極大管家類
image.png
二、APP安裝整體流程
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、根據Uri的Scheme協議不同,跳轉到不同的界面
content協議跳轉到InstallStaging,package協議跳轉到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協議 : InstallStaging
//2、package的Url協議: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協議 : InstallStaging
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
//package的Url協議: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類的介紹
主要內容:將content協議的Uri轉換為package協議的Uri,然后通過IO形式寫入到mStagedFile文件中
作用:主要起了轉換的作用,將content協議的Uri轉換為package協議,然后跳轉到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安裝的啟動入口
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類的介紹
- 它就是在安裝應用顯示彈窗的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);
...
//根據Uri的Scheme進行預處理
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}
bindUi(R.layout.install_confirm, false);
//判斷是否是未知來源的應用,如果開啟允許安裝未知來源選項則直接初始化安裝
checkIfAllowedAndInitiateInstall();
}
- 分別對content和package兩種不同協議處理
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;
}
//對parsed進行進一步處理得到包信息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() {
//判斷如果允許安裝未知來源或者根據Intent判斷得出該APK不是未知來源
if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
//初始化安裝
initiateInstall();
return;
}
// 如果管理員限制來自未知源的安裝, 就彈出提示Dialog或者跳轉到設置界面
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() 去執行系統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安裝的啟動入口
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 : 監聽安裝APP的過程
mSessionId是安裝包的會話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發送一個類型為MSG_COMMIT的消息,通知PMS安裝應用
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開始安裝應用
commitLocked(pkgInfo, appInfo);
} catch (PackageManagerException e) {
final String completeMsg = ExceptionUtils.getCompleteMessage(e);
Slog.e(TAG, "Commit of session " + sessionId + " failed: " + completeMsg);
destroyInternal();
//安裝時候出現異常問題
dispatchSessionFinished(e.error, completeMsg, null);
}
return true;
}
}
};
private void commitLocked(PackageInfo pkgInfo, ApplicationInfo appInfo)
throws PackageManagerException {
...
//通知 PMS開始安裝應用
mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
installerPackageName, installerUid, user, mCertificates);
}
總結:
- 根據Uri的Scheme協議不同,跳轉到不同的界面,content協議跳轉到InstallStaging,package跳轉到PackageInstallerActivity。
- InstallStaging將content協議的Uri轉換為File協議,然后跳轉到PackageInstallerActivity。
- PackageInstallerActivity會分別對package協議和file協議的Uri進行處理,如果是file協議會解析APK文件得到包信息PackageInfo。
- PackageInstallerActivity中會對未知來源進行處理,如果允許安裝未知來源或者根據Intent判斷得出該APK不是未知來源,就會初始化安裝確認界面,如果管理員限制來自未知源的安裝, 就彈出提示Dialog或者跳轉到設置界面。