| | |
| | | // index.uts - Android 应用保活插件 |
| | | |
| | | // 引用 Android API |
| | | import Context from "android.content.Context"; |
| | | import Intent from "android.content.Intent"; |
| | | import Build from "android.os.Build"; |
| | | import NotificationChannel from "android.app.NotificationChannel"; |
| | | import NotificationManager from "android.app.NotificationManager"; |
| | | import Notification from "android.app.Notification"; |
| | | import PendingIntent from "android.app.PendingIntent"; |
| | | import PowerManager from "android.os.PowerManager"; |
| | | import BatteryManager from "android.os.BatteryManager"; |
| | | import { UTSAndroid } from "io.dcloud.uts"; |
| | | import Context from 'android.content.Context' |
| | | import Intent from 'android.content.Intent' |
| | | import Build from 'android.os.Build' |
| | | import NotificationChannel from 'android.app.NotificationChannel' |
| | | import NotificationManager from 'android.app.NotificationManager' |
| | | import Notification from 'android.app.Notification' |
| | | import PendingIntent from 'android.app.PendingIntent' |
| | | import PowerManager from 'android.os.PowerManager' |
| | | import BatteryManager from 'android.os.BatteryManager' |
| | | import { UTSAndroid } from 'io.dcloud.uts' |
| | | |
| | | // 保活服务的通知渠道ID |
| | | const CHANNEL_ID = "keep_alive_channel"; |
| | | const NOTIFICATION_ID: Int = 1001; |
| | | const CHANNEL_ID = 'keep_alive_channel' |
| | | const NOTIFICATION_ID: Int = 1001 |
| | | |
| | | // 保活服务是否已启动的标志 |
| | | let isKeepAliveRunning = false; |
| | | let isKeepAliveRunning = false |
| | | |
| | | // WakeLock 引用,用于保持 CPU 运行 |
| | | let wakeLock: PowerManager.WakeLock | null = null; |
| | | let wakeLock: PowerManager.WakeLock | null = null |
| | | |
| | | /** |
| | | * 启动应用保活 |
| | | * 通过 WakeLock + 通知来保持应用在后台运行 |
| | | */ |
| | | export function keepAliveStart(): void { |
| | | const context = UTSAndroid.getAppContext(); |
| | | if (context == null) { |
| | | console.error("keepAliveStart: 获取应用上下文失败"); |
| | | return; |
| | | } |
| | | const context = UTSAndroid.getAppContext() |
| | | if (context == null) { |
| | | console.error('keepAliveStart: 获取应用上下文失败') |
| | | return |
| | | } |
| | | |
| | | if (isKeepAliveRunning) { |
| | | console.log("keepAliveStart: 保活服务已在运行中"); |
| | | return; |
| | | } |
| | | if (isKeepAliveRunning) { |
| | | console.log('keepAliveStart: 保活服务已在运行中') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | // 1. 获取 WakeLock 唤醒锁,保持 CPU 运行 |
| | | acquireWakeLock(context); |
| | | try { |
| | | // 1. 获取 WakeLock 唤醒锁,保持 CPU 运行 |
| | | acquireWakeLock(context) |
| | | |
| | | // 2. 创建通知渠道 (Android 8.0+) |
| | | createNotificationChannel(context); |
| | | // 2. 创建通知渠道 (Android 8.0+) |
| | | createNotificationChannel(context) |
| | | |
| | | // 3. 创建并显示前台通知 |
| | | showForegroundNotification(context); |
| | | // 3. 创建并显示前台通知 |
| | | showForegroundNotification(context) |
| | | |
| | | // 4. 注册应用生命周期监听 |
| | | registerLifecycleCallbacks(); |
| | | // 4. 注册应用生命周期监听 |
| | | registerLifecycleCallbacks() |
| | | |
| | | isKeepAliveRunning = true; |
| | | console.log("keepAliveStart: 保活服务启动成功(WakeLock已获取)"); |
| | | } catch (e) { |
| | | console.error("keepAliveStart: 启动失败", e); |
| | | } |
| | | isKeepAliveRunning = true |
| | | console.log('keepAliveStart: 保活服务启动成功(WakeLock已获取)') |
| | | } catch (e) { |
| | | console.error('keepAliveStart: 启动失败', e) |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 停止应用保活 |
| | | */ |
| | | export function keepAliveStop(): void { |
| | | const context = UTSAndroid.getAppContext(); |
| | | if (context == null) { |
| | | return; |
| | | } |
| | | const context = UTSAndroid.getAppContext() |
| | | if (context == null) { |
| | | return |
| | | } |
| | | |
| | | try { |
| | | // 释放 WakeLock |
| | | releaseWakeLock(); |
| | | try { |
| | | // 释放 WakeLock |
| | | releaseWakeLock() |
| | | |
| | | // 取消通知 |
| | | const notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager; |
| | | notificationManager.cancel(NOTIFICATION_ID); |
| | | // 取消通知 |
| | | const notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager |
| | | notificationManager.cancel(NOTIFICATION_ID) |
| | | |
| | | isKeepAliveRunning = false; |
| | | console.log("keepAliveStop: 保活服务已停止(WakeLock已释放)"); |
| | | } catch (e) { |
| | | console.error("keepAliveStop: 停止失败", e); |
| | | } |
| | | isKeepAliveRunning = false |
| | | console.log('keepAliveStop: 保活服务已停止(WakeLock已释放)') |
| | | } catch (e) { |
| | | console.error('keepAliveStop: 停止失败', e) |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 检查保活服务是否正在运行 |
| | | */ |
| | | export function isKeepAliveActive(): boolean { |
| | | return isKeepAliveRunning; |
| | | return isKeepAliveRunning |
| | | } |
| | | |
| | | /** |
| | | * 获取电池电量百分比 |
| | | */ |
| | | export function getBatteryCapacity(): string { |
| | | const context = UTSAndroid.getAppContext(); |
| | | if (context != null) { |
| | | const manager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager; |
| | | const currentLevel: number = manager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY); |
| | | return '' + currentLevel + '%'; |
| | | } |
| | | return "0%"; |
| | | const context = UTSAndroid.getAppContext() |
| | | if (context != null) { |
| | | const manager = context.getSystemService(Context.BATTERY_SERVICE) as BatteryManager |
| | | const currentLevel: number = manager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY) |
| | | return '' + currentLevel + '%' |
| | | } |
| | | return '0%' |
| | | } |
| | | |
| | | /** |
| | |
| | | * 调用此方法会弹出系统设置页面 |
| | | */ |
| | | export function requestIgnoreBatteryOptimization(): void { |
| | | const context = UTSAndroid.getAppContext(); |
| | | if (context == null) { |
| | | return; |
| | | } |
| | | const context = UTSAndroid.getAppContext() |
| | | if (context == null) { |
| | | return |
| | | } |
| | | |
| | | if (Build.VERSION.SDK_INT >= 23) { // Android 6.0+ |
| | | const powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager; |
| | | const packageName = context.getPackageName(); |
| | | if (Build.VERSION.SDK_INT >= 23) { |
| | | // Android 6.0+ |
| | | const powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager |
| | | const packageName = context.getPackageName() |
| | | |
| | | if (!powerManager.isIgnoringBatteryOptimizations(packageName)) { |
| | | try { |
| | | const intent = new Intent(); |
| | | intent.setAction("android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"); |
| | | intent.setData(android.net.Uri.parse("package:" + packageName)); |
| | | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| | | context.startActivity(intent); |
| | | } catch (e) { |
| | | console.error("requestIgnoreBatteryOptimization: 打开设置失败", e); |
| | | } |
| | | } else { |
| | | console.log("requestIgnoreBatteryOptimization: 已在白名单中"); |
| | | } |
| | | if (!powerManager.isIgnoringBatteryOptimizations(packageName)) { |
| | | try { |
| | | const intent = new Intent() |
| | | intent.setAction('android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS') |
| | | intent.setData(android.net.Uri.parse('package:' + packageName)) |
| | | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) |
| | | context.startActivity(intent) |
| | | } catch (e) { |
| | | console.error('requestIgnoreBatteryOptimization: 打开设置失败', e) |
| | | } |
| | | } else { |
| | | console.log('requestIgnoreBatteryOptimization: 已在白名单中') |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 检查是否已忽略电池优化 |
| | | */ |
| | | export function isIgnoringBatteryOptimizations(): boolean { |
| | | const context = UTSAndroid.getAppContext(); |
| | | if (context == null) { |
| | | return false; |
| | | } |
| | | const context = UTSAndroid.getAppContext() |
| | | if (context == null) { |
| | | return false |
| | | } |
| | | |
| | | if (Build.VERSION.SDK_INT >= 23) { |
| | | const powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager; |
| | | const packageName = context.getPackageName(); |
| | | return powerManager.isIgnoringBatteryOptimizations(packageName); |
| | | } |
| | | if (Build.VERSION.SDK_INT >= 23) { |
| | | const powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager |
| | | const packageName = context.getPackageName() |
| | | return powerManager.isIgnoringBatteryOptimizations(packageName) |
| | | } |
| | | |
| | | return true; // Android 6.0 以下默认返回 true |
| | | return true // Android 6.0 以下默认返回 true |
| | | } |
| | | |
| | | /** |
| | | * 创建通知渠道 (Android 8.0+ 必需) |
| | | */ |
| | | function createNotificationChannel(context: Context): void { |
| | | if (Build.VERSION.SDK_INT >= 26) { // Android 8.0+ |
| | | const channel = new NotificationChannel( |
| | | CHANNEL_ID, |
| | | "应用保活服务", |
| | | NotificationManager.IMPORTANCE_LOW |
| | | ); |
| | | channel.setDescription("保持应用在后台运行"); |
| | | channel.setShowBadge(false); |
| | | channel.enableLights(false); |
| | | channel.enableVibration(false); |
| | | if (Build.VERSION.SDK_INT >= 26) { |
| | | // Android 8.0+ |
| | | const channel = new NotificationChannel(CHANNEL_ID, '应用保活服务', NotificationManager.IMPORTANCE_LOW) |
| | | channel.setDescription('保持应用在后台运行') |
| | | channel.setShowBadge(false) |
| | | channel.enableLights(false) |
| | | channel.enableVibration(false) |
| | | |
| | | const notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager; |
| | | notificationManager.createNotificationChannel(channel); |
| | | } |
| | | const notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager |
| | | notificationManager.createNotificationChannel(channel) |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 显示前台通知 |
| | | */ |
| | | function showForegroundNotification(context: Context): void { |
| | | // 获取应用的启动 Intent |
| | | const packageManager = context.getPackageManager(); |
| | | const launchIntent = packageManager.getLaunchIntentForPackage(context.getPackageName()); |
| | | // 获取应用的启动 Intent |
| | | const packageManager = context.getPackageManager() |
| | | const launchIntent = packageManager.getLaunchIntentForPackage(context.getPackageName()) |
| | | |
| | | let pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT; |
| | | if (Build.VERSION.SDK_INT >= 31) { // Android 12+ |
| | | pendingIntentFlags = pendingIntentFlags | PendingIntent.FLAG_IMMUTABLE; |
| | | } |
| | | let pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT |
| | | if (Build.VERSION.SDK_INT >= 31) { |
| | | // Android 12+ |
| | | pendingIntentFlags = pendingIntentFlags | PendingIntent.FLAG_IMMUTABLE |
| | | } |
| | | |
| | | const pendingIntent = PendingIntent.getActivity( |
| | | context, |
| | | 0, |
| | | launchIntent, |
| | | pendingIntentFlags |
| | | ); |
| | | const pendingIntent = PendingIntent.getActivity(context, 0, launchIntent, pendingIntentFlags) |
| | | |
| | | // 构建通知 |
| | | let builder: Notification.Builder; |
| | | if (Build.VERSION.SDK_INT >= 26) { |
| | | builder = new Notification.Builder(context, CHANNEL_ID); |
| | | } else { |
| | | builder = new Notification.Builder(context); |
| | | } |
| | | // 构建通知 |
| | | let builder: Notification.Builder |
| | | if (Build.VERSION.SDK_INT >= 26) { |
| | | builder = new Notification.Builder(context, CHANNEL_ID) |
| | | } else { |
| | | builder = new Notification.Builder(context) |
| | | } |
| | | |
| | | // 获取应用图标 |
| | | const appInfo = context.getApplicationInfo(); |
| | | const appIcon = appInfo.icon; |
| | | const appName = packageManager.getApplicationLabel(appInfo).toString(); |
| | | // 获取应用图标 |
| | | const appInfo = context.getApplicationInfo() |
| | | const appIcon = appInfo.icon |
| | | const appName = packageManager.getApplicationLabel(appInfo).toString() |
| | | |
| | | builder.setSmallIcon(appIcon) |
| | | .setContentTitle(appName) |
| | | .setContentText("服务运行中") |
| | | .setContentIntent(pendingIntent) |
| | | .setOngoing(true) // 常驻通知,不可被滑动删除 |
| | | .setPriority(Notification.PRIORITY_LOW); |
| | | builder |
| | | .setSmallIcon(appIcon) |
| | | .setContentTitle(appName) |
| | | .setContentText('服务运行中') |
| | | .setContentIntent(pendingIntent) |
| | | .setOngoing(true) // 常驻通知,不可被滑动删除 |
| | | .setPriority(Notification.PRIORITY_LOW) |
| | | |
| | | if (Build.VERSION.SDK_INT >= 21) { |
| | | builder.setCategory(Notification.CATEGORY_SERVICE); |
| | | } |
| | | if (Build.VERSION.SDK_INT >= 21) { |
| | | builder.setCategory(Notification.CATEGORY_SERVICE) |
| | | } |
| | | |
| | | const notification = builder.build(); |
| | | const notification = builder.build() |
| | | |
| | | // 显示通知 |
| | | const notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager; |
| | | notificationManager.notify(NOTIFICATION_ID, notification); |
| | | // 显示通知 |
| | | const notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager |
| | | notificationManager.notify(NOTIFICATION_ID, notification) |
| | | } |
| | | |
| | | /** |
| | |
| | | * PARTIAL_WAKE_LOCK: 保持 CPU 运行,屏幕和键盘背光可以关闭 |
| | | */ |
| | | function acquireWakeLock(context: Context): void { |
| | | if (wakeLock != null && wakeLock!.isHeld()) { |
| | | console.log("acquireWakeLock: WakeLock 已持有"); |
| | | return; |
| | | } |
| | | if (wakeLock != null && wakeLock!.isHeld()) { |
| | | console.log('acquireWakeLock: WakeLock 已持有') |
| | | return |
| | | } |
| | | |
| | | try { |
| | | const powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager; |
| | | // PARTIAL_WAKE_LOCK 保持 CPU 运行 |
| | | wakeLock = powerManager.newWakeLock( |
| | | PowerManager.PARTIAL_WAKE_LOCK, |
| | | "KeepAlive::WakeLock" |
| | | ); |
| | | try { |
| | | const powerManager = context.getSystemService(Context.POWER_SERVICE) as PowerManager |
| | | // PARTIAL_WAKE_LOCK 保持 CPU 运行 |
| | | wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, 'KeepAlive::WakeLock') |
| | | |
| | | if (wakeLock != null) { |
| | | // 设置超时时间为无限(传 0 或负数表示无限) |
| | | // 使用 acquire() 无参版本 |
| | | wakeLock!.setReferenceCounted(false); |
| | | wakeLock!.acquire(); |
| | | console.log("acquireWakeLock: WakeLock 获取成功"); |
| | | } |
| | | } catch (e) { |
| | | console.error("acquireWakeLock: WakeLock 获取失败", e); |
| | | if (wakeLock != null) { |
| | | // 设置超时时间为无限(传 0 或负数表示无限) |
| | | // 使用 acquire() 无参版本 |
| | | wakeLock!.setReferenceCounted(false) |
| | | wakeLock!.acquire() |
| | | console.log('acquireWakeLock: WakeLock 获取成功') |
| | | } |
| | | } catch (e) { |
| | | console.error('acquireWakeLock: WakeLock 获取失败', e) |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 释放 WakeLock 唤醒锁 |
| | | */ |
| | | function releaseWakeLock(): void { |
| | | if (wakeLock != null && wakeLock!.isHeld()) { |
| | | try { |
| | | wakeLock!.release(); |
| | | wakeLock = null; |
| | | console.log("releaseWakeLock: WakeLock 已释放"); |
| | | } catch (e) { |
| | | console.error("releaseWakeLock: WakeLock 释放失败", e); |
| | | } |
| | | if (wakeLock != null && wakeLock!.isHeld()) { |
| | | try { |
| | | wakeLock!.release() |
| | | wakeLock = null |
| | | console.log('releaseWakeLock: WakeLock 已释放') |
| | | } catch (e) { |
| | | console.error('releaseWakeLock: WakeLock 释放失败', e) |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | * 当应用进入后台时,确保 WakeLock 保持持有 |
| | | */ |
| | | function registerLifecycleCallbacks(): void { |
| | | // 应用进入后台时的回调 |
| | | UTSAndroid.onAppActivityPause(() => { |
| | | console.log("onAppActivityPause: 应用进入后台"); |
| | | // 确保 WakeLock 仍然持有 |
| | | const context = UTSAndroid.getAppContext(); |
| | | if (context != null && isKeepAliveRunning) { |
| | | if (wakeLock == null || !wakeLock!.isHeld()) { |
| | | acquireWakeLock(context); |
| | | } |
| | | } |
| | | }); |
| | | // 应用进入后台时的回调 |
| | | UTSAndroid.onAppActivityPause(() => { |
| | | console.log('onAppActivityPause: 应用进入后台') |
| | | // 确保 WakeLock 仍然持有 |
| | | const context = UTSAndroid.getAppContext() |
| | | if (context != null && isKeepAliveRunning) { |
| | | if (wakeLock == null || !wakeLock!.isHeld()) { |
| | | acquireWakeLock(context) |
| | | } |
| | | } |
| | | }) |
| | | |
| | | // 应用返回前台时的回调 |
| | | UTSAndroid.onAppActivityResume(() => { |
| | | console.log("onAppActivityResume: 应用返回前台"); |
| | | }); |
| | | // 应用返回前台时的回调 |
| | | UTSAndroid.onAppActivityResume(() => { |
| | | console.log('onAppActivityResume: 应用返回前台') |
| | | }) |
| | | |
| | | // 应用被销毁时的回调 |
| | | UTSAndroid.onAppActivityDestroy(() => { |
| | | console.log("onAppActivityDestroy: 应用被销毁"); |
| | | // 释放资源 |
| | | releaseWakeLock(); |
| | | isKeepAliveRunning = false; |
| | | }); |
| | | // 应用被销毁时的回调 |
| | | UTSAndroid.onAppActivityDestroy(() => { |
| | | console.log('onAppActivityDestroy: 应用被销毁') |
| | | // 释放资源 |
| | | releaseWakeLock() |
| | | isKeepAliveRunning = false |
| | | }) |
| | | |
| | | console.log("registerLifecycleCallbacks: 生命周期回调已注册"); |
| | | console.log('registerLifecycleCallbacks: 生命周期回调已注册') |
| | | } |