I've got a problem with a callbacks in remote service, after register a callback rotation cause an activity leak. Can You give me some suggestion what I'm doing wrong.
IRemoteApi.aidl
import com.example.remoteservice.IRemoteListener;
interface IRemoteApi{
void addListener(IRemoteListener listener);
void removeListener(IRemoteListener listener);
void sendRequest(String msg);
}
IRemoteListener.aidl
interface IRemoteListener {
void onMessage(String text);
}
RemoteService.java
public class RemoteService extends Service {
private static final String TAG = RemoteService.class.getSimpleName();
final RemoteCallbackList<IRemoteListener> mCallbacks = new RemoteCallbackList<IRemoteListener>();
@Override
public void onCreate() {
super.onCreate();
Log.e(TAG, "Create service...");
}
@Override
public void onDestroy() {
super.onDestroy();
mCallbacks.kill();
}
private void dumpMethod(String msg){
if(msg.equals("OK")){
final int N = mCallbacks.beginBroadcast();
for (int i=0; i<N; i++) {
try {
mCallbacks.getBroadcastItem(i).onMessage("Voila!");
} catch (RemoteException e) {}
}
mCallbacks.finishBroadcast();
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private IRemoteApi.Stub mBinder = new IRemoteApi.Stub() {
@Override
public void addListener(IRemoteListener listener) throws RemoteException {
if (listener != null) mCallbacks.register(listener);
}
@Override
public void removeListener(IRemoteListener listener) throws RemoteException {
if (listener != null) mCallbacks.unregister(listener);
}
@Override
public void sendRequest(String msg) throws RemoteException {
dumpMethod(msg);
}
};
}
MainActivity.java
public class MainActivity extends ActionBarActivity {
private static final String TAG = MainActivity.class.getSimpleName();
IRemoteApi mService;
boolean isBound = false;
private ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IRemoteApi.Stub.asInterface(service);
isBound = true;
Log.e("merhold", "Bound to service");
try {
mService.addListener(serviceListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getApplicationContext().startService(new Intent(RemoteService.class.getName()));
getApplicationContext().bindService(new Intent(RemoteService.class.getName()), mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
if(isBound){
try {
mService.removeListener(serviceListener);
getApplicationContext().unbindService(mServiceConnection);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
public void sendRequest(View view) {
try {
mService.sendRequest("OK");
} catch (RemoteException e) {
e.printStackTrace();
}
}
private IRemoteListener serviceListener = new IRemoteListener.Stub(){
@Override
public void onMessage(String text) throws RemoteException {
Log.e(TAG, "Message from listener: "+text);
}
};
}
Because there are two processes there are also two garbage collectors involved.
The service and client garbage collector. The service handles small IBinder objects (IRemoteListener) which are not so important to garbage collect fast. From the client side these IBinder objects holds a reference to an activity which is big.
The activity can't be garbage collected until the service have garbage collected the IBinder objects so it will be leaked until that happens. The solution is to change the listener into a static inner class. If you want to access something in the activity you have to use a weak reference.
Here's a related question and an explanation by Dianne Hackborn; https://stackoverflow.com/a/12206516/1035854
Some code: