前言
- android系统里app有哪些类型及其安装涉及目录、所需权限是什么?
- apk安装有几种方式?
- apk安装流程会涉及到哪些android系统知识?
- apk安装的过程大体上分哪几步?
- apk安装的过程中涉及到比较重要的类有哪些?分别用来做什么?
- 了解apk安装流程有什么用?
Android系统里app类型、安装涉及目录、所需权限
1./system/framwork:
- 保存的是资源型的应用程序,它们用来打包资源文件
2./system/app:
- 系统自带的应用程序,获得adb root 权限才能删除。
- 如果想修改该目录下的app,必须手动push新的apk进去,该新app文件不会自动被安装,需要系统重启时,系统检查到apk被更新,才会去安装。
- 系统app升级时,实际上是在/data/app里重新安装了一个app,只是这个路径会重新注册到系统那里去,当打开该系统app时,会指向新app的地址。
- 如果你卸载该更新后的系统app,系统只是卸载/data/app里的app,然后恢复/system/app里的app给用户使用。
3./system/priv-app:
- 这里放的是权限更高的系统核心应用,例如系统设置、系统UI、开机launcher等,这个目录非常重要,一般情况下不建议改动。
4./vendor/app:
- 保存设备厂商提供的应用程序
5./data/app:
- 用户程序安装的目录,安装时把apk文件复制到此目录。因为Android机有内部存储和SD卡两部分,很多Android机为了节省内存空间,会把apk安装到SD卡上。如此以来,/data/app在大部分Android机上会有2个,1个在内部存储1个在SD卡里。
6./data/app-private:
- 保存受DRM保护的私有应用程序,例如一个受保护的歌曲或受保护的视频是使用 DRM保护的文件。
7./data/data:
- 存放应用程序数据的目录
8./data/dalvik-cache:
- 存放apk中的dex文件。dex文件是dalvik虚拟机的可执行文件,其大小约为原始apk文件大小的四分之一,但是ART-Android Runtime的可执行文件格式为.oat,所以启动ART时,系统会执行dex文件转换至oat文件
9./data/system:
该目录主要是存放一些系统文件
- packages.xml文件:类似于Window的注册表,这个文件是解析apk时由writeLP()创建的,里面记录了系统的permissons,以及每个apk的name,codePath,flag,ts,version,userid,native 库的存储路径等信息,这些信息主要通过apk的AndroidManifest解析获取,解析完apk后将更新信息写入这个文件并保存到flash,下次开机的时候直接从里面读取相关信息并添加到内存相关列表中。当有apk升级,安装或删除时会更新这个文件。
- pakcages-back.xml:packages.xml文件的备份。
- pakcages-stoped.xml:记录系统中被强制停止的运行的应用信息,系统在强制停止某个应用的时候,会将应用的信息记录在该文件中。
- pakcages-stoped-backup.xml:pakcages-stoped.xml文件的备份。
- package.list:packages.list指定了应用的默认存储位置/data/data/com.xxx.xxx;
- 这5个文件中pakcages-back.xml和pakcages-stoped-backup.xml是备份文件。当Android对文件packages.xml和pakcages-stoped.xml写之前,会先把它们备份,如果写文件成功了,再把备份文件删除。如果写的时候,系统出问题了,重启后在需要读取这两个文件时,如果发现备份文件存在,会使用备份文件的内容,因为源文件可能已经损坏了。其中packages.xml是PackageManagerServcie启动时,需要用到的文件。
APK安装主体流程
1.复制APK到/data/app目录下,解压并扫描安装包
2.将APP的dex文件拷贝到/data/dalvik-cache目录,再在/data/data/目录下创建应用程序的数据目录(以应用包名命令),用来存放应用的数据库、xml文件、cache、二进制的so动态库等
3.解析apk的AndroidManifest.xml文件,注册四大组件,将apk的权限、应用包名、apk的安装位置、版本、userID等重要信息保存在/data/system/packages.xml文件中。这些操作都是在PackageManagerService中完成。
4.资源管理器解析APK里的资源文件。
5.dex2oat操作,对dex文件进行优化,并保存在dalvik-cache目录下。
6.更新权限信息。
7.安装完成后,发送Intent.ACTION_PACKAGE_ADDED广播。
8.显示icon图标,应用程序经过PMS中的逻辑处理后,相当于已经注册好了,如果想要在Android桌面上看到icon图标,则需要Launcher将系统中已经安装的程序展现在桌面上。
总体说来就两件事情拷贝APK和解析APK,解析APK主要是解析APK的应用配置文件AndroidManifest.xml,以便获得它的安装信息。在安装的过程中还会这个应用分配Linux用户ID和Linux用户组ID(以便它可以在系统中获取合适的运行权限)。
Tips,dexopt/dex2oat 操作在不同版本下的区别:
- Dalvik虚拟机-dexopt: 该时机在第一次执行时app时 非安装时(详情见 Android运行流程)
- ART虚拟机-dex2oat:AOT (Ahead-Of-Time) 运行前编译
- Android O(8.0)前,将dex字节码翻译成本地字节码oat(非全量)
- Android O(8.0)开始:开始新增vdex概念,不是为了提升性能,而是为了避免不必要的验证Dex 文件合法性的过程,例如首次安装时进行dex2oat时会校验Dex 文件各个section的合法性,这时候使用的compiler filter 为了照顾安装速度等方面,并没有采用全量编译,当app盘启动后,运行一段时间后,收集了足够多的jit 热点方法信息,Android会在后台重新进行dex2oat,将热点方法编译成机器代码,这时候就不用再重复做验证Dex文件的过程了。
APK安装的几种方式
-
1、系统安装
系统启动后调用 PackageManagerService.main() 初始化注册解析安装工作。PackageManagerService处理各种应用的安装,卸载,管理等工作,开机时由systemServer启动此服务。
第1步:PackageManagerService.main()初始化注册
第2步:建立Java层的installer与C层的intalld的socket联接
第3步:建立PackageHandler消息循环
第4步:调用成员变量mSettings的readLPw()方法恢复上一次的安装信息
第5步:.jar文件的detopt优化
第6步:scanDirLI函数扫描特定目录的APK文件解析
第7步:updatePermissionsLPw()函数分配权限
第8步:调用mSettings.writeLPr()保存安装信息
-
2、adb 命令安装
通过 pm 参数,调用 PM 的 runInstall 方法,进入 PackageManagerService 进行安装工作。
第1步:pm.java的runInstall()方法
第2步:参数不对会调用showUsage方法,弹出使用说明
第3步:正常情况runInstall会调用mPm变量的installPackageWithVerification方法
第4步:由于pm.java中的变量mPm是PackageManagerService的实例,所以实际上是调用PackageManagerService的installPackageWithVerfication()方法
第5步:进入PackageManagerService的doHandleMessage方法的INIT_COPY分支
第6步:成功绑定了com.android.defcontainer.DefaultContainerService服务,进入MCS_BOUND分支
第7步:里面调用PackageManagerService中内部抽象类HandlerParams的子类InstallParams的startCopy方法。
第8步:抽象类的HandlerParams的startCopy方法调用了HandlerParams子类的handleStartCopy和handlerReturnCode两个方法
第9步:handlesStartCopy方法调用了InstallArgs的子类copyApk,它负责将下载的APK文件copy到/data/app
第10步:handleReturnCode调用handleReturnCode方法
第11步:调用PackageManagerService服务的installPackageLI(PackageParser.Package, int, int, UserHandle, String, String,PackageInstalledInfo)方法进行APK扫描。
第12步:上面的方法判断是否APP应安装,调用installNewPackageLI或replacePackageLI方法
第13步:调用updateSettingsLI方法进行更新PackageManagerService的Settings
第14步:发送what值为POST_INSTALL的Message给PackageHandler进行处理
第15步:发送what值为MCS_UNBIND的Message给PackageHandler,进而调用PackageHandler.disconnectService()中断连接
-
3、应用市场安装
这个要视应用的权限,有系统的权限无安装界面(例如MiUI的小米应用商店)。
第1步:调用PackageManagerService的installPackage方法
第2步:上面的方法调用installPackageWithVerfication(),进行权限校验,发送INIT_COPY的msg
第3步:进入PackageManagerService的doHandleMessage方法的INIT_COPY分支
第4步:成功绑定了com.android.defcontainer.DefaultContainerService服务,进入MCS_BOUND分支
第5步:里面调用PackageManagerService中内部抽象类HandlerParams的子类InstallParams的startCopy方法。
第6步:抽象类的HandlerParams的startCopy方法调用了HandlerParams子类的handleStartCopy和handlerReturnCode两个方法
第7步:handlesStartCopy方法调用了InstallArgs的子类copyApk,它负责将下载的APK文件copy到/data/app
第8步:handleReturnCode调用handleReturnCode方法
第9步:调用PackageManagerService服务的installPackageLI(PackageParser.Package, int, int, UserHandle, String, String,PackageInstalledInfo)方法进行APK扫描。
第10步:上面的方法判断是否APP应安装,调用installNewPackageLI或replacePackageLI方法
第11步:调用updateSettingsLI方法进行更新PackageManagerService的Settings
第12步:发送what值为POST_INSTALL的Message给PackageHandler进行处理
第13步:发送what值为MCS_UNBIND的Message给PackageHandler,进而调用PackageHandler.disconnectService()中断连接
-
4、第三方安装
有安装界面,通过PackageInstaller.apk来处理,安装及卸载的过程的界面先调用 InstallStart(是一个Activity) 进行权限检查之后启动 PackageInstallActivity,调用 PackageInstallActivity 的 startInstall 方法,点击 OK 按钮后进入 PackageManagerService 完成拷贝解析安装工作。
第1步:通过隐式跳转启动InstallStart
第2步:在InstallStart的onCreate生命周期里判断如果不允许未知来源的则阻止安装、targetSdkVersion<0阻止安装,如果targetSdkVersion大于等于26(8.0), 且获取不到REQUEST_INSTALL_PACKAGES权限也中止安装,然后启动PackageInstallerActivity
第3步:调用PackageInstallerActivity的onCreate方法初始化安装界面
第4步:初始化界面以后调用initiateInstall方法
第5步:上面的方法调用startInstallConfirm方法,弹出确认和取消安装的按钮
第6步:点击确认按钮,打开新的activity:InstallAppProgress
第7步:InstallAppProgress类初始化带有进度条的界面之后,调用PackageManager的installPackage方法
第8步:PackageManager是PackageManagerService实例,所以就是调用PackageManagerService的installPackage方法
第9步:调用PackageManagerService的installPackage方法
第10步:上面的方法调用installPackageWithVerfication(),进行权限校验,发送INIT_COPY的msg
第11步:进入PackageManagerService的doHandleMessage方法的INIT_COPY分支
第12步:成功绑定了com.android.defcontainer.DefaultContainerService服务,进入MCS_BOUND分支
第13步:里面调用PackageManagerService中内部抽象类HandlerParams的子类InstallParams的startCopy方法。
第14步:抽象类的HandlerParams的startCopy方法调用了HandlerParams子类的handleStartCopy和handlerReturnCode两个方法
第15步:handlesStartCopy方法调用了InstallArgs的子类copyApk,它负责将下载的APK文件copy到/data/app
第16步:handleReturnCode调用handleReturnCode方法
第17步:调用PackageManagerService服务的installPackageLI(PackageParser.Package, int, int, UserHandle, String, String,PackageInstalledInfo)方法进行APK扫描。
第18步:上面的方法判断是否APP应安装,调用installNewPackageLI或replacePackageLI方法
第19步:调用updateSettingsLI方法进行更新PackageManagerService的Settings
第20步:发送what值为POST_INSTALL的Message给PackageHandler进行处理
第21步:发送what值为MCS_UNBIND的Message给PackageHandler,进而调用PackageHandler.disconnectService()中断连接
以上4种安装apk方式最终都会走到PackageManagerService,其中第4种方式走的流程最长最完整,所以下面我们会用第4种方式的流程进行代码详细分析。
APK安装详细代码流程(android-10.0.0_r14)
-
第1步 通过隐式跳转启动InstallStart
InstallStart是从Android8.0开始PackageInstaller.apk的入口Activity,7.0 隐式匹配的 Activity 是 PackageInstallerActivity。
platform/frameworks/base/packages/PackageInstaller/AndroidManifest.xml
...
<activity android:name=".InstallStart"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
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="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="package" />
<data android:scheme="content" />
</intent-filter>
<intent-filter android:priority="1">
<action android:name="android.content.pm.action.CONFIRM_INSTALL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
...
从上面的Manifest文件我们可以看出除了通过android:mimeType=application/vnd.android.package-archive
能启动入口Activity-InstallStart,还能通过action为android.intent.action.INSTALL_PACKAGE
的其他Scheme以及"android.content.pm.action.CONFIRM_INSTALL
启动,其中定义了两个 scheme:content 和 package,这2种scheme数据会在后面流程里的PackageInstallerActivity里解析通过不同的方式获取到安装包信息,最后在InstallInstalling再通过不同的安装方式来完成安装动作(后续会有详细分析)。
content: 在 file 协议的处理中调用了
PackageUtil.getPackageInfo
方法,方法内部调用了PackageParser.parsePackage()
把 APK 文件的 manifest 和签名信息都解析完成并保存在了 Package,Package 包含了该 APK 的所有信息,调用 PackageParser.generatePackageInfo 生成 PackageInfo。package: 在 package 协议中调用了
PackageManager.getPackageInfo
方法生成 PackageInfo,PackageInfo 是跨进程传递的包数据(activities、receivers、services、providers、permissions等等)包含 APK 的所有信息。-
第2步 InstallStart启动,进入其onCreate()生命周期
其实onCreate()生命周期里主要做了如下事情:
1.判断当前的apk包是否是可信任来源,如果是不可信任来源,且没有申请不可信任来源包安装权限,则会强制结束当前的Activity。
2.此时就会从data从获取到我们传递进来的apk包的地址uri,并且设置下一个启动的Activity为InstallStaging。
3.启动InstallStaging 这个Activity。
源码如下:
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mIPackageManager = AppGlobals.getPackageManager();
Intent intent = getIntent();
String callingPackage = getCallingPackage();
final boolean isSessionInstall =
PackageInstaller.ACTION_CONFIRM_INSTALL.equals(intent.getAction());
// If the activity was started via a PackageInstaller session, we retrieve the calling
// package from that session
final int sessionId = (isSessionInstall
? intent.getIntExtra(PackageInstaller.EXTRA_SESSION_ID, -1)
: -1);
if (callingPackage == null && sessionId != -1) {
PackageInstaller packageInstaller = getPackageManager().getPackageInstaller();
PackageInstaller.SessionInfo sessionInfo = packageInstaller.getSessionInfo(sessionId);
callingPackage = (sessionInfo != null) ? sessionInfo.getInstallerPackageName() : null;
}
final ApplicationInfo sourceInfo = getSourceInfo(callingPackage);
final int originatingUid = getOriginatingUid(sourceInfo);
boolean isTrustedSource = false;
// 判断是否勾选“未知来源”选项
if (sourceInfo != null
&& (sourceInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false);
}
if (!isTrustedSource && originatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
final int targetSdkVersion = getMaxTargetSdkVersionForUid(this, originatingUid);
// 如果targetSdkVerison小于0中止安装
if (targetSdkVersion < 0) {
Log.w(LOG_TAG, "Cannot get target sdk version for uid " + originatingUid);
// Invalid originating uid supplied. Abort install.
mAbortInstall = true;
// 如果targetSdkVersion大于等于26(8.0), 且获取不到REQUEST_INSTALL_PACKAGES权限中止安装
} else if (targetSdkVersion >= Build.VERSION_CODES.O && !declaresAppOpPermission(
originatingUid, Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
Log.e(LOG_TAG, "Requesting uid " + originatingUid + " needs to declare permission "
+ Manifest.permission.REQUEST_INSTALL_PACKAGES);
mAbortInstall = true;
}
}
if (mAbortInstall) {
setResult(RESULT_CANCELED);
finish();
return;
}
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);
// 如果设置了ACTION_CONFIRM_INSTALL,则调用PackageInstallerActivity
if (isSessionInstall) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
Uri packageUri = intent.getData();
// 判断Uri的Scheme协议是否是content
if (packageUri != null && packageUri.getScheme().equals(
ContentResolver.SCHEME_CONTENT)) {
// 这个路径已经被起用了,但是仍然可以工作
// [IMPORTANT] This path is deprecated, but should still work. Only necessary
// features should be added.
// Copy file to prevent it from being changed underneath this process
// 调用InstallStaging来拷贝file/content,防止被修改
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(
PackageInstallerActivity.SCHEME_PACKAGE)) {
// 如果Uri中包含package,则调用PackageInstallerActivity
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
// Uri不合法
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();
}
从以上源码我们可以看出:
1.如果我们使用另一种安装方式,设置Intent的action为PackageInstaller.ACTION_CONFIRM_INSTALL
(也就是android.content.pm.action.CONFIRM_INSTALL
),则会直接启动PackageInstallerActivity
2.如果Scheme是package
协议也是直接启动PackageInstallerActivity
3.只有当Scheme是content
时才会跳转到InstallStaging(即使跳转到InstallStaging,最后还是会跳转到PackageInstallerActivity,所以第3步我们会分析跳转到InstallStaging的代码)
-
第3步 启动InstallStaging,进入其onResume()生命周期
看其onResume()方法(核心实现)
@Override
protected void onResume() {
super.onResume();
// This is the first onResume in a single life of the activity
if (mStagingTask == null) {
// File does not exist, or became invalid
if (mStagedFile == null) {
// Create file delayed to be able to show error
try {
mStagedFile = TemporaryFileManager.getStagedFile(this);
} catch (IOException e) {
showError();
return;
}
}
mStagingTask = new StagingAsyncTask();
mStagingTask.execute(getIntent().getData());
}
}
在这里通过TemporaryFileManager.getStagedFile(this)
方法构建了一个临时文件:
/**
* Create a new file to hold a staged file.
*
* @param context The context of the caller
*
* @return A new file
*/
@NonNull
public static File getStagedFile(@NonNull Context context) throws IOException {
return File.createTempFile("package", ".apk", context.getNoBackupFilesDir());
}
这个文件建立在当前应用私有目录下的no_backup文件夹上。也就是/data/no_backup/packagexxx.apk 这个临时文件。
这有一个StagingAsyncTask类:
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)) {
// Despite the comments in ContentResolver#openInputStream the returned stream can
// be null.
if (in == null) {
return false;
}
try (OutputStream out = new FileOutputStream(mStagedFile)) {
byte[] buffer = new byte[1024 * 1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
// Be nice and respond to a cancellation
if (isCancelled()) {
return false;
}
out.write(buffer, 0, bytesRead);
}
}
} catch (IOException | SecurityException | IllegalStateException e) {
Log.w(LOG_TAG, "Error staging apk from content URI", e);
return false;
}
return true;
}
@Override
protected void onPostExecute(Boolean success) {
if (success) {
// Now start the installation again from a file
Intent installIntent = new Intent(getIntent());
installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
installIntent.setData(Uri.fromFile(mStagedFile));
if (installIntent.getBooleanExtra(Intent.EXTRA_RETURN_RESULT, false)) {
installIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
}
installIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(installIntent);
InstallStaging.this.finish();
} else {
showError();
}
}
}
StagingAsyncTask主要工作:
1.doInBackground 是指在异步线程池中处理的事务。doInBackground中实际上就是把uri中需要安装的apk拷贝到临时文件中(上文的/data/no_backup/packagexxx.apk)。
2.onPostExecute 当拷贝任务处理完之后,就会把当前的临时文件Uri作为Intent的参数(这个时候会把Uri的类型设置为file
),跳转到DeleteStagedFileOnResult中。
-
第4步 启动DeleteStagedFileOnResult
看其源码:
/**
* Trampoline activity. Calls PackageInstallerActivity and deletes staged install file onResult.
*/
public class DeleteStagedFileOnResult extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
Intent installIntent = new Intent(getIntent());
installIntent.setClass(this, PackageInstallerActivity.class);
installIntent.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivityForResult(installIntent, 0);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
File sourceFile = new File(getIntent().getData().getPath());
sourceFile.delete();
setResult(resultCode, data);
finish();
}
}
这个Activity就非常简单了,只是作为一个过渡,启动真正大头戏份PackageInstallerActivity。如果PackageInstallerActivity安装失败了,就会退出PackageInstallerActivity界面返回到DeleteStagedFileOnResult的onActivityResult
中删除这个临时文件。
-
第5步 启动PackageInstallerActivity
这个类是真正的安装界面,我们先来看其onCreate()方法:
@Override
protected void onCreate(Bundle icicle) {
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
super.onCreate(null);
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);
final Intent intent = getIntent();
mCallingPackage = intent.getStringExtra(EXTRA_CALLING_PACKAGE);
mSourceInfo = intent.getParcelableExtra(EXTRA_ORIGINAL_SOURCE_INFO);
mOriginatingUid = intent.getIntExtra(Intent.EXTRA_ORIGINATING_UID,
PackageInstaller.SessionParams.UID_UNKNOWN);
mOriginatingPackage = (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN)
? getPackageNameForUid(mOriginatingUid) : null;
final Uri packageUri;
if (PackageInstaller.ACTION_CONFIRM_INSTALL.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;
packageUri = Uri.fromFile(new File(info.resolvedBaseCodePath));
mOriginatingURI = null;
mReferrerURI = null;
} else {
mSessionId = -1;
packageUri = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}
// if there's nothing to do, quietly slip into the ether
if (packageUri == null) {
Log.w(TAG, "Unspecified source");
setPmResult(PackageManager.INSTALL_FAILED_INVALID_URI);
finish();
return;
}
if (DeviceUtils.isWear(this)) {
showDialogInner(DLG_NOT_SUPPORTED_ON_WEAR);
return;
}
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}
// load dummy layout with OK button disabled until we override this layout in
// startInstallConfirm
bindUi();
checkIfAllowedAndInitiateInstall();
}
从上面的代码我们可以看出:
1.如果使用PackageInstaller.ACTION_CONFIRM_INSTALL(也就是第1步里InstallStart接收到的action一直传下来的)模式进行安装,那么就会获取保存在Intent中的ApplicationInfo对象,获取其中的resolvedBaseCodePath也就是代码文件路径。这种处理文件的方式会通过Uri.fromFile(new File(info.resolvedBaseCodePath))
将Uri的Scheme设置为file
类型(这个file类型会在下个Activity里用到)。
2.如果是使用android.intent.action.INSTALL_PACKAGE
,也就是带有Scheme的情况,则通过Intent的getData获取到保存在其中的临时文件(就是上一步中说的/data/no_backup/packagexxx.apk)。通过这种方式获取到的PackageUri的Scheme是package
类型。
3.上面2步主要是为了获取代码文件,形成PackageUri,然后通过方法processPackageUri()
扫描路径对应的文件包,实例化PackageInfo对象mPkgInfo(安装的必要属性)。
4.bindUi()
初始化安装UI
5.checkIfAllowedAndInitiateInstall()
启动安装
下面是processPackageUri()
方法源码:
/**
* Parse the Uri and set up the installer for this package.
*
* @param packageUri The URI to parse
*
* @return {@code true} iff the installer could be set up
*/
private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;
final String scheme = packageUri.getScheme();
switch (scheme) {
case SCHEME_PACKAGE: {
try {
mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS
| PackageManager.MATCH_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
}
if (mPkgInfo == null) {
Log.w(TAG, "Requested package " + packageUri.getScheme()
+ " not available. Discontinuing installation");
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
mPm.getApplicationIcon(mPkgInfo.applicationInfo));
} break;
case ContentResolver.SCHEME_FILE: {
File sourceFile = new File(packageUri.getPath());
PackageParser.Package parsed = PackageUtil.getPackageInfo(this, sourceFile);
// Check for parse errors
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;
}
mPkgInfo = PackageParser.generatePackageInfo(parsed, null,
PackageManager.GET_PERMISSIONS, 0, 0, null,
new PackageUserState());
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;
default: {
throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
}
}
return true;
}
从前面几个Activity的处理逻辑中我们可以发现,直接通过ACTION_CONFIRM_INSTALL跳转到PackageInstallerActivity的Uri的Scheme会被转换成file
类型;原来Scheme类型为content
的在InstallStaging的StagingAsyncTask处理完毕后其Uri的Scheme也会转成file
;只有原来Scheme类型为package
的是从InstallStart直接跳转到PackageInstallerActivity的,其Scheme才是package
,所以最终的Scheme只剩下package
和file
,而processPackageUri()
方法也只会解析这两种uri:
1.package
协议开头的uri:这种uri会通过PackageManager的getPackageInfo()
通过getSchemeSpecificPart获取对应apk文件对应的PackageInfo信息
2.file
协议开头的uri,则使用PackageParser的generatePackageInfo()
方法获取apk文件中的package信息。最终实现mPkgInfo的属性初始化。
3.这个方法里会引入PackageManager和PackageParser这2个类,后续会讲诉这俩类和PMS之间的关系,暂不多表。
我们看一下PackageParser的generatePackageInfo()
方法:
@UnsupportedAppUsage
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
Set<String> grantedPermissions, PackageUserState state, int userId) {
if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) {
return null;
}
PackageInfo pi = new PackageInfo();
pi.packageName = p.packageName;
pi.splitNames = p.splitNames;
pi.versionCode = p.mVersionCode;
pi.versionCodeMajor = p.mVersionCodeMajor;
pi.baseRevisionCode = p.baseRevisionCode;
pi.splitRevisionCodes = p.splitRevisionCodes;
pi.versionName = p.mVersionName;
pi.sharedUserId = p.mSharedUserId;
pi.sharedUserLabel = p.mSharedUserLabel;
pi.applicationInfo = generateApplicationInfo(p, flags, state, userId);
pi.installLocation = p.installLocation;
pi.isStub = p.isStub;
pi.coreApp = p.coreApp;
if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0
|| (pi.applicationInfo.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
pi.requiredForAllUsers = p.mRequiredForAllUsers;
}
pi.restrictedAccountType = p.mRestrictedAccountType;
pi.requiredAccountType = p.mRequiredAccountType;
pi.overlayTarget = p.mOverlayTarget;
pi.targetOverlayableName = p.mOverlayTargetName;
pi.overlayCategory = p.mOverlayCategory;
pi.overlayPriority = p.mOverlayPriority;
pi.mOverlayIsStatic = p.mOverlayIsStatic;
pi.compileSdkVersion = p.mCompileSdkVersion;
pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename;
pi.firstInstallTime = firstInstallTime;
pi.lastUpdateTime = lastUpdateTime;
if ((flags&PackageManager.GET_GIDS) != 0) {
pi.gids = gids;
}
if ((flags&PackageManager.GET_CONFIGURATIONS) != 0) {
int N = p.configPreferences != null ? p.configPreferences.size() : 0;
if (N > 0) {
pi.configPreferences = new ConfigurationInfo[N];
p.configPreferences.toArray(pi.configPreferences);
}
N = p.reqFeatures != null ? p.reqFeatures.size() : 0;
if (N > 0) {
pi.reqFeatures = new FeatureInfo[N];
p.reqFeatures.toArray(pi.reqFeatures);
}
N = p.featureGroups != null ? p.featureGroups.size() : 0;
if (N > 0) {
pi.featureGroups = new FeatureGroupInfo[N];
p.featureGroups.toArray(pi.featureGroups);
}
}
if ((flags & PackageManager.GET_ACTIVITIES) != 0) {
final int N = p.activities.size();
if (N > 0) {
int num = 0;
final ActivityInfo[] res = new ActivityInfo[N];
for (int i = 0; i < N; i++) {
final Activity a = p.activities.get(i);
if (state.isMatch(a.info, flags)) {
if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(a.className)) {
continue;
}
res[num++] = generateActivityInfo(a, flags, state, userId);
}
}
pi.activities = ArrayUtils.trimToSize(res, num);
}
}
if ((flags & PackageManager.GET_RECEIVERS) != 0) {
final int N = p.receivers.size();
if (N > 0) {
int num = 0;
final ActivityInfo[] res = new ActivityInfo[N];
for (int i = 0; i < N; i++) {
final Activity a = p.receivers.get(i);
if (state.isMatch(a.info, flags)) {
res[num++] = generateActivityInfo(a, flags, state, userId);
}
}
pi.receivers = ArrayUtils.trimToSize(res, num);
}
}
if ((flags & PackageManager.GET_SERVICES) != 0) {
final int N = p.services.size();
if (N > 0) {
int num = 0;
final ServiceInfo[] res = new ServiceInfo[N];
for (int i = 0; i < N; i++) {
final Service s = p.services.get(i);
if (state.isMatch(s.info, flags)) {
res[num++] = generateServiceInfo(s, flags, state, userId);
}
}
pi.services = ArrayUtils.trimToSize(res, num);
}
}
if ((flags & PackageManager.GET_PROVIDERS) != 0) {
final int N = p.providers.size();
if (N > 0) {
int num = 0;
final ProviderInfo[] res = new ProviderInfo[N];
for (int i = 0; i < N; i++) {
final Provider pr = p.providers.get(i);
if (state.isMatch(pr.info, flags)) {
res[num++] = generateProviderInfo(pr, flags, state, userId);
}
}
pi.providers = ArrayUtils.trimToSize(res, num);
}
}
if ((flags&PackageManager.GET_INSTRUMENTATION) != 0) {
int N = p.instrumentation.size();
if (N > 0) {
pi.instrumentation = new InstrumentationInfo[N];
for (int i=0; i<N; i++) {
pi.instrumentation[i] = generateInstrumentationInfo(
p.instrumentation.get(i), flags);
}
}
}
if ((flags&PackageManager.GET_PERMISSIONS) != 0) {
int N = p.permissions.size();
if (N > 0) {
pi.permissions = new PermissionInfo[N];
for (int i=0; i<N; i++) {
pi.permissions[i] = generatePermissionInfo(p.permissions.get(i), flags);
}
}
N = p.requestedPermissions.size();
if (N > 0) {
pi.requestedPermissions = new String[N];
pi.requestedPermissionsFlags = new int[N];
for (int i=0; i<N; i++) {
final String perm = p.requestedPermissions.get(i);
pi.requestedPermissions[i] = perm;
// The notion of required permissions is deprecated but for compatibility.
pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_REQUIRED;
if (grantedPermissions != null && grantedPermissions.contains(perm)) {
pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_GRANTED;
}
}
}
}
// deprecated method of getting signing certificates
if ((flags&PackageManager.GET_SIGNATURES) != 0) {
if (p.mSigningDetails.hasPastSigningCertificates()) {
// Package has included signing certificate rotation information. Return the oldest
// cert so that programmatic checks keep working even if unaware of key rotation.
pi.signatures = new Signature[1];
pi.signatures[0] = p.mSigningDetails.pastSigningCertificates[0];
} else if (p.mSigningDetails.hasSignatures()) {
// otherwise keep old behavior
int numberOfSigs = p.mSigningDetails.signatures.length;
pi.signatures = new Signature[numberOfSigs];
System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs);
}
}
// replacement for GET_SIGNATURES
if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
if (p.mSigningDetails != SigningDetails.UNKNOWN) {
// only return a valid SigningInfo if there is signing information to report
pi.signingInfo = new SigningInfo(p.mSigningDetails);
} else {
pi.signingInfo = null;
}
}
return pi;
}
1.这个方法看上去又臭又长,不做详细分析,其核心只是解析了四大组件的内容以及权限相关的标签,拿到了apk包所有运行需要的基础信息。
2.这个方法我们要留意2个类:PackageInfo和PackageParser.Package,后续我们会做分析。
bindUi()
方法源码:
private void bindUi() {
mAlert.setIcon(mAppSnippet.icon);
mAlert.setTitle(mAppSnippet.label);
mAlert.setView(R.layout.install_content_view);
mAlert.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.install),
(ignored, ignored2) -> {
if (mOk.isEnabled()) {
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, true);
finish();
} else {
startInstall();
}
}
}, null);
mAlert.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel),
(ignored, ignored2) -> {
// Cancel and finish
setResult(RESULT_CANCELED);
if (mSessionId != -1) {
mInstaller.setPermissionsResult(mSessionId, false);
}
finish();
}, null);
setupAlert();
mOk = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
mOk.setEnabled(false);
}
该方法是对安装界面UI的初始化,默认会把“安装”按钮(mOK
)隐藏掉,点击BUTTON_POSITIVE会执行startInstall()
,待我们分析完onCreate()里所有方法后再回过头来看一下这个方法。
我们再来看onCreate()里的最后一个方法:checkIfAllowedAndInitiateInstall()
/**
* Check if it is allowed to install the package and initiate install if allowed. If not allowed
* show the appropriate dialog.
*/
private void checkIfAllowedAndInitiateInstall() {
// Check for install apps user restriction first.
final int installAppsRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_APPS, Process.myUserHandle());
//允许安装未知来源的文件判断
if ((installAppsRestrictionSource & UserManager.RESTRICTION_SOURCE_SYSTEM) != 0) {
showDialogInner(DLG_INSTALL_APPS_RESTRICTED_FOR_USER);
return;
} else if (installAppsRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startActivity(new Intent(Settings.ACTION_SHOW_ADMIN_SUPPORT_DETAILS));
finish();
return;
}
if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
//核心代码
initiateInstall();
} else {
// Check for unknown sources restrictions.
final int unknownSourcesRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, Process.myUserHandle());
final int unknownSourcesGlobalRestrictionSource = mUserManager.getUserRestrictionSource(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY, Process.myUserHandle());
final int systemRestriction = UserManager.RESTRICTION_SOURCE_SYSTEM
& (unknownSourcesRestrictionSource | unknownSourcesGlobalRestrictionSource);
if (systemRestriction != 0) {
showDialogInner(DLG_UNKNOWN_SOURCES_RESTRICTED_FOR_USER);
} else if (unknownSourcesRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startAdminSupportDetailsActivity(UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES);
} else if (unknownSourcesGlobalRestrictionSource != UserManager.RESTRICTION_NOT_SET) {
startAdminSupportDetailsActivity(
UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY);
} else {
handleUnknownSources();
}
}
}
继续看其核心方法:initiateInstall()
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".
mAppInfo = mPm.getApplicationInfo(pkgName,
PackageManager.MATCH_UNINSTALLED_PACKAGES);
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
mAppInfo = null;
}
} catch (NameNotFoundException e) {
mAppInfo = null;
}
startInstallConfirm();
}
通过PackageManager的getApplicationInfo方法,获取当前Android系统中是否已经安装了当前的app,如果能找到mAppInfo对象,说明是安装了,调用startInstallConfirm刷新按钮的显示的是更新,否则就是没有安装按钮展示的是安装。需要注意的是这里又用到了PackageManager这个类,大家请多留意。
继续看方法startInstallConfirm()
:
private void startInstallConfirm() {
View viewToEnable;
if (mAppInfo != null) {
viewToEnable = (mAppInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0
? requireViewById(R.id.install_confirm_question_update_system)
: requireViewById(R.id.install_confirm_question_update);
} else {
// This is a new application with no permissions.
viewToEnable = requireViewById(R.id.install_confirm_question);
}
viewToEnable.setVisibility(View.VISIBLE);
mEnableOk = true;
mOk.setEnabled(true);
}
该方法很简单,其实就是把上文中bindUi()
方法里隐藏的mOK
按钮展示出来,用来点击进行更新/安装,所以我们可以回头看一下bindUi()
的"mOK"按钮点击执行的方法startInstall()
private void startInstall() {
// Start subactivity to actually install the application
Intent newIntent = new Intent();
newIntent.putExtra(PackageUtil.INTENT_ATTR_APPLICATION_INFO,
mPkgInfo.applicationInfo);
newIntent.setData(mPackageURI);
newIntent.setClass(this, InstallInstalling.class);
String installerPackageName = getIntent().getStringExtra(
Intent.EXTRA_INSTALLER_PACKAGE_NAME);
if (mOriginatingURI != null) {
newIntent.putExtra(Intent.EXTRA_ORIGINATING_URI, mOriginatingURI);
}
if (mReferrerURI != null) {
newIntent.putExtra(Intent.EXTRA_REFERRER, mReferrerURI);
}
if (mOriginatingUid != PackageInstaller.SessionParams.UID_UNKNOWN) {
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();
}
这个方法的核心就是把mPackageURI
这个字段放入Intent,然后启动InstallInstalling这个Activity。
后续流程请看Android APK安装流程(2)