1. INSTALACIÓN DE LA APLICACIÓN
1. Métodos de instalación comunes
Aplicación del sistema e instalación de aplicaciones prefabricadas: se completa al iniciar, no hay una interfaz de instalación y la instalación se completa en el constructor de PKMS
Descarga de Internet o instalación de aplicaciones de terceros: llame a PackageManager.installPackages(), hay una interfaz de instalación.
Instalación de la herramienta ADB: no hay una interfaz de instalación, inicia el script pm, luego llama a la clase com.android.commands.pm.Pm y luego llama a PMS.installStage() para completar la instalación.
2. Comprensión de verificación de firma APK
V1签名apk-signature-v1-location.png只是校验了apk资源,并没有约束zip,签名信息存储在zip/META-INF中。
v2签名是一个对全文件进行签名的方案,能提供更快的应用安装时间、对未授权APK文件的更改提供更多保护.
3. Proceso de instalación de APK
开机后扫描应用安装目录和系统App目录,解析其中的apk文件将相关信息加载到PKMS中的数据结构中,同时对于没有对应数据目录的App生成对应的数据目录
注册包名App等信息、以及相关的四大组件到PMS中
将解析到的数据同步到/data/system/packages.xml中
4. Comprensión de los directorios involucrados en la instalación de la aplicación
Directorio de instalación de la aplicación del sistema
1. /system/app: ruta de la aplicación del sistema Android
2. /system/priv-app: igual que arriba, pero con mayor prioridad que el permiso /system/app, puede obtener el permiso especial ApplicationInfo.PRIVATE_FLAG_PRIVILEGED
3. /vendor/app: odm o sistema prefabricado OEM Directorio de aplicaciones
4, /vendor/priva-app: Igual que arriba
Aplicación ordinaria Directorio de instalación de la aplicación
/data/app: el directorio donde está instalado el programa de la aplicación del usuario. Apk se copiará en este directorio durante la instalación
directorio de datos de usuario
/data/data: almacena datos de la aplicación, ya sea una aplicación del sistema o una aplicación normal, los datos de usuario generados por la aplicación se almacenan en el directorio /data/data/package name/.
Directorio de registro de aplicaciones
/data/system
1. packages.xml:
registra los permisos, flags, ts, versión, uesrid y otra información del apk. Esta información se obtiene principalmente analizando el AndroidManifest.xml del apk. Cuando el sistema realiza operaciones como la instalación, desinstalación y actualización del programa actualizará el archivo.
2. packages-backup.xml: archivo de copia de seguridad
3. packages-stopped.xml: registra la información del paquete de la aplicación que el usuario detuvo a la fuerza
4. packages-stopped-backup.xml: copia de seguridad de pakcages-stopped.xml file
5. packages.list: registre la información de datos de los APK que no están incluidos en el sistema. Cuando estos APK cambien, el archivo se actualizará
5. Análisis del archivo 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>
La relación del diagrama de clases BasePermission correspondiente a package.xml
BasePermission corresponde al elemento de subetiqueta de la etiqueta de permisos en packages.xml, y se generará un BasePermission para cada permiso definido anteriormente.
protección: Hay cuatro niveles:
1. Autoridad normal (normal)
2. Autoridad de tiempo de ejecución (peligroso)
3. Autoridad de firma (firma)
4. Autoridad especial (privilegiado)
<permissions>
<item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
...
<permissions/>
PermisosEstado
PermissionState corresponde al contenido en la etiqueta de subetiqueta en la etiqueta
<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>
PaqueteFirmas
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>
Configuración del paquete
La clase de estructura de datos PackageSetting es la clase correspondiente a la etiqueta de información del paquete de instalación registrada en packages.xml.Puede ver que PackageSetting hereda la clase PackageSettingBase y la clase PackageSettingBase hereda de la clase SettingBase. La información básica de la aplicación se almacena en las variables miembro de la clase PackageSettingBase, la firma se almacena en PackageSignatures y el estado del permiso se almacena en PermissionsState de la clase principal SettingBase.
<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>
Configuración de usuario compartido
La clase de estructura de datos SharedUserSetting es la clase correspondiente a la etiqueta de información del paquete de instalación registrado en packages.xml Tiene una clase padre común, SettingBase, con PackageSetting, es decir, la información de permisos se guarda a través del PermissionsState de la clase padre . SharedUserSetting está diseñado para describir la información de la aplicación con el mismo sharedUserId. Sus paquetes de variables miembro guardan todas las referencias de información de la aplicación con el mismo sharedUserId, y la variable miembro userId registra el UID compartido por varios APK. Las firmas de las aplicaciones que comparten usuarios son las mismas, y las firmas se almacenan en las firmas de variables miembro (una cosa a tener en cuenta aquí, debido a que las firmas son las mismas, es fácil para el tiempo de ejecución de Android recuperar otras aplicaciones que tienen el mismo sharedUserId para una aplicación).
<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>
Configuraciones: paquete.xml última clase de ama de llaves
2. El proceso general de instalación de la aplicación
Repositorio de código: http://androidxref.com/9.0.0_r3/xref/packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
1. Instale la entrada del código de la aplicación
<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. De acuerdo con los diferentes protocolos de esquema de Uri, salte a diferentes interfaces.
El protocolo de contenido salta a InstallStaging y el protocolo de paquete salta a 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. Introducción a la clase InstallStaging
Contenido principal: Convierta el Uri del protocolo de contenido al Uri del protocolo del paquete y luego escríbalo en el archivo mStagedFile a través de IO.Función
: Desempeña principalmente el papel de conversión, convierte el Uri del protocolo de contenido al protocolo del paquete y luego salta a 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. Introducción a la clase PackageInstallerActivity
Es la Actividad que muestra la ventana emergente al instalar la aplicación
@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();
}
Manejar por separado dos protocolos diferentes de contenido y paquete
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;
}
La ventana emergente muestra si se trata de un proceso de instalación ilegal
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() para ejecutar la capa del marco del sistema
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);
}
}
Clase PackageInstaller.java
public void commit(@NonNull IntentSender statusReceiver) {
try {
mSession.commit(statusReceiver);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
PackageInstallerSession.java clase
PackageInstallObserverAdapter hereda PackageInstallObserver: monitorear el proceso de instalación de la aplicación
mSessionId es la identificación de la sesión del paquete de instalación, mInstallId es la identificación del evento de instalación en espera
@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);
}
Resumir:
根据Uri的Scheme协议不同,跳转到不同的界面,content协议跳转到InstallStaging,package跳转到PackageInstallerActivity。
InstallStaging将content协议的Uri转换为File协议,然后跳转到PackageInstallerActivity。
PackageInstallerActivity会分别对package协议和file协议的Uri进行处理,如果是file协议会解析APK文件得到包信息PackageInfo。
PackageInstallerActivity中会对未知来源进行处理,如果允许安装未知来源或者根据Intent判断得出该APK不是未知来源,就会初始化安装确认界面,如果管理员限制来自未知源的安装, 就弹出提示Dialog或者跳转到设置界面。