Получение java.lang.ClassCastException: android.os.BinderProxy каждый раз, когда я объявляю и запускаю две службы

Я встречаю следующее исключение binder.proxy каждый раз, когда я объявляю и запускаю две службы. Одна служба выполняется в разных процессах (частное приложение), а другая служба выполняется в том же процессе, что и в моем приложении, в (процесс приложений по умолчанию) с внедрением связующего.

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>

Я запускаю свою первую услугу в MainActivity при нажатии кнопки:

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

    }
}

И вторая услуга в классе MainApplication как.

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)

Я сослался на следующие ссылки, чтобы разобраться в проблеме, которая гласит: если моя деятельность и служба находятся в отдельных процессах, тогда мы не должны связывать то, как я это сделал.

Android-версия android.os.BinderProxy для Android

java.lang.ClassCastException: android.os.BinderProxy нельзя отнести к LocalBinder

Но в моем случае: Я привязываюсь к SecondService из MainApplication, и оба они работают в одном Процессе (например, в приложении по умолчанию). Тем не менее я столкнулся с проблемой binderProxy в SecondService, а моя FirstService работает в отдельном процессе, с которым я даже не привязан.

Пожалуйста, помогите мне в этой ситуации и предложите мне наилучший способ, чтобы я мог реализовать тот же сценарий без какого-либо сбоя.

Ответ 1

Нашел ответ после проведения некоторых исследований и отладки,

Если мы создаем и привязываем любую службу к классу MainApplication (тогда служба получает привязку к целому ApplicationContext или BaseContext), и если одно и то же приложение содержит другие службы, привязанные к Контексту (-ам),

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

В OnServiceConnected() Мы получим объект связующего для обеих служб (SecondService, запущенный в MainApplication (зарегистрированный в BaseContext получит локальный объект binderObject) и FirstService, запущенный MainActivity ( получит android.os.binderProxyObject, поэтому вызывает ClassCastException).

  • Итак, чтобы исправить эту проблему, нужно запустить все приложение услуг из любого контекста активности, а не с использованием любого глобального Контекст приложения. Также этот вопрос является независимым Процессы

  • Следовательно, я переместил SecondService и FirstService в MainActivity Контекст, который исправил проблему.

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

        }
    }

Ответ 2

В эту проблему (локальная служба, возвращающая BinderProxy), захотела опубликовать то, что я нашел, так как нашел эту страницу при попытке отладки. Краткая версия как запуск по предложению: запуск удаленной службы создает второй экземпляр класса Application в новом процессе, который затем пытается привязать к локальной службе, которая была запущена исходным экземпляром приложения, как если бы это была локальная служба, но поскольку служба запускается в исходном процессе, привязывая ее к процессам, и вы получаете BinderProxy вместо ожидаемого класса Binder.

Есть несколько вещей, которые нужно помнить об услугах Android. Каждый сервис имеет назначенный процесс, в котором он будет работать. Если вы не назначаете процесс в вашем манифесте Android, он будет работать в процессе по умолчанию (процесс, в котором запускаются приложение, действия и т.д.). Не указывать имя процесса не означает, что он запустит службу в том же процессе, с которым вы связываете/запускаете службу.

Скажем, у меня есть класс MyApplication, который пытается связать две службы при запуске: одна служба работает в процессе по умолчанию (мы будем называть это LocalService), один из которых выполняется в отдельном процессе (RemoteService).

Пользователь запускает мое приложение, которое создает экземпляр MyApplication в процессе по умолчанию. Затем этот экземпляр пытается привязать к LocalService. Android создает LocalService в процессе по умолчанию и возвращает класс LocalService Binder в приложение (mBinder = (LocalBinder) service;). Что все хорошо, мы успешно привязаны к LocalService.

Затем приложение пытается связать с RemoteService. Android создает новый процесс с именем, которое вы указали в манифесте Android. Однако, прежде чем он сможет создать RemoteService, ему необходимо создать приложение для запуска службы. Он создает новый экземпляр MyApplication в удаленном процессе и запускает его.

Однако этот новый экземпляр MyApplication, запущенный в отдельном процессе, пытается связать с LocalService во время запуска. Поскольку LocalService работает в процессе по умолчанию, это привязка к перекрестному процессу, но MyApplication ожидает, что это будет привязка к процессу. Android возвращает BinderProxy, второй экземпляр MyApplication пытается передать его в LocalBinder и сбой. Самое интересное, что он падает в другом процессе, поэтому ваше приложение и активность могут продолжать работать. Вы никогда не сможете привязываться к удаленной службе.

Если вы хотите привязываться к локальной службе с контекстом приложения, а также использовать удаленную службу, вам нужно будет обработать тот факт, что Android будет создавать другое приложение в удаленном процессе при запуске удаленной службы. Я не потрудился попробовать это (я просто сделал мой удаленный сервис локальной службой), но вы, вероятно, могли бы проверить имя процесса во время приложения при создании и не связывать, если это не процесс по умолчанию.

Ответ 3

Я пробовал прежде всего решения, но ни одно из них не сработало. Если кто-то застрял так же, как я, попробуйте это на основе ответа @Coeffect. В моем сценарии служба клиентов не принадлежит моему текущему приложению (процессу)

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

Ответ 4

Вы не можете напрямую вызывать какие-либо методы своих удаленных служб (или приведений), потому что они живут в другом процессе, поэтому вы не можете получить ссылку на этот экземпляр. Но у Android есть специальные интерфейсы для обработки этой межпроцессной связи (IPC). Самый простой способ - использовать android.os.Messenger (другой - AIDL, более сложный).

В вашем Сервисе ваша реализация Service#onBind() будет немного отличаться:

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

А в реализации Activity ServiceConnection#onServiceConnected(serviceBinder: IBinder) вы не получите прямую ссылку на экземпляр удаленной службы, а вместо этого создадите Messenger с интерфейсом send(message: Message), чтобы вы могли удаленно вызывать функции службы:

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

Обратите внимание, что в сообщении идет аргумент, выполните вещи 1 или 2. Вы получите это обратно в свой обработчик службы Handler#onHandleMessage(message: Message) с атрибутом what:

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

Полное руководство можно найти в этой официальной документации