My Websocket client (OkHttp) doesn't close the connection after the app closed. It opens a new connection every time I open the app which makes the app suffering from multiple messages received on the old and the new Websocket connections from the broadcasting server.
Is that a normal behavior for the android client, as for what I have experienced with web-client, the session was closed properly after the tab killed?
I have been looking up the problem across the internet but no luck so far. I want to make sure whether it happened because of my bad code logic or just the buggy Websocket's implementation from the library?
Here is how I start a new websocket session in the main Activity
var request: Request = Request.Builder()
.url("ws://$serverIP:8080/example/sim/speed")
.build()
var webSocketListener: WebSocketListener = object : WebSocketListener() {
override fun callback(msg: Message) {
updateSpeed(msg.content)
}
override fun onClosing(webSocket: WebSocket?, code: Int, reason: String?) {
super.onClosing(webSocket, code, reason)
}
}
var webSocket = client!!.newWebSocket(request, webSocketListener)
After that updateSpeed() will update a text view on UIThread
The onClosed event was not triggered when the app closed but only when the close function called manually.
I'm sure that it opened a new socket every time because I can see new sessions created on the server with different ports.
What I want is to have the app closing its connection before it was closed.
Thank you
I also encountered this issue, that WebSocket was still opened after my app was killed.
To solve this issue I manually close socket and remove all idle connections:
fun clear() {
webSocket?.close(1001, "Android: User exited the game.")
webSocket = null
subs.clear()
subs.add(Completable.fromAction {
client.connectionPool.evictAll()
}.subscribeOn(Schedulers.io()).subscribe({}, {}))
}
And I basically call clear() inside activity/ies, that might be opened during app kill.
override fun onDestroy() {
super.onDestroy()
App.webSocketClient().clear()
}
Not an ideal solution, but works every time.
Related
I am building an offline first chat app and so I'm using Workmanager to handle POST requests to send chat messages.
I'm learning android development and so I wanted some help on architecturing the upload of chat messages
Current implementation
When ever a new chat message needs to be posted the client does the following
Saves the chat to SQLite using Room with a new UUID
Starts a workmanager unique work to POST this message
This way I can be sure the message is posted eventually when the client has internet
Is this ideal? There are a few issues I see.
I'm starting too many workers. Each message has a work request.
Chronology of messages posted to the server is lost.
A better implementation
A single unique worker to POST messages. Which will fetch all offline messages and post them in the right order
Still not ideal
The issues with these implementation are:
You have very little control on a work (not so easy) once it's started.
If a work fails we've set a backoff time. So when a new message is to be sent we need to replace the old worker with the new work request. This just seems nonoptimal.
We are mutating the worker instead of appending a new task to the queue.
We can't use the one worker per message implementation because we loose chronology and there are too many workers
This is sort of a distributed systems question.
We are starting workers who should work independ of the lifetime of the app
Workers should come back alive in a case they die (already managed by android-workmanager)
Workers should read from a queue of task to be executed (which is what I'm looking for)
There should be a persistance store that acts as a queue for the workers
There should be a service or a factory that invokes workers when ever needed (We don't have this in the current impl)
Questions
Is there a better way to post offline messages to the server when the client has internet? Like a service?
Is there a community build library that does this?
Can the current implementation be scaled to files?
I would need long running workers
Or could use this lib - android-upload-service
Yes you can use Service to effectively sync the messages, First create an Object which will extend from Live Data Class to get live updates for network updates like this,
object NetworkState : LiveData<Boolean>() {
private lateinit var application: Application
private lateinit var networkRequest: NetworkRequest
private lateinit var connectivityManager: ConnectivityManager
fun init(application: Application) {
this.application = application
connectivityManager = application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
networkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build()
}
override fun onActive() {
super.onActive()
getDetails()
}
private fun getDetails() {
connectivityManager.registerNetworkCallback(networkRequest, object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
postValue(true)
}
override fun onLost(network: Network) {
super.onLost(network)
postValue(false)
}
override fun onUnavailable() {
super.onUnavailable()
postValue(false)
}
})
}
Then initialize this in your Application class like this,
#HiltAndroidApp
class BaseApplication : Application(), Configuration.Provider {
#Inject
lateinit var workerFactory: HiltWorkerFactory
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
override fun onCreate() {
super.onCreate()
NetworkState.init(this)
if (BuildConfig.DEBUG){
Timber.plant(Timber.DebugTree())
}
}
}
Now you can create a Service class which will extend from Lifecycle Service and observe the Network updated there. And when the Live Data will emit value true you can check if there are messages which needs to be synced online. But remember it will only work when the app is in foreground.
I'm developing a frame exchange sequence between an nRF52840 and an Android smartphone using the BLE protocol.
The first time I connect, everything works fine.
I activate the listening of BLE notifications by the Android smartphone with this method:
fun enableBleNotificationsOnCentral(currentBluetoothGatt: BluetoothGatt, serviceUUID: UUID, characteristicUUID: UUID) {
getMainDeviceService(currentBluetoothGatt, serviceUUID)?.let { service ->
val notificationConfiguration = service.getCharacteristic(characteristicUUID)
val result = currentBluetoothGatt.setCharacteristicNotification(notificationConfiguration, true)
println(result)
}
}
And I enable sending BLE notifications on the nRF52840 with this method:
fun enableBleNotificationsOnPeripheral(currentBluetoothGatt: BluetoothGatt, serviceUUID: UUID, characteristicUUID: UUID, descriptorUUID: UUID) {
getMainDeviceService(currentBluetoothGatt, serviceUUID)?.let { service ->
val descriptorConfiguration = service.getCharacteristic(characteristicUUID).getDescriptor(
descriptorUUID).apply {
value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
}
val result = currentBluetoothGatt.writeDescriptor(descriptorConfiguration)
println(result)
}
}
These methods are called each time my smartphone is connected to the nRF52840.
But if I disconnect and connect a second time, I receive each of the notifications in duplicate.
In addition, if I disconnect and connect a 3rd time, I receive each notification 3 times, and one more each time I reconnect.
I checked my code on the nRF52840 and it does not duplicate notifications.
Here is the method I call when I request a disconnection:
private fun disconnectFromCurrentDevice() {
currentBluetoothGatt?.disconnect()
BLECallbackManager.currentDevice = null
setUiMode(false)
}
I guess my problem is related to the fact that I don't disable the receipt of BLE notifications by my Android application when I disconnect but I'm not sure. And if that's where the problem comes from, when should I do it in the disconnect method? Can you help me?
I guess you're creating a new BluetoothGatt object for every new connection attempt, but you not destroy the previous one.
Try change disconnect() to close().
I am making an application in Xamarin Forms that uses the Signalr service to implement a chat. The chat works perfectly in the UWP version and in the Android emulator, so it does when I am debugging on my phone (Android), but when I disconnect the phone from the PC the chaos begins. The problem is that I think that when the application goes to the background, it disconnects from the Signalr server.
I have tried automatic reconnection and even changing the times of ServerTimeout and KeepAliveInterval. But I have not been successful. It should be noted that where I live additionally there are major connectivity problems, but still my theory is when the application goes to Background.
This is my code where I initialize my service (Im using a Singleton services).
hubConnection = new HubConnectionBuilder()
.WithUrl(URL, options =>
{
options.AccessTokenProvider = () => Task.FromResult(_myAccessToken);
})
.WithAutomaticReconnect()
//.WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.Zero, TimeSpan.FromSeconds(10) })
//.WithAutomaticReconnect(new RandomRetryPolicy())
.Build();
This is my code to connect when the connection is closed
hubConnection.Closed += async (error) =>
{
OnConnectionClosed?.Invoke(this, new MessageEventArgs("Connection closed...",
string.Empty));
IsConnected = false;
await Task.Delay(new Random().Next(0, 5) * 1000);
try { await ReConnectAsync(); } catch (Exception ex) { Debug.WriteLine(ex); }
};
This is my code to connect
public async Task ReConnectAsync()
{
await ConnectAsync();
}
public async Task ConnectAsync()
{
if (IsConnected)
{
return;
}
Debug.WriteLine(hubConnection.State);
if (hubConnection.State== HubConnectionState.Disconnected)
{
await hubConnection.StartAsync();
//CancellationToken cancellationToken = new CancellationToken();
//await ConnectWithRetryAsync(hubConnection, cancellationToken);
}
IsConnected = true;
}
What else could I try to prevent it from disconnecting on Android or what will I be doing wrong in my code?
You won't be able to keep the SignalR connection alive on Android unless you have a Foreground Service running, which essentially keeps the App alive. This is how music apps etc. can keep functioning in the background.
A foreground service will also need to show a notification to the user that it is running.
Xamarin provides a nice little sample showing how to create a foreground service here https://github.com/xamarin/monodroid-samples/tree/master/ApplicationFundamentals/ServiceSamples/ForegroundServiceDemo
Essentially you create a Service:
[Service]
public class MyForegroundService : Service
{
}
Then you start it from your Activity with an Intent:
var intent = new Intent(this, typeof(MyForegroundService));
StartForegroundService(intent);
In your Service you will need to call StartForeground in a OnStartCommand override, otherwise the service will just get killed.
Question is though. Do you really need a foreground service and keep running SignalR in the background?
Have you thought about polling the back-end once in a while to fetch latest messages?
Have you thought about sending push notifications when the user receives a new message?
You will encounter a bigger limitation if you decide to target iOS as well. There it will be impossible for you to keep your SignalR connection alive.
I believe I may not understand something about how gRPC Channels, Stubs, And Transports work. I have an Android app that creates a channel and a single blocking stub and injects it with dagger when the application is initialized. When I need to make a grpc call, I have a method in my client, that calls a method with that stub. After the app is idle a while, all of my calls return DEADLINE_EXCEEDED errors, though there are no calls showing up in the server logs.
#Singleton
#Provides
fun providesMyClient(app: Application): MyClient {
val channel = AndroidChannelBuilder
.forAddress("example.com", 443)
.overrideAuthority("example.com")
.context(app.applicationContext)
.build()
return MyClient(channel)
}
Where my client class has a function to return a request with a deadline:
class MyClient(channel: ManagedChannel) {
private val blockingStub: MyServiceGrpc.MyServiceBlockingStub = MyServiceGrpc.newBlockingStub(channel)
fun getStuff(): StuffResponse =
blockingStub
.withDeadlineAfter(7, TimeUnit.SECONDS)
.getStuff(stuffRequest())
}
fun getOtherStuff(): StuffResponse =
blockingStub
.withDeadlineAfter(7, TimeUnit.SECONDS)
.getOtherStuff(stuffRequest())
}
I make the calls to the server inside a LiveData class in My Repository, where the call looks like this: myClient.getStuff()
I am guessing that the channel looses its connection at some point, and then all of the subsequent stubs simply can't connect, but I don't see anywhere in the AndroidChannelBuilder documentation that talks about how to handle this (I believed it reconnected automatically). Is it possible that the channel I use to create my blocking stub gets stale, and I should be creating a new blocking stub each time I call getStuff()? Any help in understanding this would be greatly appreciated.
After researching a bit, I believe the issue was that the proxy on the server was closing the connection after a few minutes of idle time, and the client ManagedChannel didn't automatically detect that and connect again when that happened. When constructing the ManagedChannel, I added an idleTimeout to it, which will proactively kill the connection when it's idle, and reestablish it when it's needed again, and this seems to solve the problem. So the new channel construction looks like this:
#Singleton
#Provides
fun providesMyClient(app: Application): MyClient {
val channel = AndroidChannelBuilder
.forAddress("example.com", 443)
.overrideAuthority("example.com")
.context(app.applicationContext)
.idleTimeout(60, TimeUnit.SECONDS)
.build()
return MyClient(channel)
}
As explained in some answers:
On Android, Firebase automatically manages connection state to reduce bandwidth and battery usage. When a client has no active listeners, no pending write or onDisconnect operations, and is not explicitly disconnected by the goOffline method, Firebase closes the connection after 60 seconds of inactivity.
The problem is that after 60s, even after I go to an activity with a complete new reference, event listener, etc.. It still says it is disconnect, when in fact, it is not.
val connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected")
var connectListener : ValueEventListener? = null
fun checkConnection() {
connectListener = connectedRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val connected = snapshot.getValue(Boolean::class.java)!!
if (connected) {
Log.d("FRAG", "CONNECTED")
else{
Log.d("FRAG", "DISCONNECTED")
}
}
override
fun onCancelled(error: DatabaseError) {
System.err.println("Listener was cancelled")
}
})
}
override fun onDetach() {
super.onDetach()
if (connectListener != null){
connectedRef.removeEventListener(connectListener)
}
}
How can I make sure I maintain or create a new connection to Firebase? I call the checkConnection method every onAttach of a fragment and onStart of an activity.
If you have an active listener on any data that is read from the server, the connection should remain open unless you've explicitly called goOffline() in your code. Note that .info/connected itself does not require reading from the server, so does not keep the connection open.
It seems you're using the realtime database to build an presence system on an otherwise Firestore based app. In that case: Cloud Firestore uses a gRPC-based protocol to talk between client and server, while the Firebase Realtime Database uses web sockets. They're in no way compatible or even comparable. Keeping an active listener on data in Firestore does not keep a connection to RTDB open. That's why the example in the Firestore documentation also writes an actual data node to the realtime database.
Stream<Event> checkInternetConectivity() {
Stream<Event> connectivityCheck = _firebaseDatabase.reference().child('.info/connected').onValue;
Stream<Event> randomCheck = _firebaseDatabase.reference().child('connected').onValue;
return Rx.combineLatest2(connectivityCheck, randomCheck,(connectivityCheck, _) => connectivityCheck as Event);}
}
Firebase automatically disconnects from the realtime database in android after 60 seconds if there are no active listeners and listening to '.info/connected' isn't enough to keep the connection active. Creating another stream to listen to a random node in realtime database as a way around to this automatic disconnection.
This is my workaround to this problem in Dart/Flutter