In my application I have a (background) Service which runs in its own process. To communicate with other components I defined some AIDL interfaces:
interface MyService {
void addOnChangeListener(ServiceChangedListener listener);
void removeOnChangeListener(ServiceChangedListener listener);
}
interface ServiceChangedListener {
void onUserChanged(in User newUser);
}
Here is my service with the MyService implementation.
public class UserService extends Service {
private RemoteCallbackList<ServiceChangedListener> listeners;
public class ServiceBinder extends MyService.Stub {
public void addOnChangeListener(ServiceChangedListener listener) {
listeners.register(listener);
}
public void removeOnChangeListener(ServiceChangedListener listener) {
listeners.unregister(listener);
}
}
// all the other implementation...
}
My Activity connects to this Service in onStart and disconnects from it in onStop where it also release the listener which is registered in onServiceConnected.
public class MainAcitivity extends AppCompatActivity {
private ServiceListener listener;
private MyService service;
protected void onStart() {
super.onStart();
Intent intent = new Intent(this, UserService.class);
bindService(intent, this, Context.BIND_AUTO_CREATE);
}
protected void onStop() {
super.onStop();
try { service.removeOnChangeListener(listener); }
catch (RemoteException e) { e.printStackTrace(); }
unbindService(this);
}
public void onServiceConnected(ComponentName className, IBinder service) {
service = MyService.Stub.asInterface(service);
try {
listener = new ServiceListener();
service.addOnChangeListener(listener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
private class ServiceListener extends ServiceChangedListener.Stub {
public void onUserChanged(User u) throws RemoteException {
runOnUiThread(() -> { ... });
}
}
}
The add and remove methods for the listener simply operate on an ArrayList as you would expect so there is no long ongoing work.
LeakCanary as well as the Android studio memory analysis tool tell me that this causes a memory leak. If I comment out the lines affecting the listener no memory leak is detected. Do you have any ideas why?
I finally found the answer! You have to make the inner class static and use a WeakReference within it. Here is an official answer of a Google engineer and here a simplified one. Good luck anyone with the same problem!
Related
Background: I have a few services (open for other apps to use) which run in the same process. The RPC is implemented using AIDL and therefore the services have to be open to multiple threads.
This leads me to the question: can a client still use a binder to make RPC calls even after the client unbound from the service. If so then:
Issue: I am worried that a client (either intentionally or accidentally) will bind to one of the services, unbind (possibly destroying the service because there are no other clients bound to it), then still use the binder to make remote calls. But, because the service is destroyed (resources have been released) the calls may cause exceptions. While some exceptions are implicitly "passed" back to the client (e.g. NullPointerException and IllegalStateException), most aren't and will propagate all the way back and crash the process, which may contain other alive services.
EDIT: a client can use a binder after unbinding (example code below). Now, what is the best way for a destroyed service to handle/respond to these calls (keeping in mind that the calls happen on a different thread than the thread which call onDestroy)?
public class MyService extends Service {
private volatile boolean destroyed = false;
private final IMyServiceInterface.Stub binder = new IMyServiceInterface.Stub() {
#Override
public boolean isDestroyed() {
// even worse, what if... `if (destroyed) { throw new RuntimeException(); }`
return destroyed;
}
};
#Override
public IBinder onBind(Intent intent) { return binder; }
#Override
public void onDestroy() { destroyed = true; }
}
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Button button = (Button) findViewById(R.id.button);
button.setOnClickListener((view) ->
bindService(new Intent(this, MyService.class), serviceConnection, Context.BIND_AUTO_CREATE));
}
private final ServiceConnection serviceConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder binder) {
final IMyServiceInterface service = IMyServiceInterface.Stub.asInterface(binder);
attemptExperiment(service);
}
#Override
public void onServiceDisconnected(ComponentName name) {
throw new AssertionError("service wasn't supposed to crash...");
}
};
private void attemptExperiment(IMyServiceInterface service) {
new Thread(() -> {
Toasts.show(this, "unbinding service and sleeping for 5 seconds...");
unbindService(serviceConnection);
try {
Thread.sleep(TimeUnit.SECONDS.toMillis(5));
} catch (InterruptedException e) {
return;
}
final boolean destroyed;
try {
destroyed = service.isDestroyed();
} catch (RemoteException e) {
Toasts.show(this, "service remote exception");
return;
}
Toasts.show(this, "service destroyed: " + destroyed);
}).start();
}
}
I have seen several similar examples here but can't seem to get my service to bind with activity.
I am getting the error
"android.os.binderproxy cannot be cast to IC_CommissaryService".
My service looks like this:
public class IC_CommissaryService extends Service
{
#Override
public IBinder onBind(Intent intent)
{
return mBinder;
}
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder
{
IC_CommissaryService getService()
{
return IC_CommissaryService.this;
}
}
public int onStartCommand(Intent intent, int flags, int startId)
{
}
private boolean SendOrderToServer(int orderID)
{
/* do stuff*/
}
}
and my activity looks like this:
public class SubmitOrders extends Activity
{
IC_CommissaryService ICservice;
#Override
public void onCreate(Bundle savedInstanceState)
{
Intent serviceintent = new Intent(this, IC_CommissaryService.class);
serviceintent.putExtra("binded", true);
bindService(serviceintent, mConnection, Context.BIND_AUTO_CREATE);
}
private ServiceConnection mConnection = new ServiceConnection()
{
#Override
public void onServiceConnected(ComponentName className, IBinder service)
{
Log.e("TEST", "SERVICE CONNECTED");
try
{
ICservice =(IC_CommissaryService.LocalBinder)service).getService();
for(int i = 0; i < Submitorders.size(); i++)
{
ICservice.SendOrderToServer(Submitorders.get(i).intValue());
}
}
catch(Exception ex)
{
Log.e("Error", "Error connecting service: " + ex.getMessage());
}
}
#Override
public void onServiceDisconnected(ComponentName className)
{
}
};
}
I am getting the error in my activity on the line ICservice =(IC_CommissaryService.LocalBinder)service).getService();
I think I have done the same as people already suggested in other posts so any help please?
thanks
I had the same kind of problem. I just figured it out today. Please look at the parts annotated with <<===== below. I hope it helps.
public class PracticeServiceBindingActivity extends ListActivity {
private MyService.MyBinder service; <<====
....
private ServiceConnection connection = new ServiceConnection( ){
public void onServiceConnected (ComponentName name, IBinder service) {
setService(MyService.MyBinder) service; <<====
....
}
}
public void onCreate(....) {
...
public MyService.MyBinder getService(){ <<=====
return service;
}
public void setService(MyService.MyBinder service) { <<=====
this.service = service;
}
}
}
Quote from the Bound Services documentation:
If your service is private to your own application and runs in the
same process as the client (which is common), you should create your
interface by extending the Binder class and returning an instance of
it from onBind().
Remove the android:process attribute in in AndroidManifest.ml to make the service run in the same process. I had the same problem today it did the trick.
These are the abstract classes that I use to solve this problem:
https://gist.github.com/frenchie4111/6086c6e4327d7936364a
Just extend both these classes with your service and activity (You can change the fragment from a fragment to an activity with ease). And make sure that in your service/fragment onCreate you set the serviceClass like so:
public void onCreate( Bundle savedInstance ) {
super.onCreate( savedInstance );
this.serviceClass = IC_CommissaryService.class;
}
I've got a Sticky Service (returns START_STICKY from onStartCommand) which executes some code in an AsyncTask, but I'm having some problems with how and when to start, bind, stop, unbind. I only want the service around whilst the parent activity is alive, I don't want it hanging around in the background when the app has been closed, but I need the service to survive an orientation change. I currently don't need the service to be active for the entire duration of the activity being active, so I call stopSelf() after the main work is done in my AsyncTask in the Service and then start the Service again when needed. Sometimes I'll need to interrupt the work the service is doing, cancel the AsyncTask and start again with different data. The problem is that no matter what I do - I can't seem to get it solid throughout all the different possible scenarios. Can anyone have a look through and tell me what I'm doing wrong?
My Service is :
public class ChordCalculatorService extends Service {
private final IBinder mBinder = new LocalBinder();
private AsyncTask<SearchData, SearchStatusData, List<Item>> currentTask;
#Override
public void onCreate() {}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
}
/**
* Class for clients to access. Because we know this service always runs in
* the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public ChordCalculatorService getService() {
return ChordCalculatorService.this;
}
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public SearchData getSearchData() {
return searchData;
}
public void startWork() {
if (currentTask != null && currentTask.getStatus() == Status.RUNNING) {
currentTask.cancel(true);
}
if(searchData != null) {
Worker task = new Worker();
currentTask = task.execute(new SearchData[] { searchData });
} else {
Message msg = handler.obtainMessage(ERROR, "No search data set");
handler.sendMessage(msg);
}
}
class Worker extends AsyncTask<SearchData, SearchStatusData, List<Item>> {
// ... code ...
#Override
protected void onPostExecute(List<Item> result) {
Message msg = handler.obtainMessage(COMPLETE, new StatusData(Status.STATUS_FINISHED, result));
handler.sendMessage(msg);
stopSelf();
}
}
}
Currently I have the Service being started when my custom View is created:
public class MyCustomView extends BasicFretBoardView {
private ServiceConnection conn;
private MyService myService;
private boolean isServiceStarted;
private boolean isServiceBound;
public MyCustomView(Context context, AttributeSet attr) {
super(context, attr);
startService();
}
public void startService() {
Intent serviceIntent = new Intent(getContext(), MyService.class);
conn = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName name, IBinder service) {
myService = ((LocalBinder) service).getService();
myService.registerHandler(serviceHandler);
}
#Override
public void onServiceDisconnected(ComponentName name) {
myService = null;
}
};
// Explicitly start the service. Don't use BIND_AUTO_CREATE, since it
// causes an implicit service stop when the last binder is removed.
getContext().startService(serviceIntent);
getContext().bindService(serviceIntent, conn, 0);
isServiceStarted = true;
isServiceBound = true;
}
public void stopService() {
if (isServiceStarted) {
Intent serviceIntent = new Intent(getContext(), MyService.class);
getContext().stopService(serviceIntent);
isServiceStarted = false;
}
unBindService();
}
public void unBindService() {
if(isServiceBound) {
getContext().unbindService(conn);
isServiceBound = false;
}
}
// gets called based on some user interaction
private void startServiceWork() {
if(!isServiceStarted) {
startService();
} else {
myService.cancelCalcalation();
}
myService.setData(data);
myService.startWork();
}
}
and stopping the service is handled in the Activity:
public class CustomChordActivity extends Activity {
// ... code ...
#Override
public void onBackPressed() {
super.onBackPressed();
}
#Override
protected void onPause() {
if(isFinishing()) {
chordsView.stopService();
}
super.onPause();
}
#Override
protected void onStop() {
super.onStop();
}
#Override
protected void onDestroy() {
chordsView.unBindService();
super.onDestroy();
}
#Override
protected void finalize() throws Throwable {
super.finalize();
}
}
It seems that you want your task to run on demand, maybe an IntentService would be a more suitable option. When you need work to be done, (startServiceWork()), you just start the service and that kicks off your AsyncTask. The service will then finish after the task has finished.
Now, regarding orientation changes, you would have to implement a Broadcast Receiver whose intent filter is "android.intent.action.CONFIGURATION_CHANGED". (I assume that you want the service to do work when the orientation changes) Place the Broadcast Receiver, within your activity/main ui thread. This will in effect make the hosting process of your Broadcast Receiver to be the main application process making it safer to start the service from within the Broadcast Receiver.
I just read http://www.ozdroid.com/#!BLOG/2010/12/19/How_to_make_a_local_Service_and_bind_to_it_in_Android about how there can be memory leaks when binding to a local service...
I am currently implementing binding to a local service using the following code.
In the service I have:
private final Binder binder=new LocalBinder();
public class LocalBinder extends Binder implements IStreamCommander {
public void registerPlayer(IStreamListener callback) {
theUI=callback;
}
public void removePlayer(IStreamListener callback) {
theUI=null;
}
public void play(Station NowP) {
playURL(NowP);
}
public void stop() {
stopIt();
}
}
Where IStreamCommander is defined:
public interface IStreamCommander {
void registerPlayer(IStreamListener callback);
void removePlayer(IStreamListener callback);
void play(Station SongID);
void stop();
}
and IStreamListener is defined:
public interface IStreamListener {
void updateUI(String msg, int buttons);
}
I then have this in the activity:
this.bindService(startedSvc, svcConn, 0);
and
private ServiceConnection svcConn = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder binder) {
service = (IStreamCommander) binder;
}
public void onServiceDisconnected(ComponentName className) {
service = null;
}
};
So am I leaking memory, or is this okay?
If you are going to stick with the binding pattern, I would:
Move your Binder to a standalone public class, not an inner class
Bind using getApplicationContext(), rather than this
Make sure you use onRetainNonConfigurationInstance() properly to pass your binding between instances of your activity when the configuration changes (e.g., screen rotation)
Here is a sample project demonstrating this.
I try to start an android service from the service's class.
The reason to do this is to achieve some platform independence.
Doing this I get a NullPointerException at android.content.ContextWrapper.startService(ContextWrapper.java:326). The platform target is 2.1-update1, any suggestions?
See the code below (I left the imports out to save some space)
/* GUI: HelloAndroid.java */
package com.example.helloandroid;
public class HelloAndroid extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// that works!
startService(new Intent("com.example.helloandroid.UnusualService"));
// stop the service works of course, too
stopService(new Intent("com.example.helloandroid.UnusualService"));
// unusual start does not work:
UnusualService myService = new UnusualService();
myService.startService();
}
}
/* Service: UnusualService.java */
package com.example.helloandroid;
public class UnusualService extends Service {
#Override
public void onCreate() {
Toast.makeText(this, R.string.service_started, Toast.LENGTH_SHORT).show();
}
#Override
public void onDestroy() {
Toast.makeText(this, R.string.service_stopped, Toast.LENGTH_SHORT).show();
}
#Override
public IBinder onBind(Intent intent) {
return null; // make something here when the rest works
}
public void startService() {
// folowing line will cause the NullPointerException
startService(new Intent("com.example.helloandroid.UnusualService"));
}
public void stopService() {
stopSelf();
}
}
Of course - your newly created service does not have a reference to a context, so the context is null and therefore the system throws a NullPointerException.
Remember: Do not create a service on your own by using new - the system does this for you!
Your UnusualService extends Service. Service extends ContextWrapper which contains Context.
So when you call UnusualService.startService(new Intent("...")) it actually calls ContextWrapper.startService() which calls context.startService(). However at this point context is null and NullPointerException occurs.
public abstract class Service extends ContextWrapper implements ComponentCallbacks {
private static final String TAG = "Service";
public Service() {
super(null);
}
//...
}
public class ContextWrapper extends Context {
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
#Override
public ComponentName startService(Intent service) {
return mBase.startService(service);
}
//...
}
public void startService() {
// folowing line will cause the NullPointerException
startService(new Intent("com.example.helloandroid.UnusualService"));
}
do this instead...
startService(new Intent(UnusualService.this, UnusualService.class));
Edit: Corrected as per CyprUS