I've created a service class and a worker class that is executed in a separate thread. I would like to setup a communication between them, so the worker could send some status back to the service.
I've tried to convert my worker Thread to HandlerThread and setup a Handler on the service side, but then I don't know how to actually send a message from the worker. It looks like I can't grasp the concept.
Here's my classes (without communication logic):
Service class
package com.example.app;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class ConnectionService extends Service {
protected ConnectionWorker thread;
#Override
public void onCreate() {
super.onCreate();
// Creating a connection worker thread instance.
// Not starting it yet.
this.thread = new ConnectionWorker();
Log.d(this.getClass().getName(), "Service created");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(this.getClass().getName(), "Service started");
// Checking if worker thread is already running.
if (!this.thread.isAlive()) {
Log.d(this.getClass().getName(), "Starting working thread");
// Starting the worker thread.
this.thread.start();
}
return Service.START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
Log.d(this.getClass().getName(), "Stopping thread");
// Stopping the thread.
this.thread.interrupt();
Log.d(this.getClass().getName(), "Stopping service");
super.onDestroy();
Log.d(this.getClass().getName(), "Service destroyed");
}
}
Worker class
package com.example.app;
import android.os.SystemClock;
import android.util.Log;
public class ConnectionWorker extends Thread {
public ConnectionWorker() {
super(ConnectionWorker.class.getName());
}
#Override
public void run() {
super.run();
Log.d(this.getClass().getName(), "Thread started");
// Doing the work indefinitely.
while (true) {
if (this.isInterrupted()) {
// Terminating this method when thread is interrupted.
return;
}
// #todo: send a message to the service
Log.d(this.getClass().getName(), "Doing some work for 3 seconds...");
SystemClock.sleep(3 * 1000);
}
}
}
How do I implement a HandlerThread, send messages from the worker thread and receive them in my service?
I will highly appreciate a code example.
Update
Actually, it looks like I can't use Looper inside of my thread cause it will block the thread execution and I don't want that. Is it possible to send messages from the thread without using Looper?
It looks like I've finally nailed it!
In order to pass data from thread back to a service you will need to do this:
Subclass a Handler class inside of your service (call it e.g. a LocalHandler). You will have to make it static. Override a handleMessage method, it will receive messages from the thread.
Add a Handler argument to your Thread constructor. Instantiate your LocalHandler class in a service and inject it to your thread via constructor.
Save reference to the Handler inside of your thread and use it to send messages whenever appropriate.
Here's the complete example:
Service Class
package com.example.app;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
public class ConnectionService extends Service {
protected ConnectionWorker thread;
static class LocalHandler extends Handler {
#Override
public void handleMessage(Message msg) {
Log.d(this.getClass().getName(), "Message received: " + (String) msg.obj);
}
}
protected Handler handler;
#Override
public void onCreate() {
super.onCreate();
// Instantiating overloaded handler.
this.handler = new LocalHandler();
// Creating a connection worker thread instance.
// Not starting it yet.
// Injecting our handler.
this.thread = new ConnectionWorker(this.handler);
Log.d(this.getClass().getName(), "Service created");
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(this.getClass().getName(), "Trying to start the service");
// Checking if worker thread is already running.
if (!this.thread.isAlive()) {
Log.d(this.getClass().getName(), "Starting working thread");
// Starting the worker thread.
this.thread.start();
Log.d(this.getClass().getName(), "Service started");
}
return Service.START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
Log.d(this.getClass().getName(), "Stopping thread");
// Stopping the thread.
this.thread.interrupt();
Log.d(this.getClass().getName(), "Stopping service");
super.onDestroy();
Log.d(this.getClass().getName(), "Service destroyed");
}
}
Worker Class (Thread)
package com.example.app;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
public class ConnectionWorker extends Thread {
// Reference to service's handler.
protected Handler handler;
public ConnectionWorker(Handler handler) {
super(ConnectionWorker.class.getName());
// Saving injected reference.
this.handler = handler;
}
#Override
public void run() {
super.run();
Log.d(this.getClass().getName(), "Thread started");
// Doing the work indefinitely.
while (true) {
if (this.isInterrupted()) {
// Terminating this method when thread is interrupted.
return;
}
Log.d(this.getClass().getName(), "Doing some work for 3 seconds...");
// Sending a message back to the service via handler.
Message message = this.handler.obtainMessage();
message.obj = "Waiting for 3 seconds";
this.handler.sendMessage(message);
SystemClock.sleep(3 * 1000);
}
}
}
I hope it's a valid implementation. If you now a better approach - please let me know.
Slava, actually, your answer doesn't work. Let me explain why.
Why does Android have HandlerThread when Thread already exists?
This is because as the documentation says, HandleThread is a
Handy class for starting a new thread that has a looper. The looper
can then be used to create handler classes. Note that start() must
still be called.
How is a Looper used to create a Handler?
A Looper should be passed in the Handler's constructor to tell the Handler which Looper it is working with.
So each HandlerThread has one Looper and there can be many Handlers handling messages the Looper loops through.
When you created a new Handler in your Service class using the default constructor, the Handler gets associated to the current Looper, as explained in it's documentation.
Handler ()
Default constructor associates this handler with the Looper for the
current thread. If this thread does not have a looper, this handler
won't be able to receive messages so an exception is thrown.
You can confirm the problem still exists using by printing this
Thread.currentThread().getId()
at appropriate places - within the Service's onCreate(), within the Handler's handleMessage() and within the run() of ConnectionWorker. How to solve your problem then?
You can create a HandlerThread, even within the service, and when it's Looper is ready, create the Handler using that Looper.
new HandlerThread("ConnectionWorker") {
#Override
protected void onLooperPrepared() {
new Handler(getLooper()).post(new Runnable() {
#Override
public void run() {
// do your stuff
getLooper().quitSafely();
}
});
}
}.start();
Hope it isn't too late an answer :P
Related
I am trying to make background service that will run 15 sec after user closes tha app, I have done service that runs 15 sec (loop with Logs), bud when I close tha app, then it stopes
and another problem is, when I try to stop it from main activity by stopService(intent); then the onDestroy method is called, but thread with loop continues
.. please can someone help me?
*sorry for my english - no native :D
public class NotificationService extends Service {
final private class MyThread implements Runnable {
int service_id;
MyThread(int service_id) {
this.service_id = service_id;
}
#Override
public void run() {
synchronized (this) {
for (int i = 0; i < 15; i++) {
try {
wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.e("onStartCommand", "loop:" + i);
}
stopSelf(service_id);
}
}
}
Thread thread;
#Override
public void onCreate() {
super.onCreate();
Toast.makeText(this, "onCreate", Toast.LENGTH_SHORT).show();
}
#Override
public int onStartCommand(#Nullable Intent intent, int flags, int startId) {
Log.e("onStartCommand", "started");
Toast.makeText(this, "onStartCommand", Toast.LENGTH_SHORT).show();
thread = new Thread(new MyThread(startId));
thread.start();
return START_STICKY;
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
Log.e("onDestroy", "onDestroy");
Toast.makeText(this, "onDestroy", Toast.LENGTH_SHORT).show();
super.onDestroy();
}
}
I am trying to make background service that will run 15 sec after user closes tha app, I have done service that runs 15 sec (loop with Logs), bud when I close tha app, then it stopes
Your code only starts the loop thread when startService(yourNotificationService)is called on the Activity or Broadcast Receiverthat is responsible for calling it does so. It then kills itself with stopSelf(service_id).
If, after you have returned from onStartCommand(), you immediately kill the app without calling stopSelf(service_id) (i.e. your 15 seconds is not up), then your Service will MOST LIKELY restart itself given the START_STICKY return value. However, after you call stopSelf(service_id) you are telling the Service to kill itself; after you close your app, there is nothing to tell your Service to restart through the onStartCommand() call.
and another proble is, when I try to stop it from main activity by stopService(intent); then the onDestroy method is called, but thred with loop continues
A Service is an Android component; it is not another process or thread, it runs in the same process and thread as the main UI thread unless you specify otherwise, as seen here.
Note that services, like other application objects, run in the main thread of their hosting process. This means that, if your service is going to do any CPU intensive (such as MP3 playback) or blocking (such as networking) operations, it should spawn its own thread in which to do that work. More information on this can be found in Processes and Threads. The IntentService class is available as a standard implementation of Service that has its own thread where it schedules its work to be done.
In your case, calling stopService(intent) tells the Service to stop itself, which it does. It does not stop the Thread you started (the MyThread instance). To do that, you must first make your Thread interruptible; see here to do that. Once you do that, you need to change your onDestroy() code to actually interrupt the MyThread instance, as here
#Override
public void onDestroy() {
Log.e("onDestroy", "onDestroy");
Toast.makeText(this, "onDestroy", Toast.LENGTH_SHORT).show();
thread.interrupt();
super.onDestroy();
}
i have made a java server and java client programms that communicate with TCP sockets sending String messages. Those java programs work perfectly. The client logic is that it has a UI and a thread, which is waiting all the time for new messages and updates the UI accordingly what message it recieved (e.x. add buttons,set buttons visible, change texts).
Now, im totally new to android and i want to make an equal client for android but i faced those problems:
1) I can't update the UI from a background thread just passing parameters(like java).
2) I want that one thread to update 2 or 3 Activies(i need 2-3 because the lack of screen space)
I have read about AsyncTask but they say it is recommended for sort tasks, then i read about threads with handlers but they say is difficult to work with and i got confused. So my question is what method should i use on android to achieve that "message listener/UI updater" thread.
UPDATE: Ok, i used threads and handlers and my main activity got updated, but now how i can update the second Activitie from that thread too??
well, you can start a thread for some specific time and after that check if there is any message from server, if not, restart the thread or else do your work. hope this is helpful.
you can implement it like this
package com.example.a41264.myapplication;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Created by 41264 on 04/23/17.
*/
public class Client implements Runnable{
final CopyOnWriteArrayList<MsgListener> listeners = new CopyOnWriteArrayList<>();
#Override
public void run() {
Socket socket = new Socket();
try {
int yourPort = 0;
SocketAddress address = new InetSocketAddress("your ip",yourPort);
socket.connect(address);
int your_buffer_size = 1024;
byte[] buffers = new byte[your_buffer_size];
InputStream is = socket.getInputStream();
int resultLength;
String msg;
while(true){
resultLength = is.read(buffers);
msg = new String(buffers,0,resultLength);
if(listeners.size() != 0){
for(MsgListener msgListener: listeners){
msgListener.onMsgRecived(msg);
}
}
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public void addListener(MsgListener listener){
listeners.add(listener);
}
public void removeListener(MsgListener listener){
listeners.remove(listener);
}
public interface MsgListener{
void onMsgRecived(String msg);
}
}
and do your work at activity like this
package com.example.a41264.myapplication;
import android.app.Activity;
import android.util.Log;
/**
* Created by 41264 on 04/23/17.
*/
public class YourActivity extends Activity{
private Client client; //you need to get your clent;
private Client.MsgListener msgListener = new Client.MsgListener() {
#Override
public void onMsgRecived(final String msg) {
runOnUiThread(new Runnable() {
#Override
public void run() {
//do your own work in main thread
Log.i("tag",msg);
}
});
}
};
#Override
protected void onRestart() {
super.onRestart();
client.addListener(msgListener);
}
#Override
protected void onStop() {
super.onStop();
client.removeListener(msgListener);
}
}
I'm using the ServletContextListener to create a new thread.
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import java.util.concurrent.*;
public class Port implements ServletContextListener {
private ExecutorService executor;
public void contextDestroyed(ServletContextEvent event) {
executor.shutdown();
}
public void contextInitialized(ServletContextEvent event) {
// start task
executor = Executors.newSingleThreadExecutor();
executor.submit(new Task()); //task should implement Runnable!
}
}
Inside this thread I'm reading data from a serial port (SerialPortEventListener). The task.class should read out information from the serial port during the whole period in which the server is active. I've thrown this inside a thread because there can only be one instance that reads from the serial port; data should then be shared to all clients.
Now I would like to acces the data this thread is reading from the serial port.
Can this be done? And if yes then how?
You could, for example, store the read data in a servlet context attribute. Then, from the other classes, you would get the attribute from the servlet context:
public void contextInitialized(final ServletContextEvent event) {
// start task
executor = Executors.newSingleThreadExecutor();
executor.submit(new Runnable() {
#Override
public void run() {
String data = readFromPort();
event.getServletContext().setAttribute("serialPortData", data);
}
});
}
}
Yes it can be done, and you have few options:
1- Using a shared concurrent.BlockingQueue where inside the thread add new data from the SerialPort and in your servlet read from that queue
2- Have an event listener object inside your servlet and pass it in your task constructor. The listener object should have a callback function that is invoked when SerialEvent occur.
In general, this is a typical producer/consumer pattern
You'll need to share the data in the new Runnable you're going to create. You can add to it a concurrent collection.
Let's imagine we have a simple Android application, which connects to a remote service via IPC, schedules a relatively long task, then continues working while awaiting for callback with some results. AIDL interfaces:
IRemoteService.aidl
package com.var.testservice;
import com.var.testservice.IServCallback;
interface IRemoteService {
void scheduleHeavyTask(IServCallback callback);
}
IRemoteService.aidl
package com.var.testservice;
interface IServCallback {
void onResult(int result);
}
Code for activity:
package com.var.testclient;
import com.var.testservice.IServCallback;
import com.var.testservice.IRemoteService;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
public class MainActivity extends Activity {
private static final String TAG = "TestClientActivity";
private IServCallback.Stub servCallbackListener = new IServCallback.Stub(){
#Override
public void onResult(int result) throws RemoteException {
Log.d(TAG, "Got value: " + result);
}
};
private ServiceConnection servConnection = new ServiceConnection(){
#Override
public void onServiceConnected(ComponentName name, IBinder binder) {
service = IRemoteService.Stub.asInterface(binder);
}
#Override
public void onServiceDisconnected(ComponentName name) {
service = null;
}
};
private IRemoteService service;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(!bindService(new Intent(IRemoteService.class.getName()), servConnection, Context.BIND_AUTO_CREATE)){
Log.d(TAG, "Service binding failed");
} else {
Log.d(TAG, "Service binding successful");
}
}
#Override
protected void onDestroy() {
if(service != null) {
unbindService(servConnection);
}
super.onDestroy();
}
public void onButtonClick(View view){
Log.d(TAG, "Button click");
if(service != null){
try {
service.scheduleHeavyTask(servCallbackListener);
} catch (RemoteException e) {
Log.d(TAG, "Oops! Can't schedule task!");
e.printStackTrace();
}
}
}
}
Code for service:
package com.var.testservice;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.util.Log;
public class TestService extends Service {
private static final String TAG = "TestService";
class TestServiceStub extends IRemoteService.Stub {
private IServCallback servCallback;
//These 2 fields will be used a bit later
private Handler handler;
private int result;
//The simpliest implementation. In next snippets I will replace it with
//other version
#Override
public void scheduleHeavyTask(IServCallback callback)
throws RemoteException {
servCallback = callback;
result = doSomethingLong();
callback.onResult(result);
}
private int doSomethingLong(){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
return 42;
}
}
}
#Override
public IBinder onBind(Intent intent) {
return new TestServiceStub();
}
}
This version, while being really dumb (it makes UI thread from application hang for 5 seconds, causing ANR), it successfully executes all calls via IPC, delivering result to activity.
Problems start if I try to put calculations into separate thread:
#Override
public void scheduleHeavyTask(IServCallback callback)
throws RemoteException {
servCallback = callback;
Runnable task = new Runnable(){
#Override
public void run() {
result = doSomethingLong();
try {
Log.d(TAG, "Sending result!");
servCallback.onResult(result);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
new Thread(task).start();
}
In this case the callback is just not delivered to activity: service successfully calls servCallback.onResult(result);, but nothing is called within activity. No exceptions, no clues, no survivors: perfect invocation murder. I couldn't find any information about possible cause of such behavior, so I'd be grateful if someone could clarify what happens here. My suggestion's that there's some kind of security mechanism, tracking which exact threads were bound, and ignoring "unsafe" calls from other threads (something similar happens when we try to mess with UI elements from non-UI thread), but I can't be sure.
The most obvious solution is to post callback invocation to the bound thread, so I made this:
#Override
public void scheduleHeavyTask(IServCallback callback)
throws RemoteException {
Log.d(TAG, "Schedule request received.");
servCallback = callback;
if(Looper.myLooper() == null) {
Looper.prepare();
}
handler = new Handler();
Runnable task = new Runnable(){
#Override
public void run() {
result = doSomethingLong();
Log.d(TAG, "Posting result sender");
handler.post(new Runnable(){
#Override
public void run() {
try {
Log.d(TAG, "Sending result!");
servCallback.onResult(result);
} catch (RemoteException e) {
e.printStackTrace();
}
Looper.myLooper().quit();
Log.d(TAG, "Looper stopped");
}
});
}
};
new Thread(task).start();
Looper.loop();
}
Here I faced 2 more problems:
I had to call Looper.loop() to enable processing of callback runnables, but it blocks IPC, so I have the same result as in the beginning - no actual multithreading;
Registering for callback second time (after first cycle finished and returned value) results in exception:
java.lang.RuntimeException: Handler (android.os.Handler) sending message to a Handler on a dead thread
at android.os.MessageQueue.enqueueMessage
at android.os.Handler.sendMessageAtTime
at android.os.Handler.sendMessageDelayed
at android.os.Handler.post
at com.var.testservice.TestService$TestServiceStub$1.run
at java.lang.Thread.run
This lefts me completely puzzled: I make a fresh instance from actual Looper, how can it point to dead thread?
The whole idea of service being able of queueing tasks and making callbacks when they finish doesn't sound insane to me, so I hope someone more experienced could explain me:
Why can't I actually make IPC calls from different threads?
What's wrong with my Handler?
What instruments/architecture should I use to make a clean, proper queue mechanism, so it could call IPC methods on the right thread without constantly calling Looper.loop()/Looper.quit()?
Thank you.
I can't explain why your program isn't working. But the version involving threads and an asynchronous callback:
#Override
public void scheduleHeavyTask(IServCallback callback)
throws RemoteException {
servCallback = callback;
Runnable task = new Runnable(){
#Override
public void run() {
result = doSomethingLong();
try {
Log.d(TAG, "Sending result!");
servCallback.onResult(result);
} catch (RemoteException e) {
e.printStackTrace();
}
}
};
new Thread(task).start();
}
should work just fine.
Here's how Android arranges threads for AIDL and other types of Binder transaction.
If the caller and the callee are in the same process, it becomes a simple method call. So, you should fine that scheduleHeavyTask is called from the very same thread as onButtonClick. Similarly, you should fine that the call to onResult should be a simple method call, from the thread running the task.
If the caller and callee are in different processes, Android will run the Binder transactions from a pool of threads within the callee process (they are called Binder #1 etc.) Even here it's quite clever - so if onButtonClick called scheduleHeavyTask which called back to onResult from the same thread, then onButtonClick would appear directly to call onResult within the caller process.
There are absolutely no mechanisms to avoid calls from "unsafe" or "unbound" threads - so this simple approach you posted should work. As you say, it's a common pattern, and I've used it lots of times. I'd therefore recommend proceeding with this rather than fiddling with extra loopers and handlers.
Here are some ideas:
Run it in a debugger. Assuming your service and activity are in the same process, you should be able to set breakpoints and see sensible method calls taking place.
Even if you can't, fire up ddms from the command line, or show the Devices and Threads views within Eclipse. This will give you a view of exactly what each thread is doing - you can get a callstack. It's normally possible to use this even in cases where a full-on debugger would be inconvenient.
Do you have the word synchronized anywhere? What could be happening is that onResult wants to go ahead and run, but it's blocked on some monitor. As soon as it becomes unblocked, it might well run.
I'm developing a chat application in Android and have run into a massive problem. I need a thread to constantly run in the background (polling a server), and have attached it to my main process via a Handle.
The main problem is: As long as this background thread is running, the foreground one grinds to a complete halt!
Here is an incomplete chunk of code (because the full version is much longer/uglier)...
public class ChatActivity extends Activity {
...
private Thread chatUpdateTask;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat);
...
chatUpdateTask = new ChatUpdateTask(handler);
chatUpdateTask.start();
}
public void updateChat(JSONObject json) {
// ...
// Updates the chat display
}
// Define the Handler that receives messages from the thread and update the progress
final Handler handler = new Handler() {
public void handleMessage(Message msg) {
// Get json from the sent Message and display it
updateChat(json);
}
};
public class ChatUpdateTask extends Thread {
Handler mHandler; // for handling things outside of the thread.
public ChatUpdateTask(Handler h) {
mHandler = h; // When creating, make sure we request one!
}//myTask
#Override
public void start() {
while(mState==STATE_RUNNING) {
// ...
// Send message to handler here
Thread.sleep(500); // pause on completion
}//wend
}//end start
/* sets the current state for the thread,
* used to stop the thread */
public void setState(int state) {
mState = state;
}//end setState
public JSONObject getChatMessages() {
// ... call server, return messages (could take up to 50 seconds to execute;
// server only returns messages when there are new ones
return json;
}
}//end class myTask
}
You're overriding start(). Threads run in their run() method.
http://docs.oracle.com/javase/tutorial/essential/concurrency/runthread.html