Search code examples
androidandroid-serviceipcandroid-binder

bindService from other app but same userid/process


This is about memory-based IPC (like to LocalService example) but for two apps running in the same process:

I have two apps (App1, App2) and a shared project (Shared) which defines some interfaces and abstract classes for both apps:

Shared (regular Java project, references android.jar)
    - abstract myAbstractService
    - Binder myBinder
App1 (Android Project, references Shared)
    - MainActivity
App2 (Android Project, references Shared)
    - myService extends myAbstractService

Both apps run within the same process (my.process, defined in <application>), App2 publishes com.www.app2.myService:

<-- Both Apps run in the same process -->
<manifest <!-- *snip* --> android:sharedUserId="my.shareduser">
    <!--  ... -->
    <application <!-- *snip* --> android:process="my.process">
        <-- App2 exports the service -->
        <service android:name="com.www.app2.myService" android:exported="true">
            <intent-filter>
                <action android:name="com.www.app2.myService" />
            </intent-filter>
       </service>

This is the abstract myAbstractService (myService doesn't add anything new yet):

abstract public class GameClient extends Service
{   
    private static final String LOGTAG = "GameClient";

    private myBinder binder = new myBinder();

    public IBinder onBind(Intent intent)
    {
        Log.d(LOGTAG, "onBind()");

        return this.binder;
    }

    public class myBinder extends Binder
    {
        public void sendMessage()
        {
            Log.d(LOGTAG, "sendMessage()");
        }
    }
 }

When I try to bind to myService (App2) from my MainActivity (App1):

public void onServiceConnected(ComponentName name, IBinder service) 
{
    Log.d("MS", service.getClass().toString());
    main.this.t = (myBinder) service; // Exception in this line of course
}

I get an exception:

DEBUG/MS(5464): class com.www.shared.myBinder
ERROR/AndroidRuntime(5464): java.lang.ClassCastException: com.www.shared.myBinder

As both apps run in the same process, memory-coupled communication should work (at least I thought). I really don't want to use the message-based or broadcast-based communication as I'll send quite some messages around.
I suspect this exception happens due two different classloaders being used for the same class? Is this approach simply not possible/wrong or am I missing something?

Update:
My aim is to write a very modular application, where App1 is used as a delegating and starting application of other modules (apps). As I don't want to ship App1 with every app depending on it, I made it it's own app.

Let's say I have a third app (App3, Android project). App2 & App3 are both started by App1 (responsible for setting up connections while App2 & App3 provide different application logic (but having the same interface).

On a second thought I think this could be solved with an android library as well (App1 & Shared merged as the library with App2 and App3 starting an Activity of this library and waiting for the result)? However the data is not parcelable (network connections) and I have no idea how this library could be distributed independently over the android market (like App2 and App3 are published there but ask for the library to be installed too). Would this resolve that problem at all?


Solution

  • You are correct, you receive this exception because there are two class loaders involved.

    I've added next two lines of code:

    @Override
    public void onServiceConnected(ComponentName name, IBinder service)
    {
        ...
        Log.e(TAG, "Expected class loader: "+myBinder.class.getClass().getClassLoader());
        Log.e(TAG, "Class loader: "+service.getClass().getClassLoader());
        ...
    }
    

    And received these logs:

    Expected class loader: java.lang.BootClassLoader@4001bdb0
    Class loader: dalvik.system.PathClassLoader[/data/app/com.inazaruk.shared.service-2.apk]
    

    From logs its clear that another class loader was used. This actually makes sense, as you could've installed App1 application one week later with different version of myBinder class (or any other class that is passed via myBinder interface directly or indirectly).



    Updated:
    You should stick with Android Libraries in your scenario. Note that Android Libraries are directly embedded in the application that references them. They are not distributed separately. Here is my post explaining how Android Libraries are different from simple jars, and other relevant nuances.

    You still have a high degree of modularity in Android Library, as final Android Application only includes modules it uses. But this is compile-time modularity, not runtime modularity.

    There are some issues with Android Libraries though:

    1. You need to copy all components' declarations in library's AndroidManifest.xml into Android Application's AndroidManifest.xml.
    2. Currently libraries do not support custom xml attributes (see my posts here and here).
    3. Currently libraries do not support assets (see my post here).

    The #1 is planned to be fixed soon (according to Build Support roadmap ). The #2 and #3 might get fixed in next release of SDK platform tools.