I had a problem with UDP Datagrams in that I could not receive UDP packets from a server but I could send them. I looked through many examples but could not figure out what was wrong with my code. I finally found hints to what was going wrong from different sites.
I have thus updated the question here in case it might help someone in the future. The code below is working over a WiFi network on a LG phone and was built on Android Studio 4.2 (29/4/2021); SDK Platform 30; Kotlin 1.5.0
At the end of the code section below I have written some comments as to what was causing my code not to work.
This is my MainActivity code
//Required includes
import android.os.Bundle
import android.os.StrictMode
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import java.io.IOException
import java.net.*
class MainActivity : AppCompatActivity() {
//declared variables
private var clientThread: ClientThread? = null
private var thread: Thread? = null
private var tv:TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new)
//Create a thread so that the received data does not run within the main user interface
clientThread = ClientThread()
thread = Thread(clientThread)
thread!!.start()
// create a value that is linked to a button called (id) MyButton in the layout
val buttonPress = findViewById<Button>(R.id.MyButton)
tv = findViewById(R.id.rcvdData)
tv!!.text = "Data Captured"
//Create a listener that will respond if MyButton is clicked
buttonPress.setOnClickListener{
//send a UDP package as a test
sendUDP("Hello")
}
}
//************************************ Some test code to send a UDP package
fun sendUDP(messageStr: String) {
// Hack Prevent crash (sending should be done using a separate thread)
val policy = StrictMode.ThreadPolicy.Builder().permitAll().build()
StrictMode.setThreadPolicy(policy) //Just for testing relax the rules...
try {
//Open a port to send a UDP package
val socket = DatagramSocket()
socket.broadcast = true
val sendData = messageStr.toByteArray()
val sendPacket = DatagramPacket(sendData, sendData.size, InetAddress.getByName(SERVER_IP), SERVERPORT)
socket.send(sendPacket)
println("Packet Sent")
} catch (e: IOException) {
println(">>>>>>>>>>>>> IOException "+e.message)
}
}
//************************************* Some test code for receiving a UDP package
internal inner class ClientThread : Runnable {
private var socket: DatagramSocket? = null
private val recvBuf = ByteArray(1500)
private val packet = DatagramPacket(recvBuf, recvBuf.size)
// **********************************************************************************************
// * Open the network socket connection and start receiving a Byte Array *
// **********************************************************************************************
override fun run() {
try {
//Keep a socket open to listen to all the UDP trafic that is destined for this port
socket = DatagramSocket(CLIENTPORT)
while (true) {
//Receive a packet
socket!!.receive(packet)
//Packet received
println("Packet received from: " + packet.address.hostAddress)
val data = String(packet.data).trim { it <= ' ' }
println("Packet received; data: $data")
//Change the text on the main activity view
runOnUiThread { tv?.text = data }
}
}
catch (e1: IOException) {
println(">>>>>>>>>>>>> IOException "+e1.message)
socket?.close()
}
catch (e2: UnknownHostException) {
println(">>>>>>>>>>>>> UnknownHostException "+e2.message)
socket?.close()
}
finally{
socket?.close()
}
}
}
companion object {
val CLIENTPORT = 3000
val SERVERPORT = 3000
val SERVER_IP = "192.168.8.102"
}
}
In my manifest file I added this permissions
<uses-permission android:name="android.permission.INTERNET"/>
The "activity_new.xml" only contains a button with id MyButton and a TextView with the id rcvdData
gradle.build (Project)
buildscript {
ext.kotlin_version = "1.5.0"
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
gradle.build (Module)
plugins {
id 'com.android.application'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.example.udptry1"
minSdkVersion 23
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
Although I could send datagram UDP packets I could not receive them. After finally getting it right to receive UDP packets, I could only receive UNICAST messages (messages only intended for my IP) and not BROADCAST messages (messages intended for multiple devices).
The reason I could not receive messages was that I was emulating the receiver on my PC. Android's emulator changes the IP of the emulated device and does not bind it to the PC's IP address. This meant that although my PC would receive broadcasted messages, the emulated phone did not. For more details look at this link (https://developer.android.com/studio/run/emulator-networking)
The reason I could only receive UNICAST and not BROADCAST messages was that once I got the code working on my cellphone, I would leave the phone while coding. This meant that the phone's screen would go to sleep. Apparently there is a large number of phones that DISABLE LISTENING to broadcast messages when the phone goes to sleep to conserve electricity. Once the phone is awake it would listen to broadcast messages.
Getting a multicast lock did not seem to affect this functionality on my phone (I tried this only for broadcasting, so unfortunately I don't know if it might work if you were actually using a multicastsocket instead of a datagramsocket)
I have found the solution. There was actually no issue with the code. I have updated the original question with comments on what caused the problem... Please see the question above for a full explanation.
In short the problem was with Android's emulator having a different IP to the PC IP and secondly the phone stopped listening to broadcasted messages once it goes to sleep.