I am using the updated version of this answer to link (bind) an activity to a service.
MyService.java
public class MyService extends Service {
private LocalBinder binder = new LocalBinder();
private Observable<Integer> responseObservable;
private ObservableEmitter<Integer> responseObserver;
public static boolean isRunning = false;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return binder;
}
#Override
public int onStartCommand(#Nullable Intent intent, int flags, int startId) {
GsonConverterFactory factory = GsonConverterFactory.create(new GsonBuilder()
.setLenient()
.create());
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
Client client = new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(new OkHttpClient.Builder()
.addInterceptor(interceptor)
.build())
.addConverterFactory(factory)
.build()
.create(Client.class);
for (//some loop) {
Response<Result> response = client.search(//some params here)
.execute();
responseObserver.onNext(response.code());
}
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
isRunning = false;
}
public Observable<Message> observeResponse() {
if (responseObservable == null) {
responseObservable = Observable.create(em -> responseObserver = em);
responseObservable = responseObservable.share();
}
return responseObservable;
}
public class LocalBinder extends Binder {
public DService getService() {
return MyService.this;
}
}
}
MainActivity.java
public class MainActivityextends AppCompatActivity {
private MyService service;
private Disposable disposable;
private ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
service = ((MyService.LocalBinder) iBinder).getService();
disposable = service.observeResponse()
.observeOn(Schedulers.newThread())
.subscribe(responseCode -> updateUI()); //this must run on main thread
startService(new Intent(MainActivity.this, MyService.class));
}
#Override
public void onServiceDisconnected(ComponentName componentName) {
}
};
#Override
protected void onDestroy() {
if (disposable != null)
disposable.dispose();
unbindService(serviceConnection);
super.onDestroy();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
//....
Button start = findViewById(R.id.start);
start.setOnClickListener(v -> {
Intent intent = new Intent(this, MyService.class);
bindService(intent, serviceConnection, BIND_AUTO_CREATE);
});
//....
}
}
If I use observeOn(AndroidSchedulers.mainThread()), I get NetworkOnMainThreadException, and if I use observeOn(Schedulers.newThread()), I get OnErrorNotImplementedException: Only the original thread that created a view hierarchy can touch its views.
I know what both errors mean, in normal cases I can resolve them easily, but here nothing I normally do work.
I need the network requests in the service to be executed synchronously because it is in a loop, and I treat each request result in order, asynchronous calls are not an option for me.
I tried runOnUiThread(() -> updateUI()), but it produces the same error. I also tried to execute the service on a new thread, but still the same error too.
First of Service runs on Main Thread
A service runs in the main thread of its hosting process; the service
does not create its own thread and does not run in a separate process
unless you specify otherwise. If your service is going to perform any
CPU-intensive work or blocking operations, such as MP3 playback or
networking, you should create a new thread within the service to
complete that work. By using a separate thread, you can reduce the
risk of Application Not Responding (ANR) errors, and the application's
main thread can remain dedicated to user interaction with your
activities. REFERENCE
So, making api calls in Service directly will cause NetworkOnMainThreadException in all cases.
When you put observeOn(AndroidSchedulers.mainThread()), you are definitly bound to have NetworkOnMainThreadException; reason specified above
When you put observeOn(Schedulers.newThread()), the api call in service causes NetworkOnMainThreadException; but since you have used Rx it returns an error message to its subscriber; but in your case, you have not added error part.
You have used:
subscribe(responseCode -> updateUI());
to prevent app crash, you have to use
subscribe(responseCode -> updateUI(), error -> error.printStackTrace());
Now to fix the issue:
In service, make sure to call API on new Thread in Service;
OR
You can also try to make API call using reference to another class(like Presenter in MVP), where you make API calls and send response to UI directly using :
service.observeResponse()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(responseCode -> view.updateUI(), error -> view.displayError())
A common approach for checking if you have bounded to a service in Android is keeping a boolean parameter such as mBound in your binding steps as described in Android developers guide. I noticed a problem in this Android reference tutorial and I think this approach is somehow a bad practice. Here is the code from Bound Services:
Here is a code block with language code as hint:
public class ActivityMessenger extends Activity {
Messenger mService = null;
boolean mBound;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
mBound = false;
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
#Override
protected void onStart() {
super.onStart();
// Bind to the service
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
Ok, Here is the problem. Let's assume that for any reason OnStop() is called immediately after OnStart(). You can create this situation by placing a breakpoint in OnCreate() and wait for your phone screen goes dark that forces an application recreation and then continue application run.
If this situation happens, the mBount variable will be still false when you run OnStop(), because service connection's onServiceConnected() is not called yet. So you will not call unbindService() and you will have a service that you have binded and never unbinded.
Any suggestions for a better approach for Binding services? I wonder if calling unbideService() in any situation is enough or not(for example calling even though service is not binded right now).
Best Regards
I haven't tried this in practice, but it seems reasonable to set mBound = true already when calling bindService instead of in the callback. Actually, the documentation says that you should call unbindService even if bindService returned false (meaning you would never get any onServiceConnected call and mBound would never be true):
Note: If the method returns false, your client does not have a valid
connection to the service. However, your client should still call
unbindService(); otherwise, your client will keep the service from
shutting down when it is idle.
With this in mind it's clear that only setting mBound = true in onServiceConnected is not sufficient (or at least recommended). I would suspect that calling unbindService when not previously bound is a no-op, but that might need some confirmation. If so, setting mBound = true when calling bindService seems like a good approach.
I have got an Android service that could be unavailable at some point.
If my Activity tries to call it, I want to know if the service is available.
My activity code is:
boolean bound = context.bindService(new Intent(SERVICE_NAME), mConnection, Context.BIND_AUTO_CREATE);
if (!bound) {
// ....
}
My service code is:
#Override
public void onCreate() {
mServiceEnabled = false;
// calling some C code
boolean readEnabled = JniCommunicator.enableRead();
if (!readEnabled) {
return;
}
// potentially return without reaching that point
mServiceEnabled = true;
}
public IBinder onBind(Intent intent) {
Log.d("Service", mServiceEnabled ? "enabled" : "DISABLED");
return mServiceEnabled ? serviceBinder : null;
}
As I understood, returning null would not bind the service, and my activity would be able to see it, but my bound variable is always true. And my mConnection callback is never called.
What is wrong?
Note that the service and the activity are in two separate APKs signed with two separate keys. I cannot use a static boolean to solve that.
Based on a suggestion in a previous question I asked on here, I'm trying to push my socket connection for an application that I've written into a service. I spent the better part of the day yesterday researching services and actually mocked up a few (one remote, one local).
My question is in two parts:
1) after having played with both a local service and a remote service, I'm still not sure as to which one would be best for my situation. This is due in large part to the fact that I guess I still don't quite understand what advantages running in another 'process' is going to give me. I'm spawning a new thread for the socket connection no matter what so I won't have any thread contention with the UI. So what does putting the service in another process enable me to do? Will I potentially see better performance that way? My limited understanding is that by putting it in a different process, the service will run independently of whatever activity I have running on my app. I do have a few different activities, but only one of them requires the socket connection which I will rebuild everytime that activity is opened anyway. So would a local service be the way to go for me?
2) I'm going to have my socket "listener" (DataInputStream().readLine() inside a while loop) inside my service for any new data that gets passed down from the server. After the playing I did yesterday, I could not figure out how to pass the data that it reads to the actual "client" (either bound client by remote service, or local client itself) in "realtime".
Would greatly appreciate some suggestions for part 1, and some help with part 2 (code examples? :))
TIA
Edit: added code of my service - going with local service
Service Class:
public class SocketService extends Service {
Socket s;
PrintStream os;
#Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return myBinder;
}
private final IBinder myBinder = new LocalBinder();
public class LocalBinder extends Binder {
public SocketService getService() {
return SocketService.this;
}
}
#Override
public void onCreate() {
super.onCreate();
s = new Socket();
}
public void IsBoundable(){
Toast.makeText(this,"I bind like butter", Toast.LENGTH_LONG).show();
}
public void onStart(Intent intent, int startId){
super.onStart(intent, startId);
Toast.makeText(this,"Service created ...", Toast.LENGTH_LONG).show();
Runnable connect = new connectSocket();
new Thread(connect).start();
}
class connectSocket implements Runnable {
#Override
public void run() {
SocketAddress socketAddress = new InetSocketAddress("192.168.1.104", 4505);
try {
s.connect(socketAddress);
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
public void onDestroy() {
super.onDestroy();
try {
s.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
s = null;
}
}
Activity that calls service:
public class SocketServiceController extends Activity {
private SocketService mBoundService;
private Boolean mIsBound;
public SocketServiceController ssc;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ssc = this;
setContentView(R.layout.main);
Button start = (Button)findViewById(R.id.serviceButton);
Button stop = (Button)findViewById(R.id.cancelButton);
start.setOnClickListener(startListener);
stop.setOnClickListener(stopListener);
}
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mBoundService = ((SocketService.LocalBinder)service).getService();
}
public void onServiceDisconnected(ComponentName className) {
mBoundService = null;
}
};
private void doBindService() {
bindService(new Intent(SocketServiceController.this, SocketService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
mBoundService.IsBoundable();
}
private void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
#Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
private OnClickListener startListener = new OnClickListener() {
public void onClick(View v){
startService(new Intent(SocketServiceController.this,SocketService.class));
doBindService();
}
};
private OnClickListener stopListener = new OnClickListener() {
public void onClick(View v){
stopService(new Intent(SocketServiceController.this,SocketService.class));
}
};
}
This is due in large part to the fact that I guess I still don't quite understand what advantages running in
another 'process' is going to give me.
Generally, none. You create a remote service if you are expecting other applications to communicate with the service. If it will only be used by your own application, use a local service.
Also, a remote service has nothing to do with creating a separate process within your application.
Will I potentially see better performance that way?
You will see worse performance that way, due to extra memory consumption.
My limited understanding is that by putting it in a different process, the service will run independently of
whatever activity I have running on my app.
Services have a lifecycle independent from activities regardless of whether it is local or remote.
So would a local service be the way to go for me?
Sounds likely.
After the playing I did yesterday, I could not figure out how to pass the data that it reads to the
actual "client" (either bound client by remote service, or local client itself) in "realtime".
Use the local binding pattern, and have the activity call an API on the service to register (and unregister) an event listener. Have the service pass the data to the activity via the listener.
I have an Activity calling a Service defined in IDownloaderService.aidl:
public class Downloader extends Activity {
IDownloaderService downloader = null;
// ...
In Downloader.onCreate(Bundle) I tried to bindService
Intent serviceIntent = new Intent(this, DownloaderService.class);
if (bindService(serviceIntent, sc, BIND_AUTO_CREATE)) {
// ...
and within the ServiceConnection object sc I did this
public void onServiceConnected(ComponentName name, IBinder service) {
Log.w("XXX", "onServiceConnected");
downloader = IDownloaderService.Stub.asInterface(service);
// ...
By adding all kinds of Log.xx I found that the code after if(bindService(...)) actually goes BEFORE ServiceConnection.onServiceConnected is being called - that is, when downloader is still null - which gets me into trouble. All the samples in ApiDemos avoid this timing problem by only calling services when triggered by user actions. But what should I do to right use this service after bindService succeeds? How can I wait for ServiceConnection.onServiceConnected being called reliably?
Another question related. Are all the event handlers: Activity.onCreate, any View.onClickListener.onClick, ServiceConnection.onServiceConnected, etc. actually called in the same thread (mentioned in the doc as the "main thread")? Are there interleaves between them, or Android would schedule all events come into being handled one-by-one? Or, When exactly is ServiceConnection.onServiceConnected actually going to be called? Upon completion of Activity.onCreate or sometime when A.oC is still running?
How can I wait for
ServiceConnection.onServiceConnected
being called reliably?
You don't. You exit out of onCreate() (or wherever you are binding) and you put you "needs the connection established" code in onServiceConnected().
Are all the event handlers:
Activity.onCreate, any
View.onClickListener.onClick,
ServiceConnection.onServiceConnected,
etc. actually called in the same
thread
Yes.
When exactly is
ServiceConnection.onServiceConnected
actually going to be called? Upon
completion of Activity.onCreate or
sometime when A.oC is still running?
Your bind request probably is not even going to start until after you leave onCreate(). Hence, onServiceConnected() will called sometime after you leave onCreate().
I had the same problem. I didn't want to put my bound service dependent code in onServiceConnected, though, because I wanted to bind/unbind with onStart and onStop, but I didn't want the code to run again every time the activity came back to the front. I only wanted it to run when the activity was first created.
I finally got over my onStart() tunnel vision and used a Boolean to indicate whether this was the first onServiceConnected run or not. That way, I can unbindService in onStop and bindService again in onStart without running all the start up stuff each time.
I ended up with something like this:
1) to give the auxiliary stuff some scope, I created an internal class. At least, the ugly internals are separated from the rest of the code. I needed a remote service doing something, therefore the word Something in class name
private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper();
class RemoteSomethingHelper {
//...
}
2) there are two things necessary to invoke a remote service method: the IBinder and the code to execute. Since we don't know which one becomes known first, we store them:
private ISomethingService mISomethingService;
private Runnable mActionRunnable;
Each time we write to one of these fileds, we invoke _startActionIfPossible():
private void _startActionIfPossible() {
if (mActionRunnable != null && mISomethingService != null) {
mActionRunnable.run();
mActionRunnable = null;
}
}
private void performAction(Runnable r) {
mActionRunnable = r;
_startActionIfPossible();
}
This, of course, assumes that the Runnable has access to mISomethingService, but this is true for runnables created within the methods of the RemoteSomethingHelper class.
It is really good that the ServiceConnection callbacks are called on the UI thread: if we are going to invoke the service methods from the main thread, we do not need to care about synchronization.
ISomethingService is, of course, defined via AIDL.
3) Instead of just passing arguments to methods, we create a Runnable that will invoke the method with these arguments later, when invocation is possible:
private boolean mServiceBound;
void startSomething(final String arg1) {
// ... starting the service ...
final String arg2 = ...;
performAction(new Runnable() {
#Override
public void run() {
try {
// arg1 and arg2 must be final!
mISomethingService.startSomething(arg1, arg2);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
4) finally, we get:
private RemoteSomethingHelper mRemoteSomethingHelper = new RemoteSomethingHelper();
class RemoteSomethingHelper {
private ISomethingService mISomethingService;
private Runnable mActionRunnable;
private boolean mServiceBound;
private void _startActionIfPossible() {
if (mActionRunnable != null && mISomethingService != null) {
mActionRunnable.run();
mActionRunnable = null;
}
}
private ServiceConnection mServiceConnection = new ServiceConnection() {
// the methods on this class are called from the main thread of your process.
#Override
public void onServiceDisconnected(ComponentName name) {
mISomethingService = null;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
mISomethingService = ISomethingService.Stub.asInterface(service);
_startActionIfPossible();
}
}
private void performAction(Runnable r) {
mActionRunnable = r;
_startActionIfPossible();
}
public void startSomething(final String arg1) {
Intent intent = new Intent(context.getApplicationContext(),SomethingService.class);
if (!mServiceBound) {
mServiceBound = context.getApplicationContext().bindService(intent, mServiceConnection, 0);
}
ComponentName cn = context.getApplicationContext().startService(intent);
final String arg2 = ...;
performAction(new Runnable() {
#Override
public void run() {
try {
mISomethingService.startSomething(arg1, arg2);
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
context is a field in my class; in an Activity, you can define it as Context context=this;
I did not need queuing actions; if you do, you can implement it.
You likely will need a result callback in startSomething(); I did, but this is not shown in this code.
I did something similar before, the only different is I was not binding to service, but just starting it.
I would broadcast an intent from the service to notify the caller/activity about it is started.
I wanted to add some things you should or should not do:
bind the service not on create but onResume and unbind it onPause. Your app can go into pause (background) at any time by user interaction or OS-Screens.
Use a distinct try/catch for each and every service unbinding, receiver unregistering etc in onPause so if one is not bound or registered the exception doesn't prevent the others from being destroyed too.
I usually capsule binding in a public MyServiceBinder getService() Method. I also always use a blocking boolean variable so I don't have to keep an eye on all those calls using the servie in the activity.
Example:
boolean isBindingOngoing = false;
MyService.Binder serviceHelp = null;
ServiceConnection myServiceCon = null;
public MyService.Binder getMyService()
{
if(serviceHelp==null)
{
//don't bind multiple times
//guard against getting null on fist getMyService calls!
if(isBindingOngoing)return null;
isBindingOngoing = true;
myServiceCon = new ServiceConnection(
public void onServiceConnected(ComponentName cName, IBinder binder) {
serviceHelp = (MyService.Binder) binder;
//or using aidl: serviceHelp = MyService.Stub.AsInterface(binder);
isServiceBindingOngoing = false;
continueAfterServiceConnect(); //I use a method like this to continue
}
public void onServiceDisconnected(ComponentName className) {
serviceHelp = null;
}
);
bindService(serviceStartIntent,myServiceCon);
}
return serviceHelp;
}
Android 10 has introduced a new bindService method signature when binding to a service to provide an Executor (which can be created from the Executors).
/**
* Same as {#link #bindService(Intent, ServiceConnection, int)} with executor to control
* ServiceConnection callbacks.
* #param executor Callbacks on ServiceConnection will be called on executor. Must use same
* instance for the same instance of ServiceConnection.
*/
public boolean bindService(#RequiresPermission #NonNull Intent service,
#BindServiceFlags int flags, #NonNull #CallbackExecutor Executor executor,
#NonNull ServiceConnection conn) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
This allows to bind to the service in a thread and wait until it is connected. E.g. stub:
private final AtomicBoolean connected = new AtomicBoolean()
private final Object lock = new Object();
...
private void myConnectMethod() {
// bind to service
ExecutorService executorService = Executors.newSingleThreadExecutor();
context.bindService(new Intent(context, MyServiceClass.class), Context.BIND_AUTO_CREATE, executorService, new
ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder binder) {
synchronized (lock) {
// TODO: store service instance for calls in case of AIDL or local services
connected.set(true);
lock.notify();
}
});
synchronized (lock) {
while (!connected.get()) {
try {
lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException();
}
}
}
}
It is also necessary to run the service in a separate process:
<service
android:name=".MyServiceClass"
android:process=":service"
android:enabled="true"
android:exported="true" />
I figured out that these workarounds are only worth the effort and the wait only if your bound services are running in a different process than your application's main process.
For accessing data and methods in the same process (or application), I ended up implementing singleton classes. If the classes need a context for some methods, I leak the application context to the singleton classes. There is, of course, a bad consequence of it as it breaks the "instant run". But that is an overall better compromise, I think.
*The basic idea is same with #18446744073709551615, but I will share my code as well.
As a answer of main question,
But what should I do to right use this service after bindService succeeds?
[Original expectation (but not work)]
wait until service connected like below
#Override
protected void onStart() {
bindService(service, mWebServiceConnection, BIND_AUTO_CREATE);
synchronized (mLock) { mLock.wait(40000); }
// rest of the code continues here, which uses service stub interface
// ...
}
It won't work because both bindService() in onCreate()/onStart() and onServiceConnected() is called at same main thread.
onServiceConnected() is never called before wait finishes.
[Alternative solution]
Instead of "wait", define own Runnable to be called after Service Connected and execute this runnable after service connected.
Implement custom class of ServiceConnection as follows.
public class MyServiceConnection implements ServiceConnection {
private static final String TAG = MyServiceConnection.class.getSimpleName();
private Context mContext = null;
private IMyService mMyService = null;
private ArrayList<Runnable> runnableArrayList;
private Boolean isConnected = false;
public MyServiceConnection(Context context) {
mContext = context;
runnableArrayList = new ArrayList<>();
}
public IMyService getInterface() {
return mMyService;
}
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.v(TAG, "Connected Service: " + name);
mMyService = MyService.Stub.asInterface(service);
isConnected = true;
/* Execute runnables after Service connected */
for (Runnable action : runnableArrayList) {
action.run();
}
runnableArrayList.clear();
}
#Override
public void onServiceDisconnected(ComponentName name) {
try {
mMyService = null;
mContext.unbindService(this);
isConnected = false;
Log.v(TAG, "Disconnected Service: " + name);
} catch(Exception e) {
Log.e(TAG, e.toString());
}
}
public void executeAfterServiceConnected(Runnable action) {
Log.v(TAG, "executeAfterServiceConnected");
if(isConnected) {
Log.v(TAG, "Service already connected, execute now");
action.run();
} else {
// this action will be executed at the end of onServiceConnected method
Log.v(TAG, "Service not connected yet, execute later");
runnableArrayList.add(action);
}
}
}
And then use it in the following way (in your Activity class or etc),
private MyServiceConnection myServiceConnection = null;
#Override
protected void onStart() {
Log.d(TAG, "onStart");
super.onStart();
Intent serviceIntent = new Intent(getApplicationContext(), MyService.class);
startService(serviceIntent);
myServiceConnection = new MyServiceConnection(getApplicationContext());
bindService(serviceIntent, myServiceConnection, BIND_AUTO_CREATE);
// Instead of "wait" here, create callback which will be called after service is connected
myServiceConnection.executeAfterServiceConnected(new Runnable() {
#Override
public void run() {
// Rest of the code comes here.
// This runnable will be executed after service connected, so we can use service stub interface
IMyService myService = myServiceConnection.getInterface();
// ...
}
});
}
It worked for me. But there may be more better way.