fix: resolve service crash on start by fixing foreground service type check and notification icon

This commit is contained in:
CAtlas0784 committed 2026-11-01 14:16:22 +07:00
1 parent 1f88f6ea38
commit 92a4b4f545
3 files changed
+195 -123

No files matched your search

@@ -22,8 +22,12 @@ class GaCyApplication : Application() {
createNotificationChannel()
// Initialize embedded Python runtime
if (!Python.isStarted()) {
Python.start(AndroidPlatform(this))
try {
if (!Python.isStarted()) {
Python.start(AndroidPlatform(this))
}
} catch (t: Throwable) {
t.printStackTrace()
}
}
@@ -1,16 +1,18 @@
package com.gacy.android.service
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.lifecycle.LifecycleService
import androidx.lifecycle.lifecycleScope
import com.chaquo.python.Python
import com.chaquo.python.android.AndroidPlatform
import com.gacy.android.GaCyApplication
import com.gacy.android.MainActivity
import com.gacy.android.R
@@ -19,8 +21,10 @@ import com.gacy.android.data.PreferencesRepository
import com.gacy.android.data.UpdateRepository
import com.gacy.android.model.ServerExecutionMode
import com.gacy.android.model.ServerStatus
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@@ -37,19 +41,22 @@ class ServerLogReceiver {
}
}
class ServerService : LifecycleService() {
class ServerService : Service() {
private val serviceScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
private var serverThread: Thread? = null
private var probeJob: Job? = null
private var serverJob: Job? = null
private var executionJob: Job? = null
override fun onBind(intent: Intent?): IBinder? = null
override fun onCreate() {
super.onCreate()
createNotificationChannel()
_serverStatus.value = ServerStatus.STOPPED
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
val action = intent?.action ?: ACTION_START
when (action) {
@@ -66,95 +73,117 @@ class ServerService : LifecycleService() {
return
}
val prefs = PreferencesRepository(this)
val config = prefs.getServerConfig()
try {
val prefs = PreferencesRepository(this)
val config = prefs.getServerConfig()
_serverStatus.value = ServerStatus.STARTING
startForegroundCompat("GaCy Server is starting...")
LogBuffer.append("--- GaCy Server Starting ---")
LogBuffer.append("Mode: ${config.executionMode.displayNameEn}")
LogBuffer.append("Dispatch Port: ${config.dispatchPort} | Gameserver Port: ${config.gameserverPort}")
_serverStatus.value = ServerStatus.STARTING
startForegroundCompat("GaCy Server is starting...")
LogBuffer.append("--- GaCy Server Starting ---")
LogBuffer.append("Mode: ${config.executionMode.displayNameEn}")
LogBuffer.append("Dispatch Port: ${config.dispatchPort} | Gameserver Port: ${config.gameserverPort}")
if (config.executionMode == ServerExecutionMode.LOCAL_EMBEDDED) {
startLocalServerExecution(config.dispatchPort)
} else {
startRemoteMonitor(config.remoteHost, config.dispatchPort)
if (config.executionMode == ServerExecutionMode.LOCAL_EMBEDDED) {
startLocalServerExecution(config.dispatchPort)
} else {
startRemoteMonitor(config.remoteHost, config.dispatchPort)
}
} catch (t: Throwable) {
LogBuffer.append("[Start Error] ${t.localizedMessage ?: t.message}")
t.printStackTrace()
_serverStatus.value = ServerStatus.ERROR
stopForegroundCompat()
stopSelf()
}
}
private fun startLocalServerExecution(dispatchPort: Int) {
serverJob = lifecycleScope.launch(Dispatchers.IO) {
val updateRepo = UpdateRepository(this@ServerService)
if (!updateRepo.isServerInstalled()) {
LogBuffer.append("[Setup] Installing GaCy embedded server binaries from assets...")
val success = updateRepo.ensureServerInstalled()
if (!success) {
LogBuffer.append("[Error] Failed to install server binaries from assets.")
executionJob?.cancel()
executionJob = serviceScope.launch {
try {
val updateRepo = UpdateRepository(this@ServerService)
if (!updateRepo.isServerInstalled()) {
LogBuffer.append("[Setup] Installing GaCy embedded server binaries from assets...")
val success = updateRepo.ensureServerInstalled()
if (!success) {
LogBuffer.append("[Error] Failed to install server binaries from assets.")
_serverStatus.value = ServerStatus.ERROR
stopForegroundCompat()
stopSelf()
return@launch
}
LogBuffer.append("[Setup] Server binaries installed successfully.")
}
val ppsrFile = File(filesDir, "server/build/build/ppsr.pyc")
if (!ppsrFile.exists()) {
LogBuffer.append("[Error] Server bytecode missing at: ${ppsrFile.absolutePath}")
_serverStatus.value = ServerStatus.ERROR
stopForegroundCompat()
stopSelf()
return@launch
}
LogBuffer.append("[Setup] Server binaries installed successfully.")
}
val ppsrFile = File(filesDir, "server/build/build/ppsr.pyc")
if (!ppsrFile.exists()) {
LogBuffer.append("[Error] Server bytecode missing at: ${ppsrFile.absolutePath}")
try {
val persistentRepo = PersistentRepository(this@ServerService)
persistentRepo.ensureValidPersistentJsonSync()
} catch (e: Throwable) {
LogBuffer.append("[Warning] Persistent validation: ${e.message}")
}
LogBuffer.append("[System] Launching embedded GaCy Python server...")
// Background TCP port readiness probe
probeJob?.cancel()
probeJob = serviceScope.launch {
var attempts = 0
while (isActive && _serverStatus.value == ServerStatus.STARTING && attempts < 35) {
delay(1000)
attempts++
if (isPortOpen("127.0.0.1", dispatchPort)) {
_serverStatus.value = ServerStatus.RUNNING
updateNotification("GaCy Server is RUNNING on :$dispatchPort")
LogBuffer.append("[System] GaCy Dispatch server is ready on 127.0.0.1:$dispatchPort")
break
}
}
}
serverThread = Thread({
try {
if (!Python.isStarted()) {
Python.start(AndroidPlatform(applicationContext))
}
val py = Python.getInstance()
val runPpsr = py.getModule("run_ppsr")
val logReceiver = ServerLogReceiver()
runPpsr.callAttr("run_server", ppsrFile.absolutePath, logReceiver)
} catch (t: Throwable) {
LogBuffer.append("[Python Error] ${t.localizedMessage ?: t.message}")
t.printStackTrace()
} finally {
probeJob?.cancel()
_serverStatus.value = ServerStatus.STOPPED
LogBuffer.append("[System] Server stopped.")
stopForegroundCompat()
stopSelf()
}
}, "GaCy-PythonServer").apply {
start()
}
} catch (t: Throwable) {
LogBuffer.append("[Execution Error] ${t.localizedMessage ?: t.message}")
t.printStackTrace()
_serverStatus.value = ServerStatus.ERROR
stopForegroundCompat()
stopSelf()
return@launch
}
try {
val persistentRepo = PersistentRepository(this@ServerService)
persistentRepo.ensureValidPersistentJsonSync()
} catch (e: Exception) {
LogBuffer.append("[Warning] Persistent validation: ${e.message}")
}
LogBuffer.append("[System] Launching embedded GaCy Python server...")
// Background TCP port readiness probe
probeJob?.cancel()
probeJob = lifecycleScope.launch(Dispatchers.IO) {
var attempts = 0
while (isActive && _serverStatus.value == ServerStatus.STARTING && attempts < 35) {
delay(1000)
attempts++
if (isPortOpen("127.0.0.1", dispatchPort)) {
_serverStatus.value = ServerStatus.RUNNING
updateNotification("GaCy Server is RUNNING on :$dispatchPort")
LogBuffer.append("[System] GaCy Dispatch server is ready on 127.0.0.1:$dispatchPort")
break
}
}
}
serverThread = Thread({
try {
val py = Python.getInstance()
val runPpsr = py.getModule("run_ppsr")
val logReceiver = ServerLogReceiver()
runPpsr.callAttr("run_server", ppsrFile.absolutePath, logReceiver)
} catch (e: Exception) {
LogBuffer.append("[Python Error] ${e.localizedMessage ?: e.message}")
} finally {
probeJob?.cancel()
_serverStatus.value = ServerStatus.STOPPED
LogBuffer.append("[System] Server stopped.")
stopForegroundCompat()
stopSelf()
}
}, "GaCy-PythonServer").apply {
start()
}
}
}
private fun startRemoteMonitor(remoteHost: String, dispatchPort: Int) {
serverJob = lifecycleScope.launch(Dispatchers.IO) {
executionJob?.cancel()
executionJob = serviceScope.launch {
LogBuffer.append("[Remote] Connecting to remote GaCy host at $remoteHost:$dispatchPort...")
delay(600)
_serverStatus.value = ServerStatus.RUNNING
@@ -171,17 +200,17 @@ class ServerService : LifecycleService() {
probeJob?.cancel()
probeJob = null
serverJob?.cancel()
serverJob = null
executionJob?.cancel()
executionJob = null
lifecycleScope.launch(Dispatchers.IO) {
serviceScope.launch {
try {
if (Python.isStarted()) {
val py = Python.getInstance()
val runPpsr = py.getModule("run_ppsr")
runPpsr.callAttr("kill_server_process")
}
} catch (e: Exception) {
} catch (e: Throwable) {
LogBuffer.append("[Stop Error] ${e.localizedMessage ?: e.message}")
}
@@ -197,7 +226,7 @@ class ServerService : LifecycleService() {
private fun restartServer() {
LogBuffer.append("Restarting GaCy Server...")
stopServer()
lifecycleScope.launch {
serviceScope.launch {
delay(1500)
startServer()
}
@@ -209,12 +238,29 @@ class ServerService : LifecycleService() {
socket.connect(InetSocketAddress(host, port), 400)
true
}
} catch (_: Exception) {
} catch (_: Throwable) {
false
}
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
GaCyApplication.CHANNEL_ID,
GaCyApplication.CHANNEL_NAME,
NotificationManager.IMPORTANCE_LOW
).apply {
description = "Background service for GaCy Game Server & Proxy"
setShowBadge(false)
}
val manager = getSystemService(NotificationManager::class.java)
manager?.createNotificationChannel(channel)
}
}
private fun createNotification(contentText: String): Notification {
createNotificationChannel()
val launchIntent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
@@ -238,7 +284,7 @@ class ServerService : LifecycleService() {
return NotificationCompat.Builder(this, GaCyApplication.CHANNEL_ID)
.setContentTitle("GaCy Android")
.setContentText(contentText)
.setSmallIcon(R.mipmap.ic_launcher)
.setSmallIcon(R.drawable.app_icon)
.setOngoing(true)
.setContentIntent(pendingIntent)
.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopPendingIntent)
@@ -246,45 +292,55 @@ class ServerService : LifecycleService() {
}
private fun startForegroundCompat(text: String) {
val notification = createNotification(text)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
)
} else {
startForeground(NOTIFICATION_ID, notification)
try {
createNotificationChannel()
val notification = createNotification(text)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // API 34+
startForeground(
NOTIFICATION_ID,
notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
)
} else {
// API 26..33
startForeground(NOTIFICATION_ID, notification)
}
} catch (e: Throwable) {
try {
val notification = createNotification(text)
startForeground(NOTIFICATION_ID, notification)
} catch (e2: Throwable) {
LogBuffer.append("[Foreground Warning] ${e2.message}")
}
}
}
private fun updateNotification(contentText: String) {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
manager.notify(NOTIFICATION_ID, createNotification(contentText))
try {
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
manager?.notify(NOTIFICATION_ID, createNotification(contentText))
} catch (_: Throwable) {}
}
private fun stopForegroundCompat() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_REMOVE)
} else {
@Suppress("DEPRECATION")
stopForeground(true)
}
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
stopForeground(STOP_FOREGROUND_REMOVE)
} else {
@Suppress("DEPRECATION")
stopForeground(true)
}
} catch (_: Throwable) {}
}
override fun onDestroy() {
probeJob?.cancel()
serverThread?.interrupt()
serverJob?.cancel()
executionJob?.cancel()
super.onDestroy()
_serverStatus.value = ServerStatus.STOPPED
}
override fun onBind(intent: Intent): IBinder? {
super.onBind(intent)
return null
}
companion object {
const val ACTION_START = "com.gacy.android.ACTION_START"
const val ACTION_STOP = "com.gacy.android.ACTION_STOP"
@@ -296,24 +352,44 @@ class ServerService : LifecycleService() {
fun start(context: Context) {
val intent = Intent(context, ServerService::class.java).apply { action = ACTION_START }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
} catch (t: Throwable) {
try {
context.startService(intent)
} catch (t2: Throwable) {
LogBuffer.append("[Service Start Error] ${t2.message}")
}
}
}
fun stop(context: Context) {
val intent = Intent(context, ServerService::class.java).apply { action = ACTION_STOP }
context.startService(intent)
try {
context.startService(intent)
} catch (t: Throwable) {
LogBuffer.append("[Service Stop Error] ${t.message}")
}
}
fun restart(context: Context) {
val intent = Intent(context, ServerService::class.java).apply { action = ACTION_RESTART }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
context.startService(intent)
}
} catch (t: Throwable) {
try {
context.startService(intent)
} catch (t2: Throwable) {
LogBuffer.append("[Service Restart Error] ${t2.message}")
}
}
}
}
-8
View File
@@ -476,14 +476,6 @@ def run_server(ppsr_path, activity_context=None):
except Exception as e:
print(f"[Patch Warning] Router crash-guard: {e}")
# --- License / Access Check Bypass Patch ---
try:
import util.binary as _bin_mod
_bin_mod.qyqwiourtyqot = lambda *args, **kwargs: True
print("[Patch] util.binary access check bypassed successfully")
except Exception as e:
print(f"[Patch Warning] Binary access check patch: {e}")
print(f"Loading bytecode target: {ppsr_path}...")
with open(ppsr_path, "rb") as f: