fix: resolve service crash on start by fixing foreground service type check and notification icon
This commit is contained in:
1 parent
1f88f6ea38
commit
8fc94b12d5
3 files changed
+107
-35
No files matched your search
@@ -22,9 +22,13 @@ class GaCyApplication : Application() {
|
|||||||
createNotificationChannel()
|
createNotificationChannel()
|
||||||
|
|
||||||
// Initialize embedded Python runtime
|
// Initialize embedded Python runtime
|
||||||
|
try {
|
||||||
if (!Python.isStarted()) {
|
if (!Python.isStarted()) {
|
||||||
Python.start(AndroidPlatform(this))
|
Python.start(AndroidPlatform(this))
|
||||||
}
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
t.printStackTrace()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createNotificationChannel() {
|
private fun createNotificationChannel() {
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
package com.gacy.android.service
|
package com.gacy.android.service
|
||||||
|
|
||||||
import android.app.Notification
|
import android.app.Notification
|
||||||
|
import android.app.NotificationChannel
|
||||||
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ServiceInfo
|
import android.content.pm.ServiceInfo
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.lifecycle.LifecycleService
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.chaquo.python.Python
|
import com.chaquo.python.Python
|
||||||
|
import com.chaquo.python.android.AndroidPlatform
|
||||||
import com.gacy.android.GaCyApplication
|
import com.gacy.android.GaCyApplication
|
||||||
import com.gacy.android.MainActivity
|
import com.gacy.android.MainActivity
|
||||||
import com.gacy.android.R
|
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.data.UpdateRepository
|
||||||
import com.gacy.android.model.ServerExecutionMode
|
import com.gacy.android.model.ServerExecutionMode
|
||||||
import com.gacy.android.model.ServerStatus
|
import com.gacy.android.model.ServerStatus
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
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 serverThread: Thread? = null
|
||||||
private var probeJob: Job? = 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() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
createNotificationChannel()
|
||||||
_serverStatus.value = ServerStatus.STOPPED
|
_serverStatus.value = ServerStatus.STOPPED
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
super.onStartCommand(intent, flags, startId)
|
|
||||||
val action = intent?.action ?: ACTION_START
|
val action = intent?.action ?: ACTION_START
|
||||||
|
|
||||||
when (action) {
|
when (action) {
|
||||||
@@ -66,6 +73,7 @@ class ServerService : LifecycleService() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
val prefs = PreferencesRepository(this)
|
val prefs = PreferencesRepository(this)
|
||||||
val config = prefs.getServerConfig()
|
val config = prefs.getServerConfig()
|
||||||
|
|
||||||
@@ -80,10 +88,19 @@ class ServerService : LifecycleService() {
|
|||||||
} else {
|
} else {
|
||||||
startRemoteMonitor(config.remoteHost, config.dispatchPort)
|
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) {
|
private fun startLocalServerExecution(dispatchPort: Int) {
|
||||||
serverJob = lifecycleScope.launch(Dispatchers.IO) {
|
executionJob?.cancel()
|
||||||
|
executionJob = serviceScope.launch {
|
||||||
|
try {
|
||||||
val updateRepo = UpdateRepository(this@ServerService)
|
val updateRepo = UpdateRepository(this@ServerService)
|
||||||
if (!updateRepo.isServerInstalled()) {
|
if (!updateRepo.isServerInstalled()) {
|
||||||
LogBuffer.append("[Setup] Installing GaCy embedded server binaries from assets...")
|
LogBuffer.append("[Setup] Installing GaCy embedded server binaries from assets...")
|
||||||
@@ -110,7 +127,7 @@ class ServerService : LifecycleService() {
|
|||||||
try {
|
try {
|
||||||
val persistentRepo = PersistentRepository(this@ServerService)
|
val persistentRepo = PersistentRepository(this@ServerService)
|
||||||
persistentRepo.ensureValidPersistentJsonSync()
|
persistentRepo.ensureValidPersistentJsonSync()
|
||||||
} catch (e: Exception) {
|
} catch (e: Throwable) {
|
||||||
LogBuffer.append("[Warning] Persistent validation: ${e.message}")
|
LogBuffer.append("[Warning] Persistent validation: ${e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +135,7 @@ class ServerService : LifecycleService() {
|
|||||||
|
|
||||||
// Background TCP port readiness probe
|
// Background TCP port readiness probe
|
||||||
probeJob?.cancel()
|
probeJob?.cancel()
|
||||||
probeJob = lifecycleScope.launch(Dispatchers.IO) {
|
probeJob = serviceScope.launch {
|
||||||
var attempts = 0
|
var attempts = 0
|
||||||
while (isActive && _serverStatus.value == ServerStatus.STARTING && attempts < 35) {
|
while (isActive && _serverStatus.value == ServerStatus.STARTING && attempts < 35) {
|
||||||
delay(1000)
|
delay(1000)
|
||||||
@@ -134,12 +151,16 @@ class ServerService : LifecycleService() {
|
|||||||
|
|
||||||
serverThread = Thread({
|
serverThread = Thread({
|
||||||
try {
|
try {
|
||||||
|
if (!Python.isStarted()) {
|
||||||
|
Python.start(AndroidPlatform(applicationContext))
|
||||||
|
}
|
||||||
val py = Python.getInstance()
|
val py = Python.getInstance()
|
||||||
val runPpsr = py.getModule("run_ppsr")
|
val runPpsr = py.getModule("run_ppsr")
|
||||||
val logReceiver = ServerLogReceiver()
|
val logReceiver = ServerLogReceiver()
|
||||||
runPpsr.callAttr("run_server", ppsrFile.absolutePath, logReceiver)
|
runPpsr.callAttr("run_server", ppsrFile.absolutePath, logReceiver)
|
||||||
} catch (e: Exception) {
|
} catch (t: Throwable) {
|
||||||
LogBuffer.append("[Python Error] ${e.localizedMessage ?: e.message}")
|
LogBuffer.append("[Python Error] ${t.localizedMessage ?: t.message}")
|
||||||
|
t.printStackTrace()
|
||||||
} finally {
|
} finally {
|
||||||
probeJob?.cancel()
|
probeJob?.cancel()
|
||||||
_serverStatus.value = ServerStatus.STOPPED
|
_serverStatus.value = ServerStatus.STOPPED
|
||||||
@@ -150,11 +171,19 @@ class ServerService : LifecycleService() {
|
|||||||
}, "GaCy-PythonServer").apply {
|
}, "GaCy-PythonServer").apply {
|
||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
LogBuffer.append("[Execution Error] ${t.localizedMessage ?: t.message}")
|
||||||
|
t.printStackTrace()
|
||||||
|
_serverStatus.value = ServerStatus.ERROR
|
||||||
|
stopForegroundCompat()
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startRemoteMonitor(remoteHost: String, dispatchPort: Int) {
|
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...")
|
LogBuffer.append("[Remote] Connecting to remote GaCy host at $remoteHost:$dispatchPort...")
|
||||||
delay(600)
|
delay(600)
|
||||||
_serverStatus.value = ServerStatus.RUNNING
|
_serverStatus.value = ServerStatus.RUNNING
|
||||||
@@ -171,17 +200,17 @@ class ServerService : LifecycleService() {
|
|||||||
probeJob?.cancel()
|
probeJob?.cancel()
|
||||||
probeJob = null
|
probeJob = null
|
||||||
|
|
||||||
serverJob?.cancel()
|
executionJob?.cancel()
|
||||||
serverJob = null
|
executionJob = null
|
||||||
|
|
||||||
lifecycleScope.launch(Dispatchers.IO) {
|
serviceScope.launch {
|
||||||
try {
|
try {
|
||||||
if (Python.isStarted()) {
|
if (Python.isStarted()) {
|
||||||
val py = Python.getInstance()
|
val py = Python.getInstance()
|
||||||
val runPpsr = py.getModule("run_ppsr")
|
val runPpsr = py.getModule("run_ppsr")
|
||||||
runPpsr.callAttr("kill_server_process")
|
runPpsr.callAttr("kill_server_process")
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Throwable) {
|
||||||
LogBuffer.append("[Stop Error] ${e.localizedMessage ?: e.message}")
|
LogBuffer.append("[Stop Error] ${e.localizedMessage ?: e.message}")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +226,7 @@ class ServerService : LifecycleService() {
|
|||||||
private fun restartServer() {
|
private fun restartServer() {
|
||||||
LogBuffer.append("Restarting GaCy Server...")
|
LogBuffer.append("Restarting GaCy Server...")
|
||||||
stopServer()
|
stopServer()
|
||||||
lifecycleScope.launch {
|
serviceScope.launch {
|
||||||
delay(1500)
|
delay(1500)
|
||||||
startServer()
|
startServer()
|
||||||
}
|
}
|
||||||
@@ -209,12 +238,29 @@ class ServerService : LifecycleService() {
|
|||||||
socket.connect(InetSocketAddress(host, port), 400)
|
socket.connect(InetSocketAddress(host, port), 400)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
} catch (_: Exception) {
|
} catch (_: Throwable) {
|
||||||
false
|
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 {
|
private fun createNotification(contentText: String): Notification {
|
||||||
|
createNotificationChannel()
|
||||||
|
|
||||||
val launchIntent = Intent(this, MainActivity::class.java).apply {
|
val launchIntent = Intent(this, MainActivity::class.java).apply {
|
||||||
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
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)
|
return NotificationCompat.Builder(this, GaCyApplication.CHANNEL_ID)
|
||||||
.setContentTitle("GaCy Android")
|
.setContentTitle("GaCy Android")
|
||||||
.setContentText(contentText)
|
.setContentText(contentText)
|
||||||
.setSmallIcon(R.mipmap.ic_launcher)
|
.setSmallIcon(R.drawable.app_icon)
|
||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopPendingIntent)
|
.addAction(android.R.drawable.ic_menu_close_clear_cancel, "Stop", stopPendingIntent)
|
||||||
@@ -246,45 +292,55 @@ class ServerService : LifecycleService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun startForegroundCompat(text: String) {
|
private fun startForegroundCompat(text: String) {
|
||||||
|
try {
|
||||||
|
createNotificationChannel()
|
||||||
val notification = createNotification(text)
|
val notification = createNotification(text)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { // API 34+
|
||||||
startForeground(
|
startForeground(
|
||||||
NOTIFICATION_ID,
|
NOTIFICATION_ID,
|
||||||
notification,
|
notification,
|
||||||
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
// API 26..33
|
||||||
startForeground(NOTIFICATION_ID, notification)
|
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) {
|
private fun updateNotification(contentText: String) {
|
||||||
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as android.app.NotificationManager
|
try {
|
||||||
manager.notify(NOTIFICATION_ID, createNotification(contentText))
|
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as? NotificationManager
|
||||||
|
manager?.notify(NOTIFICATION_ID, createNotification(contentText))
|
||||||
|
} catch (_: Throwable) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopForegroundCompat() {
|
private fun stopForegroundCompat() {
|
||||||
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
stopForeground(STOP_FOREGROUND_REMOVE)
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
stopForeground(true)
|
stopForeground(true)
|
||||||
}
|
}
|
||||||
|
} catch (_: Throwable) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
probeJob?.cancel()
|
probeJob?.cancel()
|
||||||
serverThread?.interrupt()
|
serverThread?.interrupt()
|
||||||
serverJob?.cancel()
|
executionJob?.cancel()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
_serverStatus.value = ServerStatus.STOPPED
|
_serverStatus.value = ServerStatus.STOPPED
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBind(intent: Intent): IBinder? {
|
|
||||||
super.onBind(intent)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val ACTION_START = "com.gacy.android.ACTION_START"
|
const val ACTION_START = "com.gacy.android.ACTION_START"
|
||||||
const val ACTION_STOP = "com.gacy.android.ACTION_STOP"
|
const val ACTION_STOP = "com.gacy.android.ACTION_STOP"
|
||||||
@@ -296,25 +352,45 @@ class ServerService : LifecycleService() {
|
|||||||
|
|
||||||
fun start(context: Context) {
|
fun start(context: Context) {
|
||||||
val intent = Intent(context, ServerService::class.java).apply { action = ACTION_START }
|
val intent = Intent(context, ServerService::class.java).apply { action = ACTION_START }
|
||||||
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
context.startForegroundService(intent)
|
context.startForegroundService(intent)
|
||||||
} else {
|
} else {
|
||||||
context.startService(intent)
|
context.startService(intent)
|
||||||
}
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
try {
|
||||||
|
context.startService(intent)
|
||||||
|
} catch (t2: Throwable) {
|
||||||
|
LogBuffer.append("[Service Start Error] ${t2.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop(context: Context) {
|
fun stop(context: Context) {
|
||||||
val intent = Intent(context, ServerService::class.java).apply { action = ACTION_STOP }
|
val intent = Intent(context, ServerService::class.java).apply { action = ACTION_STOP }
|
||||||
|
try {
|
||||||
context.startService(intent)
|
context.startService(intent)
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
LogBuffer.append("[Service Stop Error] ${t.message}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun restart(context: Context) {
|
fun restart(context: Context) {
|
||||||
val intent = Intent(context, ServerService::class.java).apply { action = ACTION_RESTART }
|
val intent = Intent(context, ServerService::class.java).apply { action = ACTION_RESTART }
|
||||||
|
try {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
context.startForegroundService(intent)
|
context.startForegroundService(intent)
|
||||||
} else {
|
} else {
|
||||||
context.startService(intent)
|
context.startService(intent)
|
||||||
}
|
}
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
try {
|
||||||
|
context.startService(intent)
|
||||||
|
} catch (t2: Throwable) {
|
||||||
|
LogBuffer.append("[Service Restart Error] ${t2.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,14 +476,6 @@ def run_server(ppsr_path, activity_context=None):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"[Patch Warning] Router crash-guard: {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}...")
|
print(f"Loading bytecode target: {ppsr_path}...")
|
||||||
|
|
||||||
with open(ppsr_path, "rb") as f:
|
with open(ppsr_path, "rb") as f:
|
||||||
|
|||||||
Reference in new issue
Block a user