# 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 权限利用