Getting java.lang.ClassCastException: android.os.BinderProxy every time i declare and run two services
Asked Answered
N

4

13

I am encountering following binder.proxy exception every time i declare and run two services. One service runs in different Process(Private to app) and another service runs in same process as My Application is running in(Default App Process) with a Binder Implementation.

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.service.check"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />

    <application
        android:name="com.service.check.MainApplication"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

         <service
            android:name="com.service.check.SecondService"
            android:exported="false"/>

        <service
            android:name="com.service.check.FirstService"
            android:process=":newProcess" >
        </service>
    </application>

</manifest>

I am launching my first service in MainActivity on Button click as:

MainActivity.java

public class MainActivity extends ActionBarActivity implements OnClickListener {

    private Button mLanchServiceBtn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mLanchServiceBtn=(Button) findViewById(R.id.launch_btn);

        mLanchServiceBtn.setOnClickListener(this);
    }
    @Override
    public void onClick(View v) {
       //Starting first service
        Intent launch=new Intent(this,FirstService.class);
        startService(launch);

    }
}

And second service in MainApplication class as.

MainApplication.java

    public class MainApplication extends Application {

        private SecondService.LocalBinder mBinder;
        private ServiceConnection mConnection = new ServiceConnection() {

            @Override
            public void onServiceConnected(ComponentName className, IBinder service) {
                mBinder = (LocalBinder) service;
            }

            @Override
            public void onServiceDisconnected(ComponentName arg0) {
            }
        };

        @Override
        public void onCreate() {
            super.onCreate();

            //starting second service               
            Intent launch=new Intent(this,SecondService.class);
            startService(launch);

            //Binding to it 
            bindService(launch, mConnection, BIND_AUTO_CREATE);
        }

    }

FirstService.java

public class FirstService extends Service {
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

SecondService.java

public class SecondService extends Service{

    //Service Containing Local Binder
    private LocalBinder mBinder=new LocalBinder();
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    class LocalBinder extends Binder{

        public LocalBinder() {
        }
    }
}

StackTrace:

 02-05 10:32:25.035: E/AndroidRuntime(1424): Process:

 com.service.check:newProcess, PID: 1424 02-05 10:32:25.035:
 E/AndroidRuntime(1424): java.lang.ClassCastException:
 android.os.BinderProxy cannot be cast to
 com.service.check.SecondService$LocalBinder 02-05 10:32:25.035:
 E/AndroidRuntime(1424):    at
 com.service.check.MainApplication$1.onServiceConnected(MainApplication.java:23)
 02-05 10:32:25.035: E/AndroidRuntime(1424):    at
 android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1101)

I have referred the following links to sort out the issue which says, if my activity and service are in separate processes then we should not bind the way I have done.

Android service android.os.BinderProxy error

java.lang.ClassCastException: android.os.BinderProxy cannot be cast to LocalBinder

But in my case: I am binding to SecondService from MainApplication and both are running in same Process(i.e Default Application Process). Still I am facing binderProxy exception in SecondService , And my FirstService runs in separate process which I am not even binding to.

Please help me out with this situation and, Suggest me a best possible way so that I can implement same scenario without any crash.

Nihhi answered 6/2, 2015 at 11:25 Comment(8)
Did you verify that Application and Service run in the same process?Kaleena
I didn't verified it explicitly in android device monitor, but by logs I see that I am getting crash in second service, which should run in a same in same process as application since I haven't assigned any new porcess to it in manifest.Nihhi
normally if a IBinder is a BinderProxy it means that the other party is running in different process, so...Kaleena
Okay I will verify second service by printing process name during runtime in logs or in ddms. Also, If some how android runs my second service in a different process internally, I wanted to know if there is any way to force this service to not to run in separate process.Nihhi
and why you want the first one to run in separate process?Kaleena
I have a requirement where I need to import library, and library contains 6-7 services running in different processes whereas my project contains another service (from another library)running in application 's proces.Nihhi
I don't get it: if those services are already installed on your device then you don't need to import anything at all, if they are not installed so why different process?Kaleena
Try binding the service using "yourActivity.this.service = (ConnectionService) binder.getService()". In this case, it should be MainActivity.Shere
N
8

Found an answer after doing some research and debugging,

If we create and bind any service to a MainApplication class(then service gets binded to whole ApplicationContext or BaseContext) and if same application contains other services which are binded to Activity specific Context(s),

//Declared in MainApplication
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
                mBinder = (LocalBinder) service;
     }

In OnServiceConnected() We will get binder object for both the Services( SecondService Started in MainApplication(registered with BaseContext will get local binderObject) class and FirstService started MainActivity(will get android.os.binderProxyObject hence causing ClassCastException).

  • So, to fix this issue one has to start all the application services from any Activity Context rather than using any Global Application Context. Also this issue is independent of the Processes

  • Hence, I moved both SecondService and FirstService into MainActivity Context which fixed the issue.

MainActivity.java

    private Button mLanchServiceBtn;
    private SecondService.LocalBinder mBinder;
    private ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName className, IBinder service) {
                mBinder = (LocalBinder) service;
            }
            @Override
            public void onServiceDisconnected(ComponentName arg0) {
            }
     };
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            mLanchServiceBtn=(Button) findViewById(R.id.launch_btn);

            mLanchServiceBtn.setOnClickListener(this);



            //starting second service in activity

            Intent launch=new Intent(this,SecondService.class);
            startService(launch);

            //Binding to it 
            bindService(launch, mConnection, BIND_AUTO_CREATE);
        }


        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.main, menu);
            return true;
        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            int id = item.getItemId();
            if (id == R.id.action_settings) {
                return true;
            }
            return super.onOptionsItemSelected(item);
        }


        @Override
        public void onClick(View v) {

           //Starting FirstService also from MainActivity
            Intent launch=new Intent(this,FirstService.class);
            startService(launch);

        }
    }
Nihhi answered 16/2, 2015 at 15:44 Comment(1)
"to fix this issue one has to start all the application services from any Activity Context rather than using any Global Application Context" can you explain why that's the case? As long as app and activity contexts live in same process then it shouldn't matter which one binds to service.Petroleum
I
27

Ran into this issue (local service returning a BinderProxy), wanted to post what I'd found since I found this page while trying to debug. The short version as a run on sentence: starting a remote service creates a second instance of your Application class in a new process which then tries to bind to the local service that was started by the original Application instance as if it was a local service but since the service is running in the original process it's binding across processes and you get a BinderProxy instead of your expected Binder class.

There's a few things to keep in mind about Android services. Every service has an assigned process it will run in. If you don't assign a process in your Android Manifest it will run in the default process (the process where the Application, Activities, etc are run). Not giving a process name doesn't mean that it will run the service in the same process that you're binding to/starting the service from.

Let's say I have a MyApplication class which attempts to bind to two services on start up: one service running in the default process (we'll call this the LocalService), one running in a separate process (the RemoteService).

The user launches my app which creates a MyApplication instance in the default process. This instance then tries to bind to the LocalService. Android creates the LocalService in the default process and returns the LocalService's Binder class to the app (mBinder = (LocalBinder) service;). That's all good, we've successfully bound to the LocalService.

Next the app tries to bind to the RemoteService. Android creates a new process with the name you've supplied in the Android Manifest. However, before it can create the RemoteService it needs to create an Application for the service to run in. It creates a new MyApplication instance in the remote process and starts that up.

However, that new MyApplication instance running in a separate process tries to bind to the LocalService during start up. Because the LocalService is running in the default process this is a cross process binding but MyApplication expects this to be an in process binding. Android returns a BinderProxy, the second MyApplication instance tries to cast it to a LocalBinder and crashes. The fun part is that it crashes in a different process so your app and activity can actually continue running. You'll just never be able to bind to the remote service.

If you want to bind to a local service with an Application context and also use a remote service you'll need to handle the fact that Android will create another Application in the remote process when starting the remote service. I haven't bothered to try this (I just made my remote service a local service), but you could probably check the process name during the application's on create and not bind if it's not the default process.

Indreetloire answered 4/6, 2015 at 22:45 Comment(1)
"Not giving a process name doesn't mean that it will run the service in the same process that you're binding to/starting the service from." is this documented somewhere? I believe that if you omit it, they will definitely run in the same process.Bean
N
8

Found an answer after doing some research and debugging,

If we create and bind any service to a MainApplication class(then service gets binded to whole ApplicationContext or BaseContext) and if same application contains other services which are binded to Activity specific Context(s),

//Declared in MainApplication
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
                mBinder = (LocalBinder) service;
     }

In OnServiceConnected() We will get binder object for both the Services( SecondService Started in MainApplication(registered with BaseContext will get local binderObject) class and FirstService started MainActivity(will get android.os.binderProxyObject hence causing ClassCastException).

  • So, to fix this issue one has to start all the application services from any Activity Context rather than using any Global Application Context. Also this issue is independent of the Processes

  • Hence, I moved both SecondService and FirstService into MainActivity Context which fixed the issue.

MainActivity.java

    private Button mLanchServiceBtn;
    private SecondService.LocalBinder mBinder;
    private ServiceConnection mConnection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName className, IBinder service) {
                mBinder = (LocalBinder) service;
            }
            @Override
            public void onServiceDisconnected(ComponentName arg0) {
            }
     };
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            mLanchServiceBtn=(Button) findViewById(R.id.launch_btn);

            mLanchServiceBtn.setOnClickListener(this);



            //starting second service in activity

            Intent launch=new Intent(this,SecondService.class);
            startService(launch);

            //Binding to it 
            bindService(launch, mConnection, BIND_AUTO_CREATE);
        }


        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            // Inflate the menu; this adds items to the action bar if it is present.
            getMenuInflater().inflate(R.menu.main, menu);
            return true;
        }

        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            int id = item.getItemId();
            if (id == R.id.action_settings) {
                return true;
            }
            return super.onOptionsItemSelected(item);
        }


        @Override
        public void onClick(View v) {

           //Starting FirstService also from MainActivity
            Intent launch=new Intent(this,FirstService.class);
            startService(launch);

        }
    }
Nihhi answered 16/2, 2015 at 15:44 Comment(1)
"to fix this issue one has to start all the application services from any Activity Context rather than using any Global Application Context" can you explain why that's the case? As long as app and activity contexts live in same process then it shouldn't matter which one binds to service.Petroleum
W
2

You can't call directly any methods of your remote services (or cast) because they live in different process so you can't get a reference to it's instance. But Android has specific interfaces to handle this interprocess communications (IPC). The easiest way is using android.os.Messenger (another is AIDL, more complex).

On your Service, your implementation of Service#onBind() will be a little bit different:

override fun onBind(intent: Intent): IBinder? {
    mMessenger = Messenger(YourServiceHandler())
    return mMessenger.binder
}

And on your Activity implementation of ServiceConnection#onServiceConnected(serviceBinder: IBinder) you will not get a directly reference to your remote service instance, but instead create a Messenger that have a send(message: Message) interface so you can remotelly call the service functions:

override fun onServiceConnected(className: ComponentName, service: IBinder) {
     mServiceMessenger = Messenger(service)
}

override fun onCreate(){
    doStuff1Button.setOnClickListener{
         val msg = Message.obtain(null, YourRemoteService.MESSAGE_DO_STUFF_1, 0, 0)
         mServiceMessenger.send(msg)
    }
    doStuff1Button.setOnClickListener{
         val msg = Message.obtain(null, YourRemoteService.MESSAGE_DO_STUFF_2, 0, 0)
         mServiceMessenger.send(msg)
    }
}

Note that in the message is going a argument do stuff 1 or 2. You will get this back on your service handler Handler#onHandleMessage(message: Message) with the attribute what:

override fun handleMessage(message: Message) {
    when (message.what) {
         MESSAGE_DO_STUFF_1 -> doStuff1()
         MESSAGE_DO_STUFF_2 -> doStuff2()
    }
}

Complete guide can be found in this official documentation

Winifield answered 20/9, 2019 at 13:31 Comment(1)
While this is a more complicated solution, it relies not on something that is probably broken but recommended practices, unlike the accepted answer, and actually works especially in cases where you cannot bind the service to an activity.Questionnaire
V
0

I tried above all solutions but none of those worked. If anyone was stuck same as myself try this based on @Coeffect answer. In my scenario service clients doesn't belong to my current application(process)

@Override
public void onServiceConnected(ComponentName className, IBinder service) {
       mBinder = LocalBinder.Stub.asInterface(service);
}
Verisimilar answered 8/11, 2018 at 5:14 Comment(1)
what is Stub.asInterface ?Regin

© 2022 - 2024 — McMap. All rights reserved.