PKMS 钻研(4) - PackageInstaller

Updated on 2020.04.01

核心源码(Android 10)

关键类 路径
PackageInstaller.java /frameworks/base/core/java/android/content/pm/PackageInstaller.java
PackageManager.java /frameworks/base/core/java/android/content/pm/PackageManager.java
ApplicationPackageManager.java /frameworks/base/core/java/android/app/ApplicationPackageManager.java
PackageUtil.java /packages/apps/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java
InstallStart.java /packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStart.java
InstallStaging.java /packages/apps/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java
PackageInstallerActivity.java /frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java

# PackageManager

在讲解包管理机制如何安装 APK 之前,我们回顾一下前面一篇文章讲解的 PackageManager

PackageManager 是一个抽象类,它的具体实现类为 ApplicationPackageManager

ApplicationPackageManager 中的方法会通过 IPackageManagerPackageManagerService 进行进程间通信,因此 PackageManager 所提供的功能最终是由 PackageManagerService 来实现的,这么设计的主要用意是为了避免系统服务 PKMS 直接被访问。

1
2
3
4
5
// PackageManager 是一个抽象类
public abstract class PackageManager {}

// ApplicationPackageManager 继承自 PackageManager
public class ApplicationPackageManager extends PackageManager {}

# 关于 APK

文件结构

APKAndroidPackage 的缩写,即 Android 安装包,它实际上是 zip 格式的压缩文件,一般情况下,解压后的文件结构如下表所示。

目录/文件 描述
assert 存放的原生资源文件,通过 AssetManager 类访问。
lib 存放库文件。
META-INF 保存应用的签名信息,签名信息可以验证 APK 文件的完整性。
res 存放资源文件。res 中除了 raw 子目录,其他的子目录都参与编译,这些子目录下的资源是通过编译出的 R 类在代码中访问。
AndroidManifest.xml 用来声明应用程序的包名称、版本、组件和权限等数据。 apk 中的 AndroidManifest.xml 经过压缩,可以通过 AXMLPrinter2 工具解开。
classes.dex Java 源码编译后生成的 Java 字节码文件。
resources.arsc 编译后的二进制资源文件。

安装场景

目前,我们常见的安装 APK 的场景主要分为以下 四种

        ✒ 1、通过 adb 命令安装:adb 命令包括 adb push / install
        ✒ 2、用户下载的 Apk,通过系统安装器 packageinstaller(系统内置的应用程序,用于安装和卸载应用程序)安装该 Apk。
        ✒ 3、系统开机时安装系统应用,没有安装界面,在 PKMS 的构造函数中完成安装。
        ✒ 4、电脑或者手机上的应用商店自动安装,调用 PackageManager.installPackages() ,有安装界面。

其实,这 4 种方式最终都是由 PackageManagerService 来进行处理,只是在此之前的调用链(逻辑)是不同的。

我们在接下来的分析中,会优先选择第二种方式,因为对于开发者来说,这是调用链比较长的安装方式,搞懂后其它几种方式理解起来就会简单很多。

安装目录

目录/文件 描述
/system/app 系统自带的应用程序,获得 adb root 权限才能删除。
/data/app 用户程序安装的目录,安装时把 apk 文件复制到此目录。
/data/data 存放应用程序的数据。
/data/dalvik-cache 将 apk 中的 dex 文件安装到 dalvik-cache 目录下,用于虚拟机安装的可执行文件。
/data/system 该目录下的 packages.xml 文件类似于 Window 的注册表,这个文件是解析 apk 时由 writeLP() 创建的,里面记录了系统的 permissons,以及每个 apk 的 name,codePath,flag,ts,version ,userid 等信息,这些信息主要通过 apk 的 AndroidManifest 解析获取,解析完 apk 后将更新信息写入这个文件并保存到 flash,下次开机的时候直接从里面读取相关信息并添加到内存相关列表中。当有 apk 升级,安装或删除时会更新这个文件。
/data/system/packages.xml 包含了该应用申请的权限、签名和代码所在的位置等信息系,并且两者都有同一个 userld。之所以每个应用都要一个 userId,是因为 Android 在系统设计上把每个应用当做 Linux 系统上的一个用户对待,这样就可以利用已有的 Linux 用户管理机制来设计 Android 应用,比如应用目录,应用权限,应用进程管理等。
/data/system/packages.list 指定了应用的默认存储位置 /data/user/0/com.xxx.xxx。

一、PackageInstaller

1.1 初始化

首先,我们需要指出的是:从 Android 8.0 开始系统是通过如下代码安装指定路径中的 APK:

1
2
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" + apkfile.toString()), "application/vnd.android.package-archive");

Intent 的 Action 属性为 ACTION_VIEW,Type 属性指定 Intent 的数据类型为 application/vnd.android.package-archive

“application/vnd.android.package-archive” 是什么?

1
2
3
4
5
6
7
final String[][] MIME_MapTable={ 
//{后缀名,MIME类型}
{".3gp", "video/3gpp"},
{".apk", "application/vnd.android.package-archive"},
{".asf", "video/x-ms-asf"},
{".avi", "video/x-msvideo"},
... ...

我们发现 "application/vnd.android.package-archive" 其实就是文件类型,具体对应 apk 类型。

那么这个 Intent 隐式匹配的 Activity 是什么?直接告诉你:InstallStart !为什么?源码为证!

(源码中,7.0 隐式匹配的 Activity 为 PackageInstallerActivity ,两者的区别我们后面会讲解到!)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// frameworks/base/packages/PackageInstaller/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.packageinstaller">
... ...

<application android:name=".PackageInstallerApplication"
android:label="@string/app_name"
android:icon="@drawable/ic_app_icon"
android:allowBackup="false"
android:theme="@style/Theme.AlertDialogActivity"
android:supportsRtl="true"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true">
... ...

<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>
... ...

</application>

</manifest>

1.2 InstallStart.onCreate()

InstallStartPackageInstaller入口。当我们调用 PackageInstaller 来安装应用时会跳转到 InstallStart,并调用它的 onCreate 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java

public class InstallStart extends Activity {

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());
... ...

Intent nextActivity = new Intent(intent);
nextActivity.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);

// 判断 Intent 的 Action 是否为 ACTION_CONFIRM_INSTALL
if (isSessionInstall) {
nextActivity.setClass(this, PackageInstallerActivity.class);
} else {
// 我们探讨的情景会走这边
Uri packageUri = intent.getData();

// 保证 packageUri 不为 null,判断 packageUri 的 Scheme 协议是否是 content 或者 File
if (packageUri != null && packageUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
// 跳转到 InstallStaging (Android 8.0 - 10.0)
nextActivity.setClass(this, InstallStaging.class);
} else if (packageUri != null && packageUri.getScheme().equals(PackageInstallerActivity.SCHEME_PACKAGE)) {
// 跳转到 PackageInstallerActivity(Android 7.0)
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) {
// 启动相应的 Activity(InstallStaging 或者 PackageInstallerActivity)
startActivity(nextActivity);
}
finish();
}

}

1.3 InstallStaging.onResume()

我们是基于 Android 10.0 的代码进行的分析,所以会走到 InstallStaging 分支。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java

public class InstallStaging extends Activity {

/** The file the package is in */
private @Nullable File mStagedFile;

@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) {
try {
// 如果 File 类型的 mStagedFile 为 null,则创建 mStagedFile,用于存储临时数据
mStagedFile = TemporaryFileManager.getStagedFile(this);
} catch (IOException e) {
showError();
return;
}
}

// 启动 StagingAsyncTask 线程,并传入了 content 协议的 Uri
mStagingTask = new StagingAsyncTask();
mStagingTask.execute(getIntent().getData());
}
}

}

InstallStaging 主要做了两件事情

        ✒ 1、判断 mStagingTask 是否为空,主要用于存储临时数据;

        ✒ 2、创建并启动 StagingAsyncTask 线程。

1.4 StagingAsyncTask.execute()

接下来,我们看看这个线程所做的工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/InstallStaging.java

public class InstallStaging extends Activity {
... ...

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;
}
// 将 packageUri(content 协议的 Uri)的内容写入到 mStagedFile 中
try (OutputStream out = new FileOutputStream(mStagedFile)) {
byte[] buffer = new byte[1024 * 1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) >= 0) {
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) {
Intent installIntent = new Intent(getIntent());
// 如果写入成功,跳转到 DeleteStagedFileOnResult
installIntent.setClass(InstallStaging.this, DeleteStagedFileOnResult.class);
// 并将 mStagedFile 传进去
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();
}
}
}
}

1.5 DeleteStagedFileOnResult.onCreate()

doInBackground 方法中将 packageUri(content 协议的 Uri)的内容写入到 mStagedFile 中,如果写入成功,onPostExecute 方法中会跳转到 DeleteStagedFileOnResult 中,并将 mStagedFile 传进去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java

/**
* 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); // 跳转到 PackageInstallerActivity

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();
}
}

1.6 小结

绕了一圈又回到了 PackageInstallerActivity,这里可以看出 InstallStaging 主要起了转换的作用,将 content 协议的 Uri 转换为 File 协议,然后跳转到 PackageInstallerActivity

二、PackageInstallerActivity

接下来,我们就要重点分析 PackageInstallerActivity !从功能上来说,PackageInstallerActivity 才是应用安装器 PackageInstaller 真正的入口 Activity

我们看下官方对于这个类的说明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* 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.
*/

/**
* 当通过渠道安装一个应用程序的时候,会启动这个 Activity。
* 如果在首次解析这个安装包的时候出现解析错误,会通过对话框的形式告诉用户。
* 如果首次解析安装包的时候,成功解析了,则会通知用户去打开"安装未知应用程序设置"。
* 在启动 Activity 的时候会进行内存检查,如果内存不足会通知用户。
* 如果这个应用程序已经在这个设备安装过了,则会向用户弹出一个对话框询问用户是否"替换现有应用程序的安装包"。
* 基于用户的回应,然后通过 InstallAppConfirm 的子类来安装应用程序。
* 所有状态的转换都是在这个 Activity 中处理。
*/

public class PackageInstallerActivity extends AlertActivity {
... ...
}

了解完了官方说明,接下来我们查看它的 onCreate() 方法!

2.1 PackageInstallerActivity.onCreate()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java

public class PackageInstallerActivity extends AlertActivity {
... ...

PackageManager mPm;
IPackageManager mIpm;
AppOpsManager mAppOpsManager;
UserManager mUserManager;
PackageInstaller mInstaller;
PackageInfo mPkgInfo;
String mCallingPackage;
ApplicationInfo mSourceInfo;

@Override
protected void onCreate(Bundle icicle) {
getWindow().addSystemFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);

super.onCreate(icicle);

if (icicle != null) {
mAllowUnknownSources = icicle.getBoolean(ALLOW_UNKNOWN_SOURCES_KEY);
}

// 初始话 PackageManager 对象:具体用来执行安装操作,最终的功能由 PMS 来实现;
mPm = getPackageManager();
// 初始话 IPackageManager 对象:一个 AIDL 的接口,用于和 PMS 进行进程间通信;
mIpm = AppGlobals.getPackageManager();
// 初始化 AppOpsManager 对象:用于权限动态检测,在,Android 4.3 中被引入;
mAppOpsManager = (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE);
// 初始化 PackageInstaller 对象:在该对象中包含了安装 APK 的基本信息;
mInstaller = mPm.getPackageInstaller();
// 初始化 UserManager 对象:用于多用户管理;
mUserManager = (UserManager) getSystemService(Context.USER_SERVICE);

final Intent intent = getIntent();
... ...

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 {
// 如果是用户自己拉起来的安装,则默认 sessionId 为 -1,并且获取 packageUri
mSessionId = -1;
packageUri = intent.getData();
mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI);
mReferrerURI = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
}

// 返回 URI 解析错误
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;
}

// 重点方法 1:根据 Uri 的 Scheme 进行预处理
boolean wasSetUp = processPackageUri(packageUri);
if (!wasSetUp) {
return;
}

bindUi(R.layout.install_confirm, false);
// 重点方法 2:判断是否是未知来源的应用,如果开启允许安装未知来源选项则直接初始化安装
checkIfAllowedAndInitiateInstall();
}
... ...

}

2.2 PackageInstallerActivity.processPackageUri()

我们首先来看看 processPackageUri 所做的工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java

public class PackageInstallerActivity extends AlertActivity {
... ...

private boolean processPackageUri(final Uri packageUri) {
mPackageURI = packageUri;

final String scheme = packageUri.getScheme(); // 得到 packageUri 的 Scheme 协议

// 根据这个 Scheme 协议分别对 package 协议和 file 协议进行处理
switch (scheme) {
case SCHEME_PACKAGE: { // 处理 scheme 为 package 的情况
try {
// 获取 package 对应的 Android 应用信息 PackageInfo,如:应用名称,权限列表等
mPkgInfo = mPm.getPackageInfo(packageUri.getSchemeSpecificPart(),
PackageManager.GET_PERMISSIONS
| PackageManager.MATCH_UNINSTALLED_PACKAGES);
} catch (NameNotFoundException e) {
}

// 如果无法获取 PackageInfo ,弹出一个错误的对话框,然后直接退出安装
if (mPkgInfo == null) {
showDialogInner(DLG_PACKAGE_ERROR);
setPmResult(PackageManager.INSTALL_FAILED_INVALID_APK);
return false;
}
// 创建 AppSnipet 对象,该对象封装了待安装 Android 应用的标题和图标
mAppSnippet = new PackageUtil.AppSnippet(mPm.getApplicationLabel(mPkgInfo.applicationInfo),
mPm.getApplicationIcon(mPkgInfo.applicationInfo));
} break;

case ContentResolver.SCHEME_FILE: { // 处理 scheme 为 file 的情况
// 根据 packageUri 创建一个新的 File
File sourceFile = new File(packageUri.getPath());
// 创建 APK 文件的分析器 parsed
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());
mAppSnippet = PackageUtil.getAppSnippet(this, mPkgInfo.applicationInfo, sourceFile);
} break;

// 如果不是这两个协议就会抛出异常
default: {
throw new IllegalArgumentException("Unexpected URI scheme " + packageUri);
}
}

return true;
}
... ...

}

上面源码中创建 APK 文件的分析器 parsed 时,涉及一个重要的方法 getPackageInfo(),我们看下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// frameworks/base/packages/PackageInstaller/src/com/android/packageinstaller/PackageUtil.java

public class PackageUtil {

/**
* Utility method to get package information for a given {@link File}
*/
public static PackageParser.Package getPackageInfo(Context context, File sourceFile) {
final PackageParser parser = new PackageParser(); // 创建一个 PackageParser 对象
parser.setCallback(new PackageParser.CallbackImpl(context.getPackageManager()));
try {
return parser.parsePackage(sourceFile, 0);
} catch (PackageParserException e) {
return null;
}
}

}

2.3 PackageInstallerActivity.checkIfAllowedAndInitiateInstall()

接下来我们看看 checkIfAllowedAndInitiateInstall 做所工作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public class PackageInstallerActivity extends AlertActivity {

/**
* 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;
}

// isInstallRequestFromUnknownSource --> 安装请求是否来自一个未知的源
// 判断如果允许安装未知来源或者根据 Intent 判断得出该 APK 不是未知来源
if (mAllowUnknownSources || !isInstallRequestFromUnknownSource(getIntent())) {
initiateInstall(); // 初始化安装,重点方法 1,见 2.5 节
} else {
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) { // 如果管理员限制来自未知源的安装, 就弹出提示 Dialog
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(); // 重点方法 2,见 2.4 节
}
}
}

}

2.4 PackageInstallerActivity.handleUnknownSources()

我们先来看看 handleUnknownSources() 的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class PackageInstallerActivity extends AlertActivity {

private void handleUnknownSources() {
if (mOriginatingPackage == null) {
Log.i(TAG, "No source found for package " + mPkgInfo.packageName);
showDialogInner(DLG_ANONYMOUS_SOURCE);
return;
}

final int appOpCode = AppOpsManager.permissionToOpCode(Manifest.permission.REQUEST_INSTALL_PACKAGES);
final int appOpMode = mAppOpsManager.noteOpNoThrow(appOpCode, mOriginatingUid, mOriginatingPackage);
switch (appOpMode) {
case AppOpsManager.MODE_DEFAULT:
mAppOpsManager.setMode(appOpCode, mOriginatingUid, mOriginatingPackage, AppOpsManager.MODE_ERRORED);
// fall through
// 我们看下这边,当系统默认不允许安装位置来源的应用时,会弹出一个 Dialog 等待用户确认
case AppOpsManager.MODE_ERRORED:
showDialogInner(DLG_EXTERNAL_SOURCE_BLOCKED);
break;
case AppOpsManager.MODE_ALLOWED:
initiateInstall();
break;
default:
Log.e(TAG, "Invalid app op mode " + appOpMode
+ " for OP_REQUEST_INSTALL_PACKAGES found for uid " + mOriginatingUid);
finish();
break;
}
}

}

2.4.1 PackageInstallerActivity.showDialogInner()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class PackageInstallerActivity extends AlertActivity {

private void showDialogInner(int id) {
DialogFragment currentDialog = (DialogFragment) getFragmentManager().findFragmentByTag("dialog");
if (currentDialog != null) {
currentDialog.dismissAllowingStateLoss();
}

DialogFragment newDialog = createDialog(id); // 创建一个 Dialog
if (newDialog != null) {
newDialog.showAllowingStateLoss(getFragmentManager(), "dialog");
}
}

}

2.4.2 PackageInstallerActivity.createDialog()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class PackageInstallerActivity extends AlertActivity {

private DialogFragment createDialog(int id) {
switch (id) {
... ...

case DLG_EXTERNAL_SOURCE_BLOCKED:
return ExternalSourcesBlockedDialog.newInstance(mOriginatingPackage);
... ...

}
return null;
}

}

2.4.3 PackageInstallerActivity.ExternalSourcesBlockedDialog()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class PackageInstallerActivity extends AlertActivity {

/**
* An error dialog shown when external sources are not allowed
*/
public static class ExternalSourcesBlockedDialog extends AppErrorDialog {
static AppErrorDialog newInstance(@NonNull String originationPkg) {
ExternalSourcesBlockedDialog dialog = new ExternalSourcesBlockedDialog();
dialog.setArgument(originationPkg);
return dialog;
}

@Override
protected Dialog createDialog(@NonNull CharSequence argument) {
try {
PackageManager pm = getActivity().getPackageManager();

ApplicationInfo sourceInfo = pm.getApplicationInfo(argument.toString(), 0);

return new AlertDialog.Builder(getActivity())
.setTitle(pm.getApplicationLabel(sourceInfo))
.setIcon(pm.getApplicationIcon(sourceInfo))
// untrusted_external_source_warning:出于安全考虑,已禁止您的手机安装来自此来源的未知应用
.setMessage(R.string.untrusted_external_source_warning)
.setPositiveButton(R.string.external_sources_settings,
(dialog, which) -> {
Intent settingsIntent = new Intent();
settingsIntent.setAction(
Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
final Uri packageUri = Uri.parse("package:" + argument);
settingsIntent.setData(packageUri);
try {
// ExternalSourcesDetails.java
getActivity().startActivityForResult(settingsIntent,
REQUEST_TRUST_EXTERNAL_SOURCE);
} catch (ActivityNotFoundException exc) {
Log.e(TAG, "Settings activity not found for action: "
+ Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
}
})
.setNegativeButton(R.string.cancel, (dialog, which) -> getActivity().finish())
.create();
} catch (NameNotFoundException e) {
Log.e(TAG, "Did not find app info for " + argument);
getActivity().finish();
return null;
}
}
}

}

2.5 PackageInstallerActivity.initiateInstall()

解决了 handleUnknownSources() 方法,我们再来看下遗留的 initiateInstall() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class PackageInstallerActivity extends AlertActivity {

// 判断如果允许安装未知来源或者根据 Intent 判断得出该 APK 不是未知来源
private void initiateInstall() {
// 得到包名
String pkgName = mPkgInfo.packageName;

// 是否有同名应用已经安装上去了,再次安装则被认为是替换安装
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;
}

// 检查这个包是否已安装,如果要替换,则显示替换对话框
try {
// 获取设备上的残存数据,并且标记为 “installed” 的,实际上已经被卸载的应用
mAppInfo = mPm.getApplicationInfo(pkgName, PackageManager.MATCH_UNINSTALLED_PACKAGES);
if ((mAppInfo.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
// 如果应用是被卸载的,但是又是被标识成安装过的,则认为是新安装
mAppInfo = null;
}
} catch (NameNotFoundException e) {
mAppInfo = null;
}

// 列出权限列表,等待用户确认安装
startInstallConfirm();
}

}

我们可以看到,initiateInstall() 方法主要做了三件事

        ✒ 1、检查设备是否是同名安装,如果是则后续是替换安装。

        ✒ 2、检查设备上是否已经安装了这个安装包,如果是,后面是替换安装。

        ✒ 3、调用 startInstallConfirm() ,这个方法是安装的核心代码。

2.6 PackageInstallerActivity.startInstallConfirm()

下面我们就来看看 startInstallConfirm() 方法里面的具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class PackageInstallerActivity extends AlertActivity {

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);
}

}

三、总结

        ✒ 1、根据 UriScheme 协议不同,跳转到不同的界面,content 协议跳转到 InstallStart,其他的跳转到 PackageInstallerActivity
        ✒ 2、InstallStartcontent 协议的 Uri 转换File 协议,然后跳转到 PackageInstallerActivity
        ✒ 3、PackageInstallerActivity 会分别对 package 协议file 协议Uri 进行处理,如果是 file 协议会解析 APK 文件得到包信息 PackageInfo
        ✒ 4、PackageInstallerActivity 中会对未知来源进行处理,如果允许安装未知来源或者根据 Intent 判断得出该 APK 不是未知来源,就会初始化安装确认界面,如果管理员限制来自未知源的安装, 就弹出提示 Dialog 或者跳转到设置界面。