# Android 8 内置安装器方案分析
## 📋 需求背景
- **目标**:在 uni-app 中实现内置安装器,不使用外部系统安装器
- **平台**:Android 8.0 (API 26)
- **当前状态**:应用已配置为 Device Owner,具备 Kiosk 模式
## 🔍 技术分析
### 1. Android 8.0 安装权限机制
#### 权限要求
- **`INSTALL_PACKAGES`**:系统级权限,普通应用无法获取
- **`REQUEST_INSTALL_PACKAGES`**:Android 8.0+ 新增,需要用户授权
- **Device Owner 特权**:作为 Device Owner 的应用可以绕过部分限制
#### 当前配置状态
```xml
```
### 2. 内置安装器实现方案对比
#### 方案一:PackageInstaller API(推荐)⭐
**适用版本**:Android 5.0+ (API 21+)
**优点**:
- ✅ 官方 API,稳定可靠
- ✅ 支持静默安装(需要系统权限或 Device Owner)
- ✅ 可以显示自定义安装界面
- ✅ 支持安装进度回调
**缺点**:
- ⚠️ Android 8.0+ 需要 `REQUEST_INSTALL_PACKAGES` 权限
- ⚠️ 需要创建 Session 和安装会话
**实现原理**:
```java
// 1. 创建 PackageInstaller.Session
PackageInstaller installer = context.getPackageManager().getPackageInstaller();
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL
);
int sessionId = installer.createSession(params);
PackageInstaller.Session session = installer.openSession(sessionId);
// 2. 写入 APK 文件
try (OutputStream out = session.openWrite("package", 0, -1)) {
// 将 APK 文件写入流
FileInputStream in = new FileInputStream(apkFile);
byte[] buffer = new byte[65536];
int c;
while ((c = in.read(buffer)) != -1) {
out.write(buffer, 0, c);
}
session.fsync(out);
}
// 3. 提交安装
Intent intent = new Intent(context, InstallReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT
);
session.commit(pendingIntent.getIntentSender());
session.close();
```
#### 方案二:Device Owner 静默安装
**适用条件**:应用必须是 Device Owner
**优点**:
- ✅ 完全静默安装,无需用户交互
- ✅ 不需要退出 Kiosk 模式
- ✅ 安装过程完全可控
**缺点**:
- ⚠️ 仅限 Device Owner 应用
- ⚠️ 需要系统级权限
**实现原理**:
```java
// 使用 DevicePolicyManager.installPackage()
if (dpm.isDeviceOwnerApp(context.getPackageName())) {
dpm.installPackage(
adminComponent,
Uri.fromFile(apkFile),
new DevicePolicyManager.InstallPackageCallback() {
@Override
public void onPackageInstalled(String packageName, int returnCode, String msg) {
// 安装完成回调
}
}
);
}
```
**注意**:此方法在 Android 8.0 中可能已被限制或移除。
#### 方案三:反射调用 PackageManager(不推荐)
**适用场景**:需要 root 权限或系统签名
**缺点**:
- ❌ 不稳定,可能在不同版本失效
- ❌ 需要 root 或系统签名
- ❌ 违反 Android 安全策略
### 3. 推荐方案:PackageInstaller + Device Owner 优化
## 🎯 推荐实现方案
### 方案架构
```
app-info.vue (下载完成)
↓
KioskModule.installApkSilently() (原生插件)
↓
PackageInstaller API
├─ 检查 Device Owner 权限
├─ 创建安装会话
├─ 写入 APK 文件
├─ 提交安装(带进度回调)
└─ 监听安装结果
↓
安装完成/失败
↓
恢复 Kiosk 模式
```
### 核心优势
1. **无需退出 Kiosk**:使用 PackageInstaller 可以在 Kiosk 模式下安装
2. **自定义界面**:可以显示自定义安装进度界面
3. **完全控制**:安装过程完全在应用内完成
4. **Device Owner 优化**:利用 Device Owner 权限简化流程
### 实现要点
#### 1. 创建原生插件 `InstallModule.java`
```java
public class InstallModule extends UniModule {
@UniJSMethod(uiThread = true)
public void installApkSilently(String apkPath, UniJSCallback callback) {
// 1. 检查 Device Owner
// 2. 使用 PackageInstaller 安装
// 3. 返回安装结果
}
@UniJSMethod(uiThread = true)
public void getInstallProgress(UniJSCallback callback) {
// 返回安装进度
}
}
```
#### 2. 安装流程
```java
// 伪代码
public void installApk(String apkPath) {
// 1. 检查权限
if (!checkInstallPermission()) {
requestInstallPermission();
return;
}
// 2. 创建安装会话
PackageInstaller installer = getPackageInstaller();
SessionParams params = new SessionParams(MODE_FULL_INSTALL);
int sessionId = installer.createSession(params);
// 3. 写入 APK
Session session = installer.openSession(sessionId);
writeApkToSession(session, apkPath);
// 4. 提交安装
Intent intent = new Intent(ACTION_INSTALL_COMPLETE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(...);
session.commit(pendingIntent.getIntentSender());
}
```
#### 3. 与 Kiosk 模式集成
- **安装前**:保持 Kiosk 模式(无需退出)
- **安装中**:显示自定义安装进度界面
- **安装后**:自动恢复或重启应用
## 📊 方案对比表
| 方案 | 需要退出 Kiosk | 用户交互 | 稳定性 | 实现难度 | 推荐度 |
|------|---------------|---------|--------|---------|--------|
| 当前方案(系统安装器) | ✅ 需要 | ✅ 需要 | ⭐⭐⭐⭐⭐ | ⭐ 简单 | ⭐⭐ |
| PackageInstaller | ❌ 不需要 | ⚠️ 可选 | ⭐⭐⭐⭐ | ⭐⭐⭐ 中等 | ⭐⭐⭐⭐⭐ |
| Device Owner 静默安装 | ❌ 不需要 | ❌ 不需要 | ⭐⭐⭐ | ⭐⭐ 简单 | ⭐⭐⭐⭐ |
| 反射调用 | ❌ 不需要 | ❌ 不需要 | ⭐⭐ | ⭐⭐⭐⭐ 困难 | ⭐ |
## ⚠️ 注意事项
### Android 8.0 特殊处理
1. **`REQUEST_INSTALL_PACKAGES` 权限**
- 需要运行时请求
- 用户需要在设置中授权"安装未知应用"
2. **FileProvider 配置**
- Android 7.0+ 需要使用 FileProvider
- 需要在 AndroidManifest.xml 中配置
3. **存储权限**
- Android 8.0 需要存储权限读取 APK 文件
- 使用 `WRITE_EXTERNAL_STORAGE` 或 `MANAGE_EXTERNAL_STORAGE`
### Device Owner 优势
- ✅ 可以绕过部分权限限制
- ✅ 可以静默安装(如果 API 支持)
- ✅ 不需要用户交互
## 🚀 实施建议
### 阶段一:基础实现
1. 创建 `InstallModule` 原生插件
2. 实现 PackageInstaller 基础安装功能
3. 添加安装进度回调
### 阶段二:优化集成
1. 与 Kiosk 模式无缝集成
2. 添加自定义安装界面
3. 优化错误处理
### 阶段三:Device Owner 优化
1. 利用 Device Owner 权限简化流程
2. 尝试静默安装(如果可行)
3. 完善权限检查
## 📝 参考资源
- [Android PackageInstaller 官方文档](https://developer.android.com/reference/android/content/pm/PackageInstaller)
- [Android 8.0 行为变更](https://developer.android.com/about/versions/oreo/android-8.0-changes)
- [Device Owner 权限说明](https://developer.android.com/work/dpc/build-device-owner)
## ✅ 结论
**推荐使用 PackageInstaller API 方案**,原因:
1. 官方 API,稳定可靠
2. 支持自定义安装界面
3. 可以在 Kiosk 模式下工作
4. 结合 Device Owner 权限可以优化流程
5. 符合 Android 安全策略
**实施优先级**:
1. ⭐⭐⭐⭐⭐ 创建 InstallModule 原生插件
2. ⭐⭐⭐⭐ 实现 PackageInstaller 安装逻辑
3. ⭐⭐⭐ 添加安装进度界面
4. ⭐⭐ 优化 Device Owner 权限利用