I don't know what might be causing my issue because I didn't really made a lot of changes to it. The changes only involved the MainActivity
, AndroidManifest
, and the helper class I made that handle the creation of the Service
, but somehow StrictMode
was pointing at some illegal disk reads on other files. Also, the Service
shouldn't immediately affect the app because I only start it on MainActivity
's onStop
because I just want it to listen to some stuff when the MainActivity
is died. I think I already fixed the illegal reads warning somehow, but I'm still getting the ANR. StrictMode
is saying that I have a leak, but LeakCanary
isn't detecting anything. It did detect 1 thing, but I already fixed it. And still, I'm still getting the ANR occasionally. The last code that runs before I get the ANR is a method that sends UDP, which wasn't giving me any issue previously. I already changed that method's parameters to accept WeakReference
s, but it didn't really help I think. All my I/O operations are either inside a lifecycleScope.launch
or in a Thread
, so I don't know why I should be getting an ANR, on an app that was already running fine previously.
Here's my code:
This is the helper class for creating the Service
:
class ServiceHelper : Service() {
private val socketHelper = SocketHelper.getInstance()!!
private var lastShownNotificationId = 1
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
when(intent.action){
Actions.START.toString() -> createAndShowForegroundNotification(this, lastShownNotificationId)
Actions.STOP.toString() -> stopSelf()
}
// socketHelper.start(null)
// socketHelper.socketRespObservers.clear()
// socketHelper.socketRespObservers.add { socketResp ->
// val respArr = socketResp.split(SocketHelper.SOCKET_DELIMITER)
//
// if (respArr[0] == SocketHelper.SERVER_UDP_HEADER) {
// processUDPMessage(respArr[1], respArr[2])
// }
// }
return START_STICKY
}
private fun createAndShowForegroundNotification(yourService: Service, notificationId: Int) {
val notifBuilder = NotificationCompat.Builder(this,
"atlantis_service_notif_channel_id")
.setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle(yourService.getString(R.string.service_notif_title))
.setContentText(yourService.getString(R.string.service_notif_content))
val notification = notifBuilder.build()
yourService.startForeground(notificationId, notification)
lastShownNotificationId = notificationId
}
enum class Actions{
START, STOP
}
override fun onBind(intent: Intent): IBinder? {
return null
}
private fun processUDPMessage(udpCommand: String, udpValue: String) {
if (udpCommand == SocketHelper.APP_NOTIFICATION) {
val srvNotif = Gson().fromJson(udpValue, ServerNotification::class.java)
Log.i(TAG, "service notif: $srvNotif")
}
}
companion object{
const val TAG = ""
}
}
Then this is that Socket
helper class:
class SocketHelper {
private val broadcastAddr: InetAddress = InetAddress.getByName("255.255.255.255")
private lateinit var localBroadcastAddr: InetAddress
private lateinit var basicSocket: DatagramSocket
val socketRespObservers = mutableListOf<(String) -> Unit>()
private var socketResp: String by Delegates.observable("") { _, _, newVal ->
socketRespObservers.forEach { it(newVal) }
}
private lateinit var _ctx: WeakReference<Context>
@Synchronized
fun start(ctx: WeakReference<Context>?) {
if(ctx != null){
_ctx = ctx
}
Thread {
try {
if(!::basicSocket.isInitialized || basicSocket == null){
basicSocket = DatagramSocket(UDP_SERVER_PORT)
val udpPayload = StringBuilder()
.append(UDP_HEADER).append(SOCKET_DELIMITER)
.append(REQ_SERVER_IP_COMMAND).append(SOCKET_DELIMITER)
.append("").toString()
basicSendToUDP(WeakReference(udpPayload), getBroadcastAddr())
}
while (true) {
basicReceiveFromUDP()
}
} catch (e: Exception) {
sendSnackbar("Socket Error: ${e.localizedMessage}")
}
}.start()
}
@Synchronized
fun basicSendToUDP(payload: WeakReference<String>, iAddr: WeakReference<InetAddress>) {
Log.i(TAG, "basicSendToUDP")
Thread {
try{
val sendPacket =
DatagramPacket(
payload.get()!!.toByteArray(),
payload.get()!!.length,
iAddr.get(),
UDP_SERVER_PORT
)
basicSocket.send(sendPacket)
} catch (e: IOException) {
Log.e(TAG, "sendUDP IO Error:", e)
close("sendUDP IO Error")
val msgArr = e.message?.split("(")
var realErrorMsg = ""
if(msgArr != null){
realErrorMsg = msgArr[1].replace(")", "")
///I'm using gorm with my golang server, this is just some error msg parsing essentially
}
if(realErrorMsg != ""){
sendSnackbar("Network error: $realErrorMsg")
}else{
sendSnackbar("Network error occurred")
}
}catch (e: Exception){
Log.e(TAG, "sendUDP IO Error:", e)
close("sendUDP IO Error")
sendSnackbar("Network error: ${e.message}")
}
}.start()
@Synchronized
private fun basicReceiveFromUDP() {
val receiveData = ByteArray(1024)
val receivePacket = DatagramPacket(receiveData, receiveData.size)
basicSocket.receive(receivePacket)
socketResp = String(receivePacket.data, 0, receivePacket.length)
val respArr = socketResp.split(SOCKET_DELIMITER)
if (respArr[0] == SERVER_UDP_HEADER && respArr.size > 3 && respArr[3] != null) {
sendAck(respArr[3].toInt())
}
}
@Synchronized
private fun sendAck(seqNum: Int) {
val udpPayload = StringBuilder()
.append(UDP_HEADER).append(SOCKET_DELIMITER)
.append(ACK_PAYLOAD_RECEIVED).append(SOCKET_DELIMITER)
.append(seqNum).toString()
basicSendToUDP(WeakReference(udpPayload), getBroadcastAddr())
}
fun getBroadcastAddr(): WeakReference<InetAddress> {
return WeakReference(broadcastAddr)
}
private fun sendSnackbar(message: String){
//I was suspecting this to be a cause of the ANR, but it doesn't seem to be
// if(::_ctx.isInitialized){
// (_ctx as MainActivity).showSnackMessage(message, "SocketHelper")
// }
}
fun close(closeReason: String) {
basicSocket.close()
}
On the call to basicSendToUDP
inside sendAck
, that seems to be when/where the ANR mostly gets triggered.
Here's how I added the Service
to the AndroidManifest
, also currently commented:
<service android:name=".utils.helpers.ServiceHelper"
android:foregroundServiceType="dataSync"
android:exported="false"></service>
I don't know if commenting that helped stop the ANR, because it's not appearing after doing that.
Then this are the code that "fixed" the illegal disk reads:
val oldPolicy = StrictMode.allowThreadDiskReads()
try{
prefHelper = (requireActivity().application as MyApp).prefHelper
/// do stuff with prefHelper
}finally {
StrictMode.setThreadPolicy(oldPolicy)
}
This is what prefHelper actually looks like
class MyApp : Application() {
...
val prefHelper by lazy { PreferenceHelper.customPrefs(this, "atlantis_prefs") }
}
Then this is what customPrefs looks like:
object PreferenceHelper {
lateinit var instance: SharedPreferences
fun customPrefs(context: Context, name: String): SharedPreferences {
if(!::instance.isInitialized){
instance = context.getSharedPreferences(name, Context.MODE_PRIVATE)
}
return instance
}
}
Right now, if I disable/comment out all the code that is related to the Service
(the ones in MainActivity
and AndroidManifest
), I'd still the get the leak warning from StrictMode
, but it won't result into an ANR. I probably have some issue with my socket
helper class that I just can't see.
UPDATE:
I think I found a very interesting cause of the ANR. After adding LeakCanary
to my app, I realized that it asks for POST_NOTIFICATION
permission. I was almost gonna go crazy that my app that I already commented out the part that asks for that permission, is still asking for it. And also, if I accept that request, that's when the ANR happens
UPDATE:
I tried "disabling" LeakCanary
as I think the leaks aren't as important right now as the ANR because the leaks are mostly caused by DataBinding
not releasing ConstraintLayout
properly and I already added a fix to it (I'm still getting some leak notifications about ConstraintLayout
but I don't want to spend more time on them right now). I also tried to ask the POST_NOTIFICATION
permission myself, and well, the ANR appeared again. I got anrwatchdog
now and it was pointing out to the line with basicSocket?.receive(receivePacket)
in it. Again, all my code works perfectly if I don't ask for POST_NOTIFICATION
permission, so I don't know why a permission would break it. Right now, even if I haven't accepted the POST_NOTIFICATION
permission, anrwatchdog
would already crash the app saying that basicSocket?.receive(receivePacket)
would cause an ANR.
UPDATE:
I think I hit a metaphorical gas pipe. I filed an issue in issuetracker and, it's already being looked at by the people from AOSP. Here's the link to the issue.