Setup:
Components:
App A that has a:
web server that runs in its own thread (started in a service). Server sends intents to app B
broadcast listener that listens for broadcast events from app B
App B that has a:
- broadcast listener that listens for intents from app A and sends intents back to it (with an extra (JSON) payload)
Execution flow:
- app A is started, it starts a service in a separate thread in MainActivity
- the service then starts a webserver that runs in its own threads and (the service) continues running in the background
- when receiving a request the webserver sends an intent to app B and waits for a period of time
- app B sends a response
- the broadcast receiver in app A writes the intent extras to a file
- the webserver now checks whether the file exists and sends a response back to the client accordingly
The webserver is started in a "regular" service (WebService:
public class WebService extends Service {
private static final String TAG = WebService.class.getSimpleName();
private Looper serviceLooper;
private ServiceHandler serviceHandler;
private static final String extensionId = "";
private static final String receiverServiceAppId = "com.cam.receiverservice";
private static final String intentChannelsJsonKey = "Channels";
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
Log.d(TAG, "Created service handler");
}
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "Received message");
}
}
@Override
public void onCreate() {
Log.d(TAG, "Creating service");
// Start up the thread running the service. Note that we create a
// separate thread because the service normally runs in the process's
// main thread, which we don't want to block. We also make it
// background priority so CPU-intensive work doesn't disrupt our UI.
HandlerThread thread = new HandlerThread("ServiceStartArguments",
Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
serviceLooper = thread.getLooper();
serviceHandler = new ServiceHandler(serviceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "Starting web server");
WebServer webServer;
int portNumber = 8080;
try {
// WEBSERVER STARTED HERE
webServer = new WebServer(getApplicationContext(), portNumber);
} catch (IOException e) {
Log.d(TAG, "\nCouldn't start web server on port " + portNumber + "\n");
e.printStackTrace();
}
Log.d(TAG, "Started web server");
// For each start request, send a message to start a job and deliver the
// start ID so we know which request we're stopping when we finish the job
Message msg = serviceHandler.obtainMessage();
msg.arg1 = startId;
serviceHandler.sendMessage(msg);
// If we get killed, after returning from here, restart
return START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "Binding service");
// We don't provide binding, so return null
return null;
}
@Override
public void onDestroy() {
Log.d(TAG, "Destroying service");
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
}
).
The broadcast receiver in app A writes the extras of the intent it receives from app B to a file:
public class AppABroadcastReceiver extends BroadcastReceiver {
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public void onReceive(Context context, Intent intent) {
// writes contents of intent to file
handleReceiver(Constants.RECEIVER_TYPE.RESPONSE_RECEIVER, context, intent);
}
}
The webserver started by the service:
public class WebServer extends NanoHTTPD {
private static final String TAG = WebServer.class.getName();
private static final int PORT_NO = 8080;
private int portNumber = PORT_NO;
private Context context;
private String cwd;
public WebServer(Context context) throws IOException {
super(PORT_NO);
this.context = context;
start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
cwd = context.getApplicationInfo().dataDir;
Log.d(TAG, "\nWeb server started at http://localhost:" + PORT_NO + "/ \n");
}
public WebServer(Context context, int portNumber) throws IOException {
super(portNumber);
this.context = context;
start(NanoHTTPD.SOCKET_READ_TIMEOUT, false);
cwd = context.getApplicationInfo().dataDir;
Log.d(TAG, "\nWeb server started at http://localhost:" + portNumber + "/ \n");
}
public int getPortNumber() {
return portNumber;
}
private void sendIntent(String data) {
Intent intent = new Intent();
Log.d(TAG, "Creating intent to request data from app B");
intent.setAction(APP_B_ACTION_NAME);
intent.putExtra(REQ_KEY_NAME, data);
context.sendBroadcast(intent);
Log.d(TAG, "GET Data req intent sent");
}
@RequiresApi(api = Build.VERSION_CODES.O)
@Override
public Response serve(IHTTPSession session) {
Map<String, String> params = session.getParms();
if (params.get(REQ_KEY_NAME) != null) {
// send intent to req data to app B
sendIntent(Constants.REQ_TYPE.GET_USER_DATA, params.get(REQ_KEY_NAME));
// wait for response
sleepThread(3);
// check file system for response
String responseData = checkResponse(cwd);
// check for possible timeouts/issues
if (responseData == null || responseData.isEmpty()) {
return newFixedLengthResponse(requestTimedOutJSON);
} else {
return newFixedLengthResponse(responseData);
}
}
return newFixedLengthResponse(invalidRequestTypeJSON);
}
}
all logic in the webserver takes place in server()
. Looking at the differences between a Service
and an IntentService
,
Service vs IntentService in the Android platform
the IntentService
queues intents and processes them in sequence:
yup I commented too soon. I was doing the work onStartCommand instead of onHandleIntent-- looks like onStartCommand is run on the UI thread, however a separate thread is spawned for onHandleIntent execution
IntentService calls that itself, after onHandleIntent() returns, if there is no more work to be done.
The question is:
If the Service
were to be replaced by an IntentService
, could the webserver be started in startService()
and then, later, it would check for intents that would arrive at onHandleIntent()
from the broadcast receiver (this is all in app A)?
The intent service has a server object, it initializes and starts it in startService()
. Then it periodically calls, say, a setter method on the webserver object as it receives intents in onHandleIntent()
.
In other words, does launching the server count as an individual unit of work that must be finished by the intent service in order to proceed with another (e.g. intents received from the broadcast receiver)?