内置安装器方案分析.md 7.7 KB

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 的应用可以绕过部分限制

当前配置状态

<!-- AndroidManifest.xml 中已配置 -->
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

2. 内置安装器实现方案对比

方案一:PackageInstaller API(推荐)⭐

适用版本:Android 5.0+ (API 21+)

优点

  • ✅ 官方 API,稳定可靠
  • ✅ 支持静默安装(需要系统权限或 Device Owner)
  • ✅ 可以显示自定义安装界面
  • ✅ 支持安装进度回调

缺点

  • ⚠️ Android 8.0+ 需要 REQUEST_INSTALL_PACKAGES 权限
  • ⚠️ 需要创建 Session 和安装会话

实现原理

// 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 应用
  • ⚠️ 需要系统级权限

实现原理

// 使用 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

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. 安装流程

// 伪代码
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_STORAGEMANAGE_EXTERNAL_STORAGE

Device Owner 优势

  • ✅ 可以绕过部分权限限制
  • ✅ 可以静默安装(如果 API 支持)
  • ✅ 不需要用户交互

🚀 实施建议

阶段一:基础实现

  1. 创建 InstallModule 原生插件
  2. 实现 PackageInstaller 基础安装功能
  3. 添加安装进度回调

阶段二:优化集成

  1. 与 Kiosk 模式无缝集成
  2. 添加自定义安装界面
  3. 优化错误处理

阶段三:Device Owner 优化

  1. 利用 Device Owner 权限简化流程
  2. 尝试静默安装(如果可行)
  3. 完善权限检查

📝 参考资源

✅ 结论

推荐使用 PackageInstaller API 方案,原因:

  1. 官方 API,稳定可靠
  2. 支持自定义安装界面
  3. 可以在 Kiosk 模式下工作
  4. 结合 Device Owner 权限可以优化流程
  5. 符合 Android 安全策略

实施优先级

  1. ⭐⭐⭐⭐⭐ 创建 InstallModule 原生插件
  2. ⭐⭐⭐⭐ 实现 PackageInstaller 安装逻辑
  3. ⭐⭐⭐ 添加安装进度界面
  4. ⭐⭐ 优化 Device Owner 权限利用