I am working on an app that will communicate with a UDP server for its data. Obviously, this is going to be a long-running process (i.e., the life of the activity), so I want to run this process in the background. When the app gets its data from the UDP server, I will, obviously, want to manipulate the data so I can display it within the app's UI.
From what I have learned, there are 3 ways to run a long-running process in the background:
- Standard Java Thread
- AsyncTask
- Service (Bounded)
For the standard Java thread, from my searches on the internet, I hear lots of grumblings on how creating your own threads in Android is not a good idea. I have tried this route before and it does work, but it also requires quite a bit of manipulation in order to use the thread's received data within the app's UI.
For an AsyncTask, the Android documentation itself says:
AsyncTasks should ideally be used for short operations (a few seconds at the most.)
This is not good for me, as I need this thread to run for the entire life of the activity.
Lastly, there is the bound Service. This seems perfect for my needs. Again, from the Android documentation:
A Service is an application component that can perform long-running operations in the background, and it does not provide a user interface. Another application component can start a service, and it continues to run in the background even if the user switches to another application. Additionally, a component can bind to a service to interact with it and even perform interprocess communication (IPC).
After reading this bit, I proceeded down the Android Service path. Things were going well until I tried to run my app.
First, here is my service:
Listener.java:
public class Listener extends Service {
private boolean keepWorking = true;
private int localPort;
private final IBinder iBinder = new LocalBinder();
public class LocalBinder extends Binder {
Listener getService(){
return Listener.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return iBinder;
}
public void listenToServer(){
try {
byte[] buffer = new byte[16 * 1024];
DatagramPacket recvPacket = new DatagramPacket(buffer, buffer.length);
DatagramSocket server = new DatagramSocket(0);
server.setSoTimeout(1000);
localPort = server.getLocalPort();
while (keepWorking) {
try {
server.receive(recvPacket);
byte[] data = new byte[recvPacket.getLength()];
System.arraycopy(recvPacket.getData(), 0, data, 0, data.length);
// Do stuff with the data ...
} catch ( IOException ioe) {
// something here
}
}
} catch (SocketException se) {
// something here
}
}
public int getPort() {
return this.localPort;
}
public void stopWorking() {
this.keepWorking = false;
}
}
And, my MainActivity.java that binds to this service:
public class MainActivity extends AppCompatActivity {
Listener myListener;
boolean isBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, Listener.class);
startService(intent);
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
if(isBound){
unbindService(serviceConnection);
isBound = false;
}
}
private ServiceConnection serviceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Listener.LocalBinder localBinder = (Listener.LocalBinder) service;
myListener = localBinder.getService();
myListener.listenToServer();
isBound = true;
}
@Override
public void onServiceDisconnected(ComponentName name) {
isBound = false;
}
};
}
I quickly discovered that, even though I am doing this from a service, it is apparently still being run from the main thread anyway, as I get this error:
android.os.NetworkOnMainThreadException
from the line in my Listener.java that reads server.receive(recvPacket);
.
After digging around, I discovered that I may need a thread or AsyncTask to handle this anyway.
So, my question is this: What is the authoritative, "proper" way to perform long-running, network operations in an Android app?
Why does Android make it so difficult to do what is, really, the "bread and butter" of Android apps? I know I can't be the first person to want to do this.
Thanks for your help.