How to make socket io work in the background android - android

I have socket IO connected to an android app and it's receiving events via background services. while this is working fine when the app is in foreground. When I close the app or when app goes in background I have confirmed that the sockets are connected but I'm not able to receive events when app is in background.
This is my service
const val SOCKET_EVENT = "message:response"
#AndroidEntryPoint
class MService : LifecycleService() {
#Inject
lateinit var ioSocket: Socket
override fun onCreate() {
super.onCreate()
ioSocket.connect()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
ioSocket.connect()
AndroidRemoteDebugger.Log.d("Starting My Service")
listenToSocketsEvents()
return START_STICKY
}
private fun listenToSocketsEvents() {
ioSocket.on(SOCKET_EVENT, onSocketEvent)
}
private val onSocketEvent: Emitter.Listener = object : Emitter.Listener {
override fun call(vararg args: Any?) {
try {
val data = args[0] as JSONObject
//While the main thing about the service is that I'm logging data jhere when I get an
// incoming socket event it does log when app is in foreground but when it's in
// background it doesn't log
AndroidRemoteDebugger.Log.v(data.toString())
val message = data.getJSONObject("message")
val id = message.getString("id")
playSound()
val topic = "message-request"
val intent = Intent(
this#MService,
MessageReceiver::class.java
).apply {
putExtra("messageId", id)
putExtra("topic", topic)
}
sendBroadcast(intent)
} catch (e: JSONException) {
AndroidRemoteDebugger.Log.e(e.message)
return
}
}
}
private fun playSound() {
val notificationSound: Uri =
Uri.parse(
(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
packageName) + "/" + R.raw.car_horn
)
val ringtone: Ringtone = RingtoneManager.getRingtone(
applicationContext,
notificationSound
)
ringtone.play()
}
}
This is my receiver
class MessageReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
val topic = intent?.getStringExtra("topic")
val tripId = intent?.getStringExtra("tripId")
val intent = Intent(Constants.NOTIFICATION).apply {
putExtra("Notify", topic)
putExtra("tripId", tripId)
putExtra("from", Constants.SOCKETS)
}
if (context != null) {
LocalBroadcastManager.getInstance(context).sendBroadcast(intent)
}
}
}
While the main thing about the service is that I'm logging something when I get an incoming socket event it does log when app is in foreground but when it's in background it doesn't log

LifecycleService will be killed when the app goes in the background/killed.
You should use ForegroundService to run any background tasks.
From official doc
While an app is in the foreground, it can create and run both foreground and background services freely. When an app goes into the background, it has a window of several minutes in which it is still allowed to create and use services. At the end of that window, the app is considered to be idle. At this time, the system stops the app's background services, just as if the app had called the services' Service.stopSelf() methods.
Your sockets are still connected because you don't disconnect the socket when the service gets killed. Always disconnect the socket when you're done with the service to avoid unnecessary resource consumption.

Related

How to get the name of an app when it opens on Android?

I need to get the name of any Android app when it opens and launch a password window. I have searched everywhere how to find out the name of the app which the user has opened but I have found no working solution yet. I have a service which I'm running on the background, but it only returns my app's name or the home screen name, no matter which app I open.
Here is my service code:
package com.example.applock
import android.app.ActivityManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.IBinder
import android.provider.Settings
class BackAppListenerService : Service() {
private var isRunning = false
private var lastApp = ""
override fun onCreate() {
println(TAG + "Service onCreate")
isRunning = true
val intent = Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
println(TAG + "Service onStartCommand")
//Creating new thread for my service
//Always write your long running tasks in a separate thread, to avoid ANR
Thread(Runnable { //Your logic that service will perform will be placed here
//In this example we are just looping and waits for 1000 milliseconds in each loop.
while (true) {
try {
Thread.sleep(1000)
} catch (e: Exception) {
}
val mActivityManager = this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val mPackageName = mActivityManager.runningAppProcesses[0].processName
println(mPackageName)
}
}).start()
return START_STICKY
}
override fun onBind(arg0: Intent): IBinder? {
println(TAG + "Service onBind")
return null
}
override fun onDestroy() {
isRunning = false
println(TAG + "Service onDestroy")
}
companion object {
private const val TAG = "HelloService"
}
}
Ok, so I have figured it out.
First add this to your manifest.xml under the manifest tag:
<uses-permission
android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions"/>
Then go to apps permissions > Usage Stats go to your app and enable it. You can also make a pop up asking for the user to do it once the apps loads.
Now, to get name of the current foreground app:
fun getForegroundApp(): String {
var currentApp = "NULL"
// You can delete the if-else statement if you don't care about Android versions
// lower than 5.0. Just keep the code that is inside the if and delete the one
// inside the else statement.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val usm = this.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val time = System.currentTimeMillis()
val appList =
usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 1000, time)
if (appList != null && appList.size > 0) {
val mySortedMap: SortedMap<Long, UsageStats> =
TreeMap<Long, UsageStats>()
for (usageStats in appList) {
mySortedMap.put(usageStats.lastTimeUsed, usageStats)
}
if (mySortedMap != null && !mySortedMap.isEmpty()) {
currentApp = mySortedMap.get(mySortedMap.lastKey())!!.getPackageName()
}
}
} else {
val am = this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val tasks = am.runningAppProcesses
currentApp = tasks[0].processName
}
// Get only the app name name
return currentApp.split(".").last()
}
Sometimes the app name isn't the one displayed, for example the "gmail" app has the name "gm" when I tested it. The home screen name also changes from device to device
The logic to call it every few milliseconds to get the current foreground app is simple:
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
//Creating new thread for my service
//Always write your long running tasks in a separate thread, to avoid ANR
Thread(Runnable {
while (true) {
try {
Thread.sleep(10)
} catch (e: Exception) {
}
val currentApp = getForegroundApp()
if (currentApp != lastApp) {
println(currentApp)
// New app on front
lastApp = currentApp
println(currentApp)
// Do whatever you wan
}
}
}).start()
return START_STICKY
}
And that's it.

Does Android force a destroy of a Service after 1 minute if the controlling application is running in background?

It seems if I start a Service from an app but do not call startForeground() in the onStartCommand method, and I then put the app in background, after one minute the onDestroy callback is called and the Service is destroyed.
I have not seen this stated anywhere in the Android documentation. Rather, the documentation says vaguely that eventually this kind of Service will be destroyed.
Notice that the onStartCommand method returns START_STICKY.
Any suggestions would be appreciated.
Code for the Service follows; it is meant to explore the Service capability, not as an actual production app. Also, programming sometimes does the same thing different ways; I have been trying different approaches to get more insight. And I am pretty new to Android; regret naively written code.
class IntegerCounting : Service() {
companion object {
var theCounter : Int = 0
}
private lateinit var mHandler: Handler
private lateinit var mRunnable: Runnable
override fun onBind(intent: Intent): IBinder? {
throw UnsupportedOperationException("Not yet implemented")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// Send a notification that service is started
Toast.makeText(getApplicationContext(), "Service started.", Toast.LENGTH_SHORT).show()
// Do a periodic task
mHandler = Handler()
mRunnable = Runnable { CountByOne() }
mHandler.postDelayed(mRunnable, 500)
return Service.START_STICKY
}
override fun onDestroy() {
super.onDestroy()
Toast.makeText(getApplicationContext(), "Service destroyed.", Toast.LENGTH_SHORT).show()
mHandler.removeCallbacks(mRunnable)
}
override fun onTaskRemoved(rootIntent: Intent?) {
Toast.makeText(getApplicationContext(), "On Task Removed called", Toast.LENGTH_SHORT).show()
}
// Custom method to do a task
private fun CountByOne() {
++theCounter
if (theCounter % 10 == 0) {
Toast.makeText(getApplicationContext(), "count by one " + theCounter.toString(), Toast.LENGTH_SHORT).show()
}
//showSomeText(theCounter.toString())
mHandler.postDelayed(mRunnable, 500)
}
}
The MainActivity class follows:
class MainActivity : AppCompatActivity() {
// Here is how you find out if an Android service is running
private fun isServiceRunning(serviceClass: Class<*>): Boolean {
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
// Loop through the running services
for (service in activityManager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.name == service.service.className) {
// If the service is running then return true
return true
}
}
return false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Variable to hold service class name
val serviceClass = IntegerCounting::class.java
// Notice the service class is defined here, so subsequently all
// we need to access this service is this instance of Intent
val intent = Intent(applicationContext, serviceClass)
counterText = findViewById(R.id.counter)
counterText.text = "Ready to start counting"
val startButton: Button = findViewById(R.id.start_counting)
val stopButton: Button = findViewById(R.id.stop_counting)
val showStatus: Button = findViewById(R.id.show_status)
startButton.setOnClickListener({
if (!isServiceRunning(serviceClass)) {
counterText.text = "Starting Integer Counter service"
startService(intent)
} else {
counterText.text = "Integer Counter service already running"
}
})
stopButton.setOnClickListener {
if (isServiceRunning(serviceClass)) {
counterText.text = "Stopping Integer Counter service"
stopService(intent)
} else {
counterText.text = "Integer Counter service already stopped"
}
}
showStatus.setOnClickListener({
if (isServiceRunning(serviceClass)) {
counterText.text = "Integer Counter service is running, counter: " +
IntegerCounting.theCounter.toString()
} else {
counterText.text = "Integer Counter service is stopped, counter " +
IntegerCounting.theCounter.toString()
}
})
}
}
If your app target larger than android O it's normal behaviors when your app go background. To keeping Service try to use startForceground with Notification
While an app is in the foreground, it can create and run both foreground and background services freely. When an app goes into the background, it has a window of several minutes in which it is still allowed to create and use services. At the end of that window, the app is considered to be idle. At this time, the system stops the app's background services, just as if the app had called the services' Service.stopSelf() methods.
Documents here

Context.startForegroundService() did not then call Service.startForeground() - still a problem

After checking the google issue and a lot of other issues on SO, I came to the solution I added at the bottom.
What I take care of is following:
in onCreate AND in onStartCommand I make sure to move the service to the foregorund (if it is not already in the foreground)
I don't simply stop a service, I do this with an extra command that I send to the onStartCommand handler to make sure, that the service is not stopped while it's started (eventually before it could finish going to the foreground)
I never stop the service directly (context.stopService(...)), I always stop the service via a command from the running service itself - so I make sure that it can only be stopped while it is running in the foreground and not during start up
a service can only be stopped once
I stop a service by cancelling it's foreground notification and only afterwards I stop the service itself
I personally use the service for overlays, so I do not handle binders inside my class, as I do not use them.
Log I got
2019-07-29 21:41:27,146 [[BaseOverlayService:62 onCreate]]: onCreate
2019-07-29 21:41:27,146 [[BaseOverlayService:142 b]]: BEFORE moveToForeground (called by onCreate)
2019-07-29 21:41:27,152 [[BaseOverlayService:159 b]]: AFTER moveToForeground (called by onCreate) - moved to foreground: true
2019-07-29 21:41:27,176 [[BaseOverlayService:79 onStartCommand]]: onStartCommand: isForeground: true | action: null | isStopping: false
2019-07-29 21:41:27,945 [[BaseOverlayService:142 b]]: BEFORE moveToForeground (called by updateNotification [OverlayService [onInitFinished]])
2019-07-29 21:41:27,947 [[BaseOverlayService:159 b]]: AFTER moveToForeground (called by updateNotification [OverlayService [onInitFinished]]) - moved to foreground: false
This is the log of one crash report - as you can see, service is moved to foreground in line 3 (moved to foreground: true) and in line 6 it knows that it is running in foreground already.
I use this app on my android 9 device heavily (24/7, it's constantly running) and don't have problems and since I use the base class from below the problem has really minimized itself to a few crashes a month in total. Still, the log above shows, that my service is running in foreground within milliseconds, still it can crash like following:
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{86fa711 u0 com.my.app/com.my.app.service.OverlayService}
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1855)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:6986)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1445)
Does anyone see any issues with my base class?
Code
abstract class BaseOverlayService<T : BaseOverlayService<T>>(
val foregroundNotificationId: Int,
val notificationCreator: ((service: T) -> Notification)
) : Service() {
companion object {
val DEBUG = true
// helper function, simply checks if this service is already running by checking the ActivityManager
inline fun <reified T : BaseOverlayService<T>> isRunning(context: Context): Boolean {
return Tools.isServiceRunning(context, T::class.java)
}
inline fun <reified T : BaseOverlayService<T>> start(context: Context, checkIfServiceIsRunning: Boolean) {
if (checkIfServiceIsRunning && isRunning<T>(context)) {
L.logIf { DEBUG }?.d { "IGNORED start intent" }
return
}
L.logIf { DEBUG }?.d { "send start intent" }
val intent = Intent(context, T::class.java)
ContextCompat.startForegroundService(context, intent)
}
inline fun <reified T : BaseOverlayService<T>> sendAction(context: Context, checkIfServiceIsRunning: Boolean, action: String, intentUpdater: ((Intent) -> Unit) = {}) {
if (checkIfServiceIsRunning && !isRunning<T>(context)) {
L.logIf { DEBUG }?.d { "IGNORED action intent - action: $action" }
return
}
L.logIf { DEBUG }?.d { "send action intent - action: $action" }
val intent = Intent(context, T::class.java)
intent.action = action
intentUpdater(intent)
ContextCompat.startForegroundService(context, intent)
}
}
protected var isForeground = false
private set
protected var isStopping: Boolean = false
private set
// ------------------------
// service events
// ------------------------
final override fun onCreate() {
L.logIf { DEBUG }?.d { "onCreate" }
super.onCreate()
if (foregroundNotificationId <= 0) {
throw RuntimeException("foregroundNotificationId must be > 0!")
}
moveToForeground("onCreate")
onCreateEvent()
}
final override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val returnValue = START_STICKY
L.logIf { DEBUG }?.d { "onStartCommand: isForeground: $isForeground | action: ${intent?.action} | isStopping: $isStopping" }
// 1) if service is stopping, we ignore the event
if (isStopping) {
return returnValue
}
// 2) if service is not running in foreground we make it run in the foreground
if (!isForeground) {
moveToForeground("onStartCommand")
}
onStartCommandEvent(intent, flags, startId)
return returnValue
}
final override fun onBind(intent: Intent): IBinder? {
// overlay service is never bound!
return null
}
// ------------------------
// Forwarded abstract events
// ------------------------
abstract fun onCreateEvent()
abstract fun onStartCommandEvent(intent: Intent?, flags: Int, startId: Int)
abstract fun onStopEvent()
// ------------------------
// protected functions
// ------------------------
protected fun stopService() {
L.logIf { DEBUG }?.d { "stopService | isStopping: $isStopping" }
if (isStopping) {
L.logIf { DEBUG }?.d { "IGNORED stopService" }
return
}
onStopEvent()
isStopping = true
moveToBackground(true)
stopSelf()
L.logIf { DEBUG }?.d { "stopService finished" }
}
protected fun updateNotification(caller: String) {
moveToForeground("updateNotification [$caller]")
}
// ------------------------
// private foreground/background functions
// ------------------------
private fun moveToForeground(caller: String): Boolean {
L.logIf { DEBUG }?.d { "BEFORE moveToForeground (called by $caller)" }
// 1) Create notification
val notification = notificationCreator(this as T)
// 2.1) Create foreground notification
val result = if (!isForeground) {
isForeground = true
startForeground(foregroundNotificationId, notification)
true
}
// 2.2) Update foreground notification
else {
notificationManager.notify(foregroundNotificationId, notification)
false
}
L.logIf { DEBUG }?.d { "AFTER moveToForeground (called by $caller) - moved to foreground: $result" }
return result
}
private fun moveToBackground(cancelNotification: Boolean) {
isForeground = false
super.stopForeground(cancelNotification)
}
// ------------------------
// private helper functions
// ------------------------
private val notificationManager by lazy {
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
}
I had the same issue and could solve it like this:
From Android 9 Pie if your service does not call startForeground within 5 seconds after it has been started with the command startForegroundService ... then it produces an ANR + Crash.
The solution is to add a startForeground() command, with your notification, right at the beginning of the onStartCommand method of your foreground service, like this:
final override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int
{
startForeground(YOUR_FOREGROUND_NOTIFICATION_ID, Notification);
// rest of your code here...
}

Nearby Messages using an IntentService

Initially I setup a BroadcastReceiver to receive intents from the Nearby Messages API.
class BeaconMessageReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Nearby.getMessagesClient(context).handleIntent(intent, object : MessageListener() {
override fun onFound(message: Message) {
val id = IBeaconId.from(message)
Timber.i("Found iBeacon=$id")
sendNotification(context, "Found iBeacon=$id")
}
override fun onLost(message: Message) {
val id = IBeaconId.from(message)
Timber.i("Lost iBeacon=$id")
sendNotification(context, "Lost iBeacon=$id")
}
})
}
private fun sendNotification(context: Context, text: String) {
Timber.d("Send notification.")
val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = NotificationCompat.Builder(context, Notifications.CHANNEL_GENERAL)
.setContentTitle("Beacons")
.setContentText(text)
.setSmallIcon(R.drawable.ic_notification_white)
.build()
manager.notify(NotificationIdGenerator.nextID(), notification)
}
}
Then registered this receiver in my MainActivity after location permissions have been granted.
class MainActivity : AppCompatActivity() {
// ...
private fun onLocationPermissionsGranted() {
val filter = MessageFilter.Builder()
.includeIBeaconIds(UUID.fromString("B9407F30-F5F8-466E-AFF9-25556B57FEED"), null, null)
.build()
val options = SubscribeOptions.Builder().setStrategy(Strategy.BLE_ONLY).setFilter(filter).build()
Nearby.getMessagesClient(context).subscribe(getPendingIntent(), options)
}
private fun getPendingIntent(): PendingIntent = PendingIntent.getBroadcast(
this, 0, Intent(context, BeaconMessageReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
}
This worked well while the app was open, but does not work when the app is closed. So I found this example, that demonstrates how to setup an IntentService to receive messages while the app is in the background.
The example does use the Nearby.Messages class, which was deprecated in favor of the MessagesClient. So I replaced the deprecated code with the MessagesClient implementation.
class MainActivity : AppCompatActivity() {
// ...
private fun onLocationPermissionsGranted() {
val filter = MessageFilter.Builder()
.includeIBeaconIds(UUID.fromString("B9407F30-F5F8-466E-AFF9-25556B57FEED"), null, null)
.build()
val options = SubscribeOptions.Builder().setStrategy(Strategy.BLE_ONLY).setFilter(filter).build()
Nearby.getMessagesClient(context).subscribe(getPendingIntent(), options)
.addOnSuccessListener {
Timber.i("Subscribed successfully.")
startService(Intent(this, BeaconMessageIntentService::class.java))
}.addOnFailureListener {
Timber.e(exception, "Subscription failed.")
}
}
private fun getPendingIntent(): PendingIntent = PendingIntent.getBroadcast(
this, 0, Intent(context, BeaconMessageIntentService::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
}
And this is the IntentService (which is almost identical to my BroadcastReceiver).
class BeaconMessageIntentService : IntentService("BeaconMessageIntentService") {
override fun onHandleIntent(intent: Intent?) {
intent?.let {
Nearby.getMessagesClient(this)
.handleIntent(it, object : MessageListener() {
override fun onFound(message: Message) {
val id = IBeaconId.from(message)
Timber.i("Found iBeacon=$id")
sendNotification("Found iBeacon=$id")
}
override fun onLost(message: Message) {
val id = IBeaconId.from(message)
Timber.i("Lost iBeacon=$id")
sendNotification("Lost iBeacon=$id")
}
})
}
}
private fun sendNotification(text: String) {
Timber.d("Send notification.")
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notification = NotificationCompat.Builder(this, Notifications.CHANNEL_GENERAL)
.setContentTitle("Beacons")
.setContentText(text)
.setSmallIcon(R.drawable.ic_notification_white)
.build()
manager.notify(NotificationIdGenerator.nextID(), notification)
}
}
onHandleIntent is called, and the Intent is not null; yet for some reason onFound() and onLost() are never called. Why would this be the case?
It's not really a solution but what I found is this (credit to this answer):
I've tried a few configurations including a BroadcastReceiver and adding a JobIntentService to run the code in the background, but every time I got this the onExpired callback which you can set to the SubscribeOptions:
options.setCallback(new SubscribeCallback() {
#Override
public void onExpired() {
super.onExpired();
Toast.makeText(context.get(), "No longer Subscribing!", Toast.LENGTH_SHORT).show();
}
}
When the subscribe occurred in the background it was delayed, but it was still called.
Notes:
1. When I've tested with Strategy.BLE_ONLY I did not get the onFound callback.
2. From Google's documentation:
Background subscriptions consumes less power than foreground
subscriptions, but have higher latency and lower reliability
When testing I found this "lower reliability" to be an understatement: onFound was rarely called and I never got the onLost.
I know this is a late reply, but I had the same problem and found out by debugging that it is an issue related to this error: "Attempting to perform a high-power operation from a non-Activity Context". This can be solved when calling Nearby.getMessagesClient(this) by passing in an activity context instead of this.
In my case I added a class extending Application which helps in returning this context (the below is in java but should be translatable to kotlin easily)
public class MyApplication extends Application {
private Activity currentActivity = null;
public Activity getCurrentActivity(){
return currentActivity;
}
public void setCurrentActivity(Activity mCurrentActivity){
this.currentActivity = mCurrentActivity;
}
}
And in my base activity, from which all activities extend, I set the current activity by calling ((MyApplication) this.getApplicationContext()).setCurrentActivity(this); in the constructor.
My service can then call getMessagesClient with the correct context like below:
final Activity context = ((MyApplication)getApplicationContext()).getCurrentActivity();
Nearby.getMessagesClient(context).[...]
Do not forget to register your Application class in the AndroidManifest:
<application
android:name="com.example.xxx.MyApplication"`

Android service can't connect to server after recreation only in battery saving mode

I'm writing a chat app with the server and Android client written in Kotlin. I create a background service that constantly reads from the socket connected to the server and sends notifications when a message arrives. Everything works fine until user taps 'x' button and closes the app. Connection with server fails during executing cleanUp code posted below. Server had gotten EOF before service managed to send EXIT request and close streams. Then, service is recreated but when it tries to connect to the server it gets ConnectException (connection refused). It happens only when battery saving mode is on. When it's off or phone is connected to my laptop with USB and charging there's no problem.
The ss command lists that there is someone listening on the specified port, so it's not that problem. I've tried to connect in a loop, i. e. try to connect 5 times every 10 seconds, but it got refused every time. I've tried listening on two different ports, but both failed even if one of them wasn't used before. Docs say that default backlog is 50, so I guess it's not that either. I tried to set a SO_REUSEADDR flag on the server socket, but still nothing. And the strange thing is, that when service is started from the app when I launch it for the second time it can connect again. So I've created a broadcast receiver that starts the service the same way as the app in case it crashes, but it's not helping either.
I really was googling it for over a week but it's my first attempt at using both Kotlin and sockets and I'm running out of ideas. If someone has a clue to what might be going on, I'd really appreciate some help.
Here is the service onStartCommand:
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
activeConversation = intent?.getStringExtra(CONV_NAME) ?: ""
login = intent?.getStringExtra(LOGIN) ?: login
if (thread?.isAlive != true) {
thread = thread(start = true) {
synchronized(lock) {
try {
socket = Socket(SERVER_IP, SERVICE_PORT)
output = ObjectOutputStream(socket?.getOutputStream())
input = ObjectInputStream(socket?.getInputStream())
output?.writeObject(Request(START_SERVICE, mutableMapOf(LOGIN to login)))
} catch (e: IOException) {
e.printStackTrace()
return#thread
}
}
handleMessages() //contains input?.readObject() in infinite loop
}
}
return START_STICKY
}
In onDestory() and onTaskRemoved() I call this function:
private fun cleanUp() {
synchronized(lock) {
thread(start = true) {
try {
output?.writeObject(Request(EXIT, mutableMapOf(LOGIN to login)))
output?.close()
input?.close()
socket?.close()
nullStreams()
thread?.join()
println("SERVICE: thread joined")
} catch(e: IOException) {
e.printStackTrace()
return#thread
} finally {
println("Service sends broadcast to ask for recreation")
val restartIntent = Intent(this, ServiceRestarter::class.java)
restartIntent.putExtra(LOGIN, login)
sendBroadcast(restartIntent)
}
}.join()
}
}
ServiceRestarter:
class ServiceRestarter : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
val login = intent?.getStringExtra(LOGIN)
println("SERVICE RESTARTER: receiving restart request from $login")
val serviceIntent = Intent(context, MessengerService::class.java)
serviceIntent.putExtra(LOGIN, login)
context.startService(serviceIntent)
}}
Part of my server responsible for listening:
val clientsSocket = ServerSocket(CLIENTS_PORT)
val serviceSocket = ServerSocket(SERVICE_PORT)
serviceSocket.setReuseAddress(true)
println("Server socket ready!")
println("Service socket port: ${serviceSocket.localPort}")
thread(start = true) {
while(true) ClientThread(clientsSocket.accept(), loggedInUsers, pendingRequests).start()
}
thread(start = true) {
while(true) ServiceThread(serviceSocket.accept(), loggedInUsers).start()
}
And ServiceThread:
class ServiceThread(val socket: Socket,
val loggedInUsers: HashMap<String, UserConnection>) : Thread() {
private var login = ""
private val input = ObjectInputStream(socket.getInputStream())
private val output = ObjectOutputStream(socket.getOutputStream())
override fun run() {
var request = input.readObject() as Request
login = request.content[LOGIN] as String
var userConn: UserConnection?
synchronized(loggedInUsers) {
userConn = loggedInUsers[login]
if(request.action == START_SERVICE) {
println("SERVICE THREAD: New socket conn from $login")
userConn?.run {
println("SERVICE THREAD: putting $login output to logged in users")
serviceStream = output
if(pendingMessage != null) {
output.writeObject(Request(SEND,
mutableMapOf(RESULT to SUCCESS, DATA to pendingMessage)))
pendingMessage = null
}
}
}
}
try { request = input.readObject() as Request }
catch(e: IOException) {
println(e.printStackTrace())
cleanUp()
return#run
}
if(request.action == EXIT) {
println("SERVICE THREAD: Service of user $login is terminating")
cleanUp()
}
}
private fun cleanUp() {
synchronized(loggedInUsers) {
output.close()
input.close()
socket.close()
loggedInUsers[login]?.serviceStream = null
}
}}

Categories

Resources