Remote service, leaks activity when rotating

2020-07-13 11:37发布

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);
        }
    };

}

1条回答
Fickle 薄情
2楼-- · 2020-07-13 12:38

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:

private static class MyRemoteListener extends IRemoteListener.Stub {
    private final WeakReference<Activity> mWeakActivity;

    public MyRemoteListener(Activity activity) {
        mWeakActivity = new WeakReference<Activity>(activity);
    }

    @Override
    public void onMessage(String text) throws RemoteException {
        Activity activity = mWeakActivity.get();
        if (activity != null) {
            // ((MainActivity)activity).handleOnMessage(text);
        }
    }
}
查看更多
登录 后发表回答