Dependency injection with IntentService doesn't work - android

I want to create TCP_Client which sends data to server in many activities. I decided to use Dependency Injection to inject all samely configured clients to all clients. Unfortunately it stops working on start.
My application module
val appModule = module {
single<ConnectionService> { ConnectionServiceTcp("192.168.0.1", 8888) }
}
Main Application
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this#MainApplication)
androidLogger()
modules(appModule)
}
}
}
class ConnectionServiceTcp(private val ipAddress: String, private val port : Int)
: IntentService("TCP_CLIENT"), ConnectionService {
private var client : Socket? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_STICKY
}
override fun onHandleIntent(intent: Intent?) {
startTcpServer()
}
private fun startTcpServer() {
client = Socket(ipAddress, port)
}
override fun isConnectedToServer(): Boolean {
Log.println(Log.INFO, null, "Adres = ${client?.localAddress} port = ${client?.localPort}")
return false
}
}
class MainActivity : AppCompatActivity() {
private val connectionService : ConnectionService by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startTcpServer()
}
private fun startTcpServer() {
val serverTcp = Intent(this, ConnectionServiceTcp::class.java)
startService(serverTcp)
}
And then I receive
java.lang.RuntimeException: Unable to instantiate service connection.impl.ConnectionServiceTcp: java.lang.InstantiationException: java.lang.Class<connection.impl.ConnectionServiceTcp> has no zero argument constructor
I can't find a way to Inject background client for sending TCP requests

Just as with Activities, Fragments or some other platform components, Android system implies that Services should have a single no-arg constructor. The system looks for a default constructor in Service class and calls it using reflection. That's why it's prohibited to add non-default constructors (i.e. constuctors with arguments).
To inject dependencies into Service you should do the same as you do in Activities (declare a field and inject it using by inject() delegate. So the final code will look as follows:
class ConnectionServiceTcp()
: IntentService("TCP_CLIENT"), ConnectionService {
private val ipAddress: String by inject()
private val port : Int by inject()
private var client : Socket? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_STICKY
}
override fun onHandleIntent(intent: Intent?) {
startTcpServer()
}
private fun startTcpServer() {
client = Socket(ipAddress, port)
}
override fun isConnectedToServer(): Boolean {
Log.println(Log.INFO, null, "Adres = ${client?.localAddress} port = ${client?.localPort}")
return false
}
}

Related

How to return response or send data to service from activity from another app ? Android Kotlin

I have a started service app. It intent to activity from another app, but still running in foreground. After a button click in that activity, I want to send data (for example a string "potato") to service without startService() in order to continue, not restart. That's how service keeps running till get the data, while(requiredData != "potato"){}.start. How can I send it, or return response ? I think to use Messenger or Broadcast, but I'm not sure it fits well and how to do.
Note: Service App connected to an activity from another app.
Service App
class RegistryService : Service() {
override fun onBind(p0: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val i = packageManager.getLaunchIntentForPackage("com.myexample.potatoactivity")
if (i!=null) {
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(i)
} else {
Toast.makeText(this,"Fail",Toast.LENGTH_SHORT).show()
}
while (true) { // requiredData != "potato"
//Log.d("MyService", "Wait for potato")
}
return START_STICKY
}
}
Potato Activity
class PotatoActivity : AppCompatActivity() {
private lateinit var binding: ActivityPotatoBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPotatoBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.buttonSendData.setOnClickListener {
//it.putExtra("REQUIRED_DATA", "potato")
}
}
}

Observe Kotlin StateFlow from Background service's companion object

Are there any cons of using Kotlin state flow in companion object for checking if background service is running?
I wanted to use ActivityManager and its function isServiceRunning(Service::class.name), but this method is deprecated.
By the way, I should be observing this service from Fragment/Fragment's ViewModel, so I wanted to avoid Binding the service to Activity.
class TestService : Service() {
companion object {
private val _testStateFlow = MutableStateFlow<TestServiceEvent>()
val testServiceStateFlow = _testStateFlow.asStateFlow()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
_testStateFlow.value = TestServiceEvent.ServiceRunning(true)
....
....
....
....
_testStateFlow.value = TestServiceEvent.TestTaskDone()
}
override fun onDestroy(){
super.onDestroy()
_testStateFlow.value = TestServiceEvent.ServiceRunning(false)
}
sealed class TestServiceEvent{
data class ServiceRunning(val isRunning : Boolean) : TestServiceEvent()
object TestTaskDone : TestServiceEvent()
}
}

How to use MutableSharedFlow in Android Service?

I want to use MutableSharedFlow in the Service class, but I'm not sure how to stop subscribing when Service ends. How to implement the MutableSharedFlow function in service or any other function available to listen to stream data?
To use a Flow in an android Service class we need a CoroutineScope instance to handle launching coroutines and cancellations. Please see the following code with my comments:
class CoroutineService : Service() {
private val scope = CoroutineScope(Dispatchers.IO)
private val flow = MutableSharedFlow<String>(extraBufferCapacity = 64)
override fun onCreate() {
super.onCreate()
// collect data emitted by the Flow
flow.onEach {
// Handle data
}.launchIn(scope)
}
override fun onStartCommand(#Nullable intent: Intent?, flags: Int, startId: Int): Int {
scope.launch {
// retrieve data from Intent and send it to Flow
val messageFromIntent = intent?.let { it.extras?.getString("KEY_MESSAGE")} ?: ""
flow.emit(messageFromIntent)
}
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() {
scope.cancel() // cancel CoroutineScope and all launched coroutines
}
}

Proper way to unregister a callback from an Application class

I have implemented a custom Application class in my app which handles updating the app theme before the app start up.
I also registered a network callback to set a variable each time there is a connection change.
My application class is as such:
Application.kt
package com.th3pl4gu3.mes.ui
.....
class MesApplication : Application() {
companion object {
#Volatile
private var INSTANCE: MesApplication? = null
fun getInstance() =
INSTANCE ?: synchronized(this) {
INSTANCE
?: MesApplication().also { INSTANCE = it }
}
}
override fun onCreate() {
super.onCreate()
// Assigns 'this' to the singleton object
INSTANCE = this
// Updates the application's theme
updateAppTheme()
// Start a network callback to monitor internet connection
startNetworkCallback()
}
private fun startNetworkCallback(){
try{
val cm = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val builder = NetworkRequest.Builder()
cm.registerNetworkCallback(builder.build(), object: ConnectivityManager.NetworkCallback(){
override fun onAvailable(network: Network) {
super.onAvailable(network)
Log.v("INTERNET_TEST", "AC: Network Available")
Global.isNetworkConnected = true
}
override fun onLost(network: Network) {
super.onLost(network)
Log.v("INTERNET_TEST", "AC: Network Lost")
Global.isNetworkConnected = false
}
})
Global.isNetworkConnected = false
}catch (e: Exception){
Global.isNetworkConnected = false
}
}
}
However, from the docs, they recommend to unregister this callback but the Application class lifecycle doesn't have any onPause or onDestroy function.
Is there any proper way to unregister this callback to not cause any memory leaks?
Also feel free to suggest any alternatives in case I am coding this wrong
In this case , you can use ActivityLifecycleCallbacks, to detect are any Activity of your is in Foreground?
ActivityLiveCycleListener
class ActivityLiveCycleListener(private val appStateListener: AppStateListener) : Application.ActivityLifecycleCallbacks {
companion object {
var foregroundActivities = 0
}
override fun onActivityPaused(p0: Activity) {
}
override fun onActivityStarted(p0: Activity) {
if(foregroundActivities == 0){
appStateListener.onAppForeGround()
}
foregroundActivities++
}
override fun onActivityDestroyed(p0: Activity) {
}
override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {
}
override fun onActivityStopped(p0: Activity) {
foregroundActivities--
if(foregroundActivities == 0){
appStateListener.onAppBackground()
}
}
override fun onActivityCreated(p0: Activity, p1: Bundle?) {
}
override fun onActivityResumed(p0: Activity) {
}
}
And your interface can have two methods to indicate background/foreground state
interface AppStateListener{
fun onAppForeGround()
fun onAppBackground()
}
Now in Application onCreate(), register to ActivityLifeCycleListener
override fun onCreate(){
registerActivityLifecycleCallbacks(ActivityLiveCycleListener(object : AppStateListener{
override fun onAppForeGround() {
//start network listener
}
override fun onAppBackground() {
//remove network listener
}
}))
}

Socket connection in background

I need to make an application where, while the user is authorized, it keeps the socket connection until it is logged out. For this purpose, a foreground service is created, which starts after the authorization of the user, and stops when it is logged out. It implements connection and reconnection on the socket.
All works well until you press the power button and turn off the charging. After this, the user stops receiving pongs from the server and the SocketTimeoutException is received on the OkHttp, and also stops receiving messages on the socket. On JavaWebsocket, The connection was closed because the other endpoint did not respond with a pong in time is received, after which you can successfully create a new socket connection, but it will repeat the same problem in the loop.
In the settings, the optimization of the battery for this application was disabled. What can I do to make a stable connection socket work in the background?
Implementation of activity:
class MainActivity : BaseFragmentPermissionActivity(), MainMvpView {
private var mIsSocketBound = false
private var mSocketBroadcastReceiver = SocketBroadcastReceiver(this)
private var mSocketConnection = SocketConnection(this)
private var mSocketService: SocketService? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
doBindService()
}
private fun doBindService() {
bindService(Intent(this, SocketService::class.java), mSocketConnection, Context.BIND_AUTO_CREATE)
mIsSocketBound = true
}
override fun onStart() {
super.onStart()
...
mSocketService?.doStopForeground()
}
override fun onStop() {
mSocketService?.doStartForeground()
...
super.onStop()
}
override fun onDestroy() {
doUnbindService()
...
super.onDestroy()
}
private fun doUnbindService() {
if (mIsSocketBound) {
unbindService(mSocketConnection)
mIsSocketBound = false
mSocketService = null
}
}
class SocketConnection(mainActivity: MainActivity) : ServiceConnection {
private val mMainActivity: WeakReference<MainActivity> = WeakReference(mainActivity)
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
val socketService = (service as SocketService.LocalBinder).getService()
mMainActivity.get()?.mSocketService = socketService
if (socketService.isForeground()) {
socketService.doStopForeground()
}
}
override fun onServiceDisconnected(name: ComponentName?) {
mMainActivity.get()?.mSocketService = null
}
}
}
Implementation of service:
class SocketService : Service(), MvpErrorHandler {
private val mConnectingHandler = Handler()
private val mConnectingTask = ConnectingTask(this)
private var mIsRunningForeground = false
override fun onBind(intent: Intent?): IBinder {
startService(Intent(this, SocketService::class.java))
return mBinder
}
override fun onCreate() {
super.onCreate()
DaggerServiceComponent.builder()
.serviceModule(ServiceModule(this))
.applicationComponent(PatrolApplication.applicationComponent)
.build()
.inject(this)
startConnecting()
...
}
override fun onDestroy() {
...
stopConnecting()
super.onDestroy()
}
private fun startConnecting() {
if (!mIsConnecting) {
mIsConnecting = true
mConnectingHandler.post(mConnectingTask)
}
}
private fun stopConnecting() {
mConnectingHandler.removeCallbacks(mConnectingTask)
mIsConnecting = false
}
private fun openConnection() {
mCompositeDisposable.add(mDataManager.getSocketToken()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(false, this, {
stopConnecting()
mDataManager.openSocketConnection(it.token)
}, {
mConnectingHandler.postDelayed(mConnectingTask, RECONNECT_TIME.toLong())
return#subscribe ErrorHandlerUtil.handleGetSocketError(it, this)
}))
}
class ConnectingTask(socketService: SocketService) : Runnable {
private val mSocketService: WeakReference<SocketService> = WeakReference(socketService)
override fun run() {
mSocketService.get()?.openConnection()
}
}
}
Implementation of SocketHelper using JavaWebsocket:
class CustomApiSocketHelper #Inject constructor() : ApiSocketHelper {
private var mCustomSocketClient: WebSocketClient? = null
override fun openSocketConnection(token: String) {
mCustomSocketClient = CustomSocketClient(URI(CONNECTION_URL + token))
mCustomSocketClient?.connect()
}
override fun sendMessage(text: String) {
if (mCustomSocketClient?.isOpen == true) {
try {
mCustomSocketClient?.send(text)
} catch (t: Throwable) {
Log.e(TAG, Log.getStackTraceString(t))
Crashlytics.logException(t)
}
}
}
override fun closeSocketConnection() {
mCustomSocketClient?.close(CLOSE_REASON_OK)
}
class CustomSocketClient(uri: URI) : WebSocketClient(uri) {
init {
connectionLostTimeout = PING_TIMEOUT
}
override fun onOpen(handshakedata: ServerHandshake?) {
sendBroadcast(SocketActionType.OPEN.action)
}
override fun onMessage(message: String?) {
sendBroadcast(SocketActionType.MESSAGE.action, message)
}
override fun onClose(code: Int, reason: String?, remote: Boolean) {
if (code != CLOSE_REASON_OK) {
//call startConnecting() in service
sendBroadcast(SocketActionType.CLOSE.action)
}
}
override fun onError(ex: Exception?) {
sendBroadcast(SocketActionType.FAILURE.action)
}
private fun sendBroadcast(type: Int) {
val intent = Intent().apply { action = SOCKET_BROADCAST_ACTION }
intent.putExtra(SOCKET_MESSAGE_TYPE, type)
LocalBroadcastManager.getInstance(CustomApplication.application).sendBroadcast(intent)
}
private fun sendBroadcast(type: Int, text: String?) {
val intent = Intent().apply { action = SOCKET_BROADCAST_ACTION }
intent.putExtra(SOCKET_MESSAGE_TYPE, type)
intent.putExtra(SOCKET_MESSAGE, text)
LocalBroadcastManager.getInstance(CustomApplication.application).sendBroadcast(intent)
}
}
}
Implementation of SocketHelper using OkHttp:
class CustomApiSocketHelper #Inject constructor() : ApiSocketHelper {
private var mCustomSocketClient: WebSocket? = null
override fun openSocketConnection(token: String) {
val request = Request.Builder()
.url(CONNECTION_URL + token)
.build()
mCustomSocketClient = CustomApplication.applicationComponent.authorizedClient().newWebSocket(request, CustomSocketClient())
}
override fun sendMessage(text: String) {
mPatrolSocketClient?.send(text)
}
override fun closeSocketConnection() {
mCustomSocketClient?.close(CLOSE_REASON_OK, null)
}
class CustomSocketClient : WebSocketListener() {
override fun onOpen(webSocket: WebSocket, response: Response) {
super.onOpen(webSocket, response)
sendBroadcast(SocketActionType.OPEN.action)
}
override fun onMessage(webSocket: WebSocket, text: String) {
super.onMessage(webSocket, text)
sendBroadcast(SocketActionType.MESSAGE.action, text)
}
override fun onClosed(webSocket: WebSocket?, code: Int, reason: String?) {
super.onClosed(webSocket, code, reason)
if (code != CLOSE_REASON_OK) {
sendBroadcast(SocketActionType.CLOSE.action)
}
}
override fun onFailure(webSocket: WebSocket?, t: Throwable?, response: Response?) {
super.onFailure(webSocket, t, response)
sendBroadcast(SocketActionType.FAILURE.action)
}
private fun sendBroadcast(type: Int) {
val intent = Intent().apply { action = SOCKET_BROADCAST_ACTION }
intent.putExtra(SOCKET_MESSAGE_TYPE, type)
LocalBroadcastManager.getInstance(CustomApplication.application).sendBroadcast(intent)
}
private fun sendBroadcast(type: Int, text: String?) {
val intent = Intent().apply { action = SOCKET_BROADCAST_ACTION }
intent.putExtra(SOCKET_MESSAGE_TYPE, type)
intent.putExtra(SOCKET_MESSAGE, text)
LocalBroadcastManager.getInstance(CustomApplication.application).sendBroadcast(intent)
}
}
}
...
#Provides
#Singleton
#Named(AUTHORIZED_CLIENT)
fun provideAuthorizedClient(builder: OkHttpClient.Builder, interceptor: Interceptor, authenticator: Authenticator): OkHttpClient = builder
.addInterceptor(interceptor)
.authenticator(authenticator)
.pingInterval(PING_TIMEOUT.toLong(), TimeUnit.SECONDS)
.build()
#Provides
#Singleton
fun provideOkHttpBuilder() = CustomApiHelper.getOkHttpBuilder()
fun getOkHttpBuilder(): OkHttpClient.Builder {
val builder = OkHttpClient.Builder()
builder.readTimeout(NETWORK_CALL_TIMEOUT, TimeUnit.SECONDS)
builder.writeTimeout(NETWORK_CALL_TIMEOUT, TimeUnit.SECONDS)
if (BuildConfig.DEBUG) {
val logger = HttpLoggingInterceptor()
logger.level = HttpLoggingInterceptor.Level.BASIC
builder.addInterceptor(logger)
}
return builder
}
After some research and testing on different devices, it was found that for stable operation on the network, it is necessary that the device is charging or has a screen enabled. In the other case, neither PARTIAL_WAKE_LOCK nor the disabling of battery optimization in the settings itself can solve the problem.
The recommended way to solve this problem is to add this code to your activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
}
This prevents the screen from turning off and provides a stable socket connection. But we still have the situation that the user can press the power button. And, if at this moment the device is charging, everything will work as before, but otherwise, we will get the socket disconnect. To solve this problem, you need to periodically wake the device, in order to support the ping-pong process. This is not a recommended solution because it will lead to battery draining, and can not guarantee 100% performance, but if this moment is critical for you, then you can use this solution. You need to add this code, in a suitable place for you, in this example is used at the time of ping.
#Suppress("DEPRECATION")
override fun onWebsocketPing(conn: WebSocket?, f: Framedata?) {
if (mSocketWakeLock == null) {
mSocketWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK or PowerManager.SCREEN_BRIGHT_WAKE_LOCK or PowerManager.ACQUIRE_CAUSES_WAKEUP, TAG)
}
mSocketWakeLock?.takeIf { !it.isHeld }?.run { acquire(WAKE_TIMEOUT) }
super.onWebsocketPing(conn, f)
mSocketWakeLock?.takeIf { it.isHeld }?.run { release() }
}
Using this solution, on the test devices socket connection, with good Internet, stays stable for 2 hours or more. Without it, it is constantly disconnect.

Categories

Resources