const { withAndroidManifest, withDangerousMod } = require('@expo/config-plugins'); const fs = require('fs'); const path = require('path'); const FOREGROUND_SERVICE_PERMISSION = 'android.permission.FOREGROUND_SERVICE'; const FOREGROUND_SERVICE_DATA_SYNC = 'android.permission.FOREGROUND_SERVICE_DATA_SYNC'; /** * Expo Config Plugin:为 Android 添加前台服务保活 * * 功能: * 1. 添加 FOREGROUND_SERVICE 相关权限 * 2. 在 AndroidManifest 中声明 Service * 3. 生成 Kotlin 原生服务代码 * 4. 生成 React Native Bridge 模块 * * 参考:Element Android 的 GuardAndroidService */ const withForegroundService = (config, options = {}) => { const { notificationTitle = '威友', notificationBody = '正在后台同步消息', notificationIcon = 'ic_notification', channelId = 'withyou_sync_foreground', channelName = '消息同步', } = options; // 1. 添加权限和 Service 声明到 AndroidManifest config = withAndroidManifest(config, (config) => { const manifest = config.modResults; // 添加权限声明 const permissions = [ FOREGROUND_SERVICE_PERMISSION, FOREGROUND_SERVICE_DATA_SYNC, ]; permissions.forEach((permission) => { const existingPermission = manifest.manifest['uses-permission']?.find( (p) => p.$['android:name'] === permission ); if (!existingPermission) { if (!manifest.manifest['uses-permission']) { manifest.manifest['uses-permission'] = []; } manifest.manifest['uses-permission'].push({ $: { 'android:name': permission }, }); } }); // 添加 Service 声明 if (!manifest.manifest.application[0].service) { manifest.manifest.application[0].service = []; } const existingService = manifest.manifest.application[0].service.find( (s) => s.$['android:name'] === '.SyncForegroundService' ); if (!existingService) { manifest.manifest.application[0].service.push({ $: { 'android:name': '.SyncForegroundService', 'android:enabled': 'true', 'android:exported': 'false', 'android:foregroundServiceType': 'dataSync', }, }); } return config; }); // 2. 生成 Kotlin 原生代码 config = withDangerousMod(config, [ 'android', async (config) => { const projectRoot = config.modRequest.projectRoot; const packageName = config.android.package; const kotlinPath = path.join( projectRoot, 'android/app/src/main/java', packageName.replace(/\./g, '/') ); // 确保目录存在 if (!fs.existsSync(kotlinPath)) { fs.mkdirSync(kotlinPath, { recursive: true }); } // 生成 SyncForegroundService.kt const serviceCode = generateServiceCode(packageName, { notificationTitle, notificationBody, notificationIcon, channelId, channelName, }); fs.writeFileSync( path.join(kotlinPath, 'SyncForegroundService.kt'), serviceCode ); // 生成 ForegroundServiceModule.kt (React Native Bridge) const moduleCode = generateModuleCode(packageName); fs.writeFileSync( path.join(kotlinPath, 'ForegroundServiceModule.kt'), moduleCode ); // 生成 ForegroundServicePackage.kt const packageCode = generatePackageCode(packageName); fs.writeFileSync( path.join(kotlinPath, 'ForegroundServicePackage.kt'), packageCode ); // 修改 MainApplication.kt 注册模块 const mainAppPath = path.join(kotlinPath, 'MainApplication.kt'); if (fs.existsSync(mainAppPath)) { let content = fs.readFileSync(mainAppPath, 'utf-8'); // 添加 import if (!content.includes('ForegroundServicePackage')) { content = content.replace( /package .*\n/, `$&\nimport ${packageName}.ForegroundServicePackage\n` ); } // 在 packages 列表中添加 if (!content.includes('ForegroundServicePackage()')) { // 查找 PackageList(this).packages.apply 块 content = content.replace( /(PackageList\(this\)\.packages\.apply\s*\{)/, `$1\n // 前台服务模块\n add(ForegroundServicePackage())` ); } fs.writeFileSync(mainAppPath, content); } // 创建通知图标目录和文件 const drawablePath = path.join(projectRoot, 'android/app/src/main/res/drawable'); if (!fs.existsSync(drawablePath)) { fs.mkdirSync(drawablePath, { recursive: true }); } const iconPath = path.join(drawablePath, 'ic_notification.xml'); if (!fs.existsSync(iconPath)) { fs.writeFileSync(iconPath, generateNotificationIcon()); } return config; }, ]); return config; }; /** * 生成前台服务 Kotlin 代码 */ function generateServiceCode(packageName, options) { const { notificationTitle, notificationBody, notificationIcon, channelId, channelName } = options; return `package ${packageName} import android.app.Notification import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent import android.content.pm.ServiceInfo import android.os.Build import android.os.IBinder import androidx.core.app.NotificationCompat import androidx.core.content.ContextCompat /** * 前台同步服务 * * 在通知栏显示常驻通知,防止应用进程被系统杀死 * 类似 Element Android 的 GuardAndroidService 和 V2Ray 的保活机制 */ class SyncForegroundService : Service() { companion object { const val NOTIFICATION_ID = 1001 const val CHANNEL_ID = "${channelId}" const val CHANNEL_NAME = "${channelName}" const val ACTION_START = "${packageName}.foreground.START" const val ACTION_STOP = "${packageName}.foreground.STOP" const val ACTION_UPDATE = "${packageName}.foreground.UPDATE" const val EXTRA_TITLE = "title" const val EXTRA_BODY = "body" /** * 启动前台服务 */ fun start(context: Context, title: String = "${notificationTitle}", body: String = "${notificationBody}") { val intent = Intent(context, SyncForegroundService::class.java).apply { action = ACTION_START putExtra(EXTRA_TITLE, title) putExtra(EXTRA_BODY, body) } try { ContextCompat.startForegroundService(context, intent) } catch (e: Exception) { android.util.Log.e("SyncForegroundService", "启动前台服务失败", e) } } /** * 停止前台服务 */ fun stop(context: Context) { val intent = Intent(context, SyncForegroundService::class.java).apply { action = ACTION_STOP } context.startService(intent) } /** * 更新通知内容 */ fun update(context: Context, title: String, body: String) { val intent = Intent(context, SyncForegroundService::class.java).apply { action = ACTION_UPDATE putExtra(EXTRA_TITLE, title) putExtra(EXTRA_BODY, body) } context.startService(intent) } } private var notificationTitle: String = "${notificationTitle}" private var notificationBody: String = "${notificationBody}" override fun onCreate() { super.onCreate() createNotificationChannel() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.action) { ACTION_START -> { notificationTitle = intent.getStringExtra(EXTRA_TITLE) ?: "${notificationTitle}" notificationBody = intent.getStringExtra(EXTRA_BODY) ?: "${notificationBody}" startForegroundCompat() } ACTION_STOP -> { stopForegroundCompat() stopSelf() } ACTION_UPDATE -> { notificationTitle = intent.getStringExtra(EXTRA_TITLE) ?: notificationTitle notificationBody = intent.getStringExtra(EXTRA_BODY) ?: notificationBody updateNotification() } } // START_STICKY: 服务被杀后自动重启 return START_STICKY } override fun onBind(intent: Intent?): IBinder? = null /** * 启动前台服务(兼容 Android Q+) */ private fun startForegroundCompat() { val notification = buildNotification() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { startForeground( NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC ) } else { startForeground(NOTIFICATION_ID, notification) } } /** * 停止前台服务(兼容 Android N+) */ private fun stopForegroundCompat() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { stopForeground(STOP_FOREGROUND_REMOVE) } else { @Suppress("DEPRECATION") stopForeground(true) } } /** * 创建通知渠道 */ private fun createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_MIN // 最低重要性,不打扰用户 ).apply { description = "后台消息同步服务" setSound(null, null) setShowBadge(false) enableLights(false) enableVibration(false) } val manager = getSystemService(NotificationManager::class.java) manager.createNotificationChannel(channel) } } /** * 构建通知 */ private fun buildNotification(): Notification { // 点击通知打开应用 val launchIntent = packageManager.getLaunchIntentForPackage(packageName) val pendingIntent = PendingIntent.getActivity( this, 0, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) return NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle(notificationTitle) .setContentText(notificationBody) .setSmallIcon(R.drawable.${notificationIcon}) .setCategory(NotificationCompat.CATEGORY_SERVICE) .setPriority(NotificationCompat.PRIORITY_MIN) .setOngoing(true) // 不可滑动清除 .setShowWhen(false) .setContentIntent(pendingIntent) .build() } /** * 更新通知内容 */ private fun updateNotification() { val manager = getSystemService(NotificationManager::class.java) manager.notify(NOTIFICATION_ID, buildNotification()) } } `; } /** * 生成 React Native Bridge 模块代码 */ function generateModuleCode(packageName) { return `package ${packageName} import android.content.Context import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.bridge.ReactContextBaseJavaModule import com.facebook.react.bridge.ReactMethod import com.facebook.react.bridge.Promise /** * React Native Bridge: 前台服务控制模块 */ class ForegroundServiceModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) { override fun getName(): String = "ForegroundService" /** * 启动前台服务 */ @ReactMethod fun start(title: String, body: String, promise: Promise) { try { SyncForegroundService.start(reactApplicationContext, title, body) promise.resolve(true) } catch (e: Exception) { promise.reject("FOREGROUND_SERVICE_ERROR", e.message) } } /** * 停止前台服务 */ @ReactMethod fun stop(promise: Promise) { try { SyncForegroundService.stop(reactApplicationContext) promise.resolve(true) } catch (e: Exception) { promise.reject("FOREGROUND_SERVICE_ERROR", e.message) } } /** * 更新通知内容 */ @ReactMethod fun update(title: String, body: String, promise: Promise) { try { SyncForegroundService.update(reactApplicationContext, title, body) promise.resolve(true) } catch (e: Exception) { promise.reject("FOREGROUND_SERVICE_ERROR", e.message) } } } `; } /** * 生成 React Native Package 代码 */ function generatePackageCode(packageName) { return `package ${packageName} import com.facebook.react.ReactPackage import com.facebook.react.bridge.NativeModule import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.uimanager.ViewManager class ForegroundServicePackage : ReactPackage { override fun createNativeModules(reactContext: ReactApplicationContext): List { return listOf(ForegroundServiceModule(reactContext)) } override fun createViewManagers(reactContext: ReactApplicationContext): List> { return emptyList() } } `; } /** * 生成通知图标 XML */ function generateNotificationIcon() { return ` `; } module.exports = withForegroundService;