在固定屏幕(Kiosk/LockTask Mode)下,应用无法拉起 APK 安装页面。这是因为:
startLockTask() 状态时,系统只允许白名单中的包运行setLockTaskPackages() 设置的白名单中在安装 APK 前临时退出 Kiosk 模式,安装完成或失败后自动恢复 Kiosk 模式。
文件: myLockView/src/main/java/com/ble/mylockview/admin/KioskManager.java
新增两个方法:
/**
* 临时退出 Kiosk 模式(用于安装 APK 等操作)
* 不修改 kioskEnabled 标志,允许后续自动重新锁定
*/
public static void temporaryExitKiosk() {
Activity act = getActivity();
if (act == null) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
try {
act.stopLockTask();
kioskStarted = false;
Log.i(TAG, "⏸️ 临时退出 Kiosk(用于 APK 安装)");
} catch (Exception e) {
Log.e(TAG, "临时退出 Kiosk 失败", e);
}
}
}
/**
* 恢复 Kiosk 模式(临时退出后重新进入)
*/
public static void resumeKiosk() {
Activity act = getActivity();
if (act == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) return;
// 只有在 kioskEnabled 为 true 且未启动时才重新启动
if (!kioskEnabled || kioskStarted) return;
if (debugMode) {
try {
act.startLockTask();
kioskStarted = true;
Log.i(TAG, "▶️ 恢复 Kiosk (Debug 模式)");
} catch (Exception ignored) {}
return;
}
if (!dpm.isDeviceOwnerApp(act.getPackageName())) {
Log.w(TAG, "❌ 非 DeviceOwner,无法恢复 Kiosk");
return;
}
try {
dpm.setLockTaskPackages(
admin,
new String[]{act.getPackageName()}
);
act.startLockTask();
kioskStarted = true;
Log.i(TAG, "▶️ 恢复 Kiosk");
} catch (Exception e) {
Log.e(TAG, "恢复 Kiosk 失败", e);
}
}
关键区别:
exitKiosk(): 完全退出,设置 kioskEnabled = false,不会自动恢复temporaryExitKiosk(): 临时退出,保持 kioskEnabled = true,可以通过 resumeKiosk() 恢复文件: uniplugin_module/src/main/java/io/dcloud/uniplugin/KioskModule.java
package io.dcloud.uniplugin;
import com.ble.mylockview.admin.KioskManager;
import io.dcloud.feature.uniapp.annotation.UniJSMethod;
import io.dcloud.feature.uniapp.bridge.UniJSCallback;
import io.dcloud.feature.uniapp.common.UniModule;
public class KioskModule extends UniModule {
@UniJSMethod(uiThread = true)
public void temporaryExitKiosk(UniJSCallback callback) {
// 临时退出 Kiosk
}
@UniJSMethod(uiThread = true)
public void resumeKiosk(UniJSCallback callback) {
// 恢复 Kiosk
}
@UniJSMethod(uiThread = true)
public void exitKiosk(UniJSCallback callback) {
// 完全退出 Kiosk
}
}
文件: app/src/main/assets/dcloud_uniplugins.json
{
"nativePlugins": [
{
"plugins": [
{
"type": "module",
"name": "KioskModule",
"class": "io.dcloud.uniplugin.KioskModule"
}
]
}
]
}
文件: heart-app-hbuilder-x/pages/platform-page/app-info/app-info.vue
/**
* 临时退出 Kiosk 模式并安装 APK
*/
temporaryExitKioskAndInstall() {
const KioskModule = uni.requireNativePlugin('KioskModule');
if (!KioskModule) {
console.warn('⚠️ KioskModule 不可用,直接尝试安装');
this.installApk();
return;
}
// 临时退出 Kiosk
KioskModule.temporaryExitKiosk(res => {
console.log('KioskModule.temporaryExitKiosk 结果:', res);
// 延迟 300ms 后安装,确保 Kiosk 完全退出
setTimeout(() => {
this.installApk();
}, 300);
});
},
/**
* 执行 APK 安装
*/
installApk() {
plus.runtime.install(this.saveFile, { force: true }, () => {
console.log('✅ 安装成功');
uni.showToast({ title: '安装成功', icon: 'none' });
// 安装成功后,尝试恢复 Kiosk 模式
this.resumeKiosk();
}, err => {
console.error('❌ 安装失败', err);
uni.showToast({ title: '安装失败: ' + err.message, icon: 'none' });
// 安装失败后也要恢复 Kiosk
this.resumeKiosk();
});
},
/**
* 恢复 Kiosk 模式
*/
resumeKiosk() {
const KioskModule = uni.requireNativePlugin('KioskModule');
if (!KioskModule) {
console.warn('⚠️ KioskModule 不可用,无法恢复 Kiosk');
return;
}
// 延迟 1 秒后恢复,给用户一些操作时间
setTimeout(() => {
KioskModule.resumeKiosk(res => {
console.log('KioskModule.resumeKiosk 结果:', res);
});
}, 1000);
}
文件: uniplugin_module/build.gradle
dependencies {
implementation project(path: ':core')
implementation project(path: ':ICDeviceManager')
implementation project(path: ':myLockView') // ✅ 新增
// ...
}
用户点击"更新" 或 "安装已下载的文件"
↓
调用 temporaryExitKioskAndInstall()
↓
KioskModule.temporaryExitKiosk() ← 退出 Kiosk,但保持 kioskEnabled=true
↓
延迟 300ms(确保退出完成)
↓
plus.runtime.install() ← 拉起系统安装界面(现在可以成功)
↓
安装成功/失败的回调
↓
resumeKiosk() ← 延迟 1 秒后自动恢复 Kiosk
↓
KioskManager.resumeKiosk()
↓
重新进入 Kiosk 模式
bBeng-HeartRate-4.66-pad/uniplugin_module/src/main/java/io/dcloud/uniplugin/KioskModule.javabBeng-HeartRate-4.66-pad/myLockView/src/main/java/com/ble/mylockview/admin/KioskManager.java
temporaryExitKiosk() 方法resumeKiosk() 方法bBeng-HeartRate-4.66-pad/app/src/main/assets/dcloud_uniplugins.json
KioskModule 插件bBeng-HeartRate-4.66-pad/uniplugin_module/build.gradle
myLockView 模块依赖heart-app-hbuilder-x/pages/platform-page/app-info/app-info.vue
temporaryExitKioskAndInstall() 方法installApk() 方法resumeKiosk() 方法openDownloadFolder() 调用新方法cd bBeng-HeartRate-4.66-pad
./gradlew assembleRelease
在 HBuilderX 中:
heart-app-hbuilder-x 项目adb install -r app/build/outputs/apk/release/app-release.apk
测试固定屏幕下载安装:
测试已下载文件安装:
测试 Kiosk 恢复:
时序控制:
兼容性:
KioskModule 不可用(非 Kiosk 模式运行),会自动降级到直接安装Device Owner 要求:
resumeKiosk() 需要应用是 Device Owner安装成功后的行为:
MyApplication 处理)resumeKiosk() 主要用于安装失败的情况安装过程中可以查看以下日志:
adb logcat -s KioskManager KioskModule app-info.vue
关键日志:
⏸️ 临时退出 Kiosk(用于 APK 安装) - 成功退出▶️ 恢复 Kiosk - 成功恢复KioskModule.temporaryExitKiosk 结果 - Vue 层调用结果✅ 安装成功 / ❌ 安装失败 - 安装结果这个方案不仅可以用于 APK 安装,还可以用于其他需要跳转到外部应用的场景:
只需在跳转前调用 temporaryExitKiosk(),完成后调用 resumeKiosk() 即可。
通过增强 KioskManager 和创建 KioskModule 插件,我们实现了:
✅ 固定屏幕下可以正常拉起 APK 安装界面
✅ 安装完成后自动恢复 Kiosk 模式
✅ 代码结构清晰,易于维护
✅ 兼容普通模式和 Kiosk 模式
✅ 可扩展到其他需要跳转的场景
修复日期: 2026/1/26
修复人员: AI Assistant
测试状态: 待测试