Comunicación entre procesos de Servicio

Imagine tal escenario, tenemos 2 aplicaciones (o 2 procesos, cualquiera), una de las aplicaciones necesita proporcionar un servicio relacionado con la persona (el servicio tiene una interfaz llamada eat), lo llamamos PersonServer; la otra aplicación necesita acceder los servicios proporcionados por PersonServer, lo llamamos Cliente. Es decir, ahora hay dos aplicaciones, una del lado del servidor, que proporciona servicios, y la otra del lado del cliente, que usa servicios.

¿Veamos cómo implementarlo en Android?

Implementación del lado de PersonServer

PersonServer es el proveedor del servicio. Primero debemos crear un Servicio para proporcionar el servicio, y el servicio debe tener la capacidad de comunicarse entre procesos para que el Cliente pueda llamar.

Implementar AIDL

Proceso cruzado, nuestro primer pensamiento es usar AIDL para implementar la capacidad de proceso cruzado de la interfaz.

Primero necesitamos definir una interfaz AIDL llamada IPerson y proporcionar el método eat:

package com.testaidl;
interface IPerson {
    boolean eat(String food);
}

Vemos que la implementación de IPerson es muy sencilla, usamos IDE para compilarlo, y el compilador generará un archivo llamado IPerson.java, que es el código java correspondiente a aidl.

Implementar servicio

A continuación definimos un Servicio:

public class PersionService extends Service
{
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new IPerson.Stub() {
            @Override
            public boolean eat(String food) throws RemoteException {
                Log.d("PersionService", "this is Server");
                Log.d("PersionService", "Persion eat " + food);
                return true;
            }
        };
    }
}

El enfoque aquí está en el método onBind, que devuelve un objeto IBinder, que es la clase generada por IPerson.Stub de IPerson.aidl que definimos anteriormente. La clase IPerson.Stub tiene un método abstracto eat() que debe implementarse aquí, que es La implementación concreta de las acciones realizadas por el servicio remoto proporcionado por el servicio.

No olvide registrarse en AndroidManifest:

 <service android:name=".PersionService">
            <intent-filter>
                <action android:name="com.example.simpledemo.PersionService"/>
            </intent-filter>
        </service>

Cliente

El lado del servidor está listo, entonces, ¿cómo accede el lado del cliente a los servicios proporcionados por PersonServer?

Veamos la implementación del lado del cliente:

public class MainActivity extends AppCompatActivity {
    private IPerson mService = null;
    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) { //绑定Service成功后执行
            mService = IPerson.Stub.asInterface(iBinder);//获取远程服务的代理对象,一个IBinder对象
            try {
                mService.eat("banana"); //调用远程服务的接口。
            } catch (RemoteException e) {
                e.printStackTrace();
            }

        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {
            mService = null;
        }
    };

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

        Button bindService = findViewById(R.id.bind_service);
        bindService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //调用bindService发送绑定远程服务的请求
                Intent intent = new Intent();
                intent.setAction("com.example.simpledemo.PersionService");
                intent.setPackage("com.example.simpledemo");
                bindService(intent, mConnection, BIND_AUTO_CREATE);
            }
        });
    }
}

Análisis de pasos:

  • Llame a bindService para enviar una solicitud para vincular un servicio remoto.
  • El objeto ServiceConnection ejecuta la función de devolución de llamada onServiceConnected después de vincular el servicio.
  • En onServiceConnected, obtenga el objeto proxy del servicio remoto, un objeto IBinder.
  • Finalmente, a través del objeto proxy de servicio remoto obtenido en el paso anterior, se llama a la interfaz del servicio remoto.

Aviso

No olvide colocar el archivo IPerson.aidl en la misma ubicación en el lado del cliente, con el mismo nombre de paquete y nombre de archivo .

Análisis de procedimiento de llamada remota de servicio/AIDL

En el proceso de comunicación remota de Serivce, el punto más crítico es AIDL, que asume la tarea de acceso a la comunicación remota , por supuesto, su implementación de bajo nivel también se logra a través del mecanismo Binder , pero en comparación con Binder, AIDL simplifica nuestro uso.

IPerson.aidl

Veamos primero IPerson.aidl:

package com.testaidl;
interface IPerson {
    boolean eat(String food);
}

Su implementación es muy simple, entonces, ¿cómo se da cuenta de la capacidad de comunicación remota?

Sabemos que después de que el compilador compile el archivo aidl, se generará un archivo .java, y este archivo java es el lugar donde realmente se almacena el código lógico de la implementación de la comunicación remota.

IPerson.java:

package com.testaidl;
// Declare any non-default types here with import statements

public interface IPerson extends android.os.IInterface
{
  /** Local-side IPC implementation stub class. */
  public static abstract class Stub extends android.os.Binder implements com.testaidl.IPerson
  {
    private static final java.lang.String DESCRIPTOR = "com.testaidl.IPerson";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an com.testaidl.IPerson interface,
     * generating a proxy if needed.
     */
    public static com.testaidl.IPerson asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.testaidl.IPerson))) {
        return ((com.testaidl.IPerson)iin);
      }
      return new com.testaidl.IPerson.Stub.Proxy(obj);
    }
    @Override public android.os.IBinder asBinder()
    {
      return this;
    }
    @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_eat:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          boolean _result = this.eat(_arg0);
          reply.writeNoException();
          reply.writeInt(((_result)?(1):(0)));
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }
    private static class Proxy implements com.testaidl.IPerson
    {
      private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }
      @Override public android.os.IBinder asBinder()
      {
        return mRemote;
      }
      public java.lang.String getInterfaceDescriptor()
      {
        return DESCRIPTOR;
      }
      @Override public boolean eat(java.lang.String food) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        boolean _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeString(food);
          boolean _status = mRemote.transact(Stub.TRANSACTION_eat, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().eat(food);
          }
          _reply.readException();
          _result = (0!=_reply.readInt());
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      public static com.testaidl.IPerson sDefaultImpl;
    }
    static final int TRANSACTION_eat = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    public static boolean setDefaultImpl(com.testaidl.IPerson impl) {
      if (Stub.Proxy.sDefaultImpl == null && impl != null) {
        Stub.Proxy.sDefaultImpl = impl;
        return true;
      }
      return false;
    }
    public static com.testaidl.IPerson getDefaultImpl() {
      return Stub.Proxy.sDefaultImpl;
    }
  }
  public boolean eat(java.lang.String food) throws android.os.RemoteException;
}

Cuando definimos aidl, solo usamos unas pocas líneas de código y todavía hay mucho código en la clase java generada.

Veamos primero su estructura de clases:

 Su estructura se puede dividir en las siguientes partes;

  1. Interfaz IInterfaz.
  2. Método abstracto comer.
  3. Stub de clase interna.
  4. Proxy de clase interna de Stub.

Analizamos estos componentes por separado.

1. Interfaz IInterfaz

Interfaz android.os.IInterfaz:

public interface IInterface
{
    /**
     * Retrieve the Binder object associated with this interface.
     * You must use this instead of a plain cast, so that proxy objects
     * can return the correct result.
     */
    public IBinder asBinder();
}

Aquí hay solo un método que debe implementarse como Binder(), que devuelve el objeto IBinder asociado con la interfaz IInterface.

En IPerson.java, el método asBinder sigue siendo un método abstracto y no necesita ser implementado.La función principal de IPerson es proporcionar una clase abstracta para la interfaz, y la implementación real está en su subclase Stub.

2. Método abstracto comer

Este es un método abstracto a implementar, que finalmente es implementado por la persona que llama en el lado del servidor. En este ejemplo, se implementa en PersionService:

@Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return new IPerson.Stub() {
            @Override
            public boolean eat(String food) throws RemoteException {
                Log.d("PersionService", "this is Server");
                Log.d("PersionService", "Persion eat " + food);
                return true;
            }
        };
    }

3. Stub de clase interna

Esta clase interna es la lógica principal para implementar IPC, y todo uso externo generalmente se realiza llamándola.

La clase Stub hereda la clase Binder e implementa la interfaz IPerson.

Método de construcción

El objeto de instancia de Stub se crea en el lado PersonServer, es decir, se crea una instancia en el lado del servidor.

Su constructor:

private static final java.lang.String DESCRIPTOR = "com.testaidl.IPerson";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
      this.attachInterface(this, DESCRIPTOR);
    }

El método attachInterface de la clase padre Binder se llama directamente:

  public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }

Aquí, el objeto actual y una cadena de descripción se asignan a las variables correspondientes para su uso en búsquedas futuras.

como método de interfaz

El método asInterface devuelve un objeto IPerson. Cuando el lado del cliente obtiene el objeto de servicio, este método se llama:

mService = IPerson.Stub.asInterface(iBinder);

El método asInterface se llama en el lado del cliente y devuelve un objeto proxy del servicio PersonServer en el lado del cliente.

Veamos su implementación lógica:

public static com.testaidl.IPerson asInterface(android.os.IBinder obj)
    {
      if ((obj==null)) {
        return null;
      }
      android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
      if (((iin!=null)&&(iin instanceof com.testaidl.IPerson))) {
        return ((com.testaidl.IPerson)iin);
      }
      return new com.testaidl.IPerson.Stub.Proxy(obj);
    }

Análisis lógico:

  • Devuelve nulo si el parámetro obj es nulo. obj es un objeto IBinder, y obj es el parámetro del método onServiceConnected, que representa el parámetro de retorno después de que el servicio de conexión sea exitoso. Si es el mismo proceso, el objeto es la instancia del servicio IPerson. Si es cruzado proceso, es IPerson en el cliente.Objeto de proxy remoto.
  • Si el parámetro obj no está vacío, intente obtener una instancia del objeto de servicio IPerson a través de obj.queryLocalInterface(DESCRIPTOR) y devuelva la instancia directamente si tiene éxito. No es realmente un proceso cruzado aquí.
  • Finalmente, cree un objeto de proxy para el servicio de IPersion (el objeto de proxy en el lado del cliente) IPerson.Stub.Proxy, y luego devuélvalo como una referencia de proxy para IPersion cuando se comunique entre procesos.

Implementación de queryLocalInterface

El método queryLocalInterface se usa para consultar si hay un servicio correspondiente en el proceso actual. Si existe en el proceso actual, significa que se llama al proceso, y no involucra procesos cruzados, y puede devolver directamente el correspondiente referencia de servicio

public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
        if (mDescriptor != null && mDescriptor.equals(descriptor)) {
            return mOwner;
        }
        return null;
    }

mOwner se asigna en el método de construcción de Stub.Si tanto el proveedor de servicios de PersonServer como el usuario del servicio de Cliente están en el mismo proceso, el mOwner no debe estar vacío.

A continuación, queda la situación de procesos cruzados, analicemos la clase interna de Proxy.

4. Proxy de clase interna de Stub

La clase de proxy se crea en el lado del cliente como una clase de proxy en el lado del cliente del lado del servicio remoto para ayudar a acceder a los servicios remotos. La clase Proxy implementa la interfaz IPerson.

constructor de proxy

private android.os.IBinder mRemote;
      Proxy(android.os.IBinder remote)
      {
        mRemote = remote;
      }

Guardado aquí, el objeto IBinder del servidor remoto pasado por el método onServiceConnected después de que la conexión del servicio se haya realizado correctamente.

comer metodo

Cuando el Cliente llama al método eat de IPerson a través del objeto Proxy, el método eat utilizará el objeto IBinder remoto guardado cuando se crea el Proxy para realizar una llamada remota.

static final int TRANSACTION_eat = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    
      @Override public boolean eat(java.lang.String food) throws android.os.RemoteException
      {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        boolean _result;
        try {
          _data.writeInterfaceToken(DESCRIPTOR);
          _data.writeString(food);
          boolean _status = mRemote.transact(Stub.TRANSACTION_eat, _data, _reply, 0);
          if (!_status && getDefaultImpl() != null) {
            return getDefaultImpl().eat(food);
          }
          _reply.readException();
          _result = (0!=_reply.readInt());
        }
        finally {
          _reply.recycle();
          _data.recycle();
        }
        return _result;
      }
      public static com.testaidl.IPerson sDefaultImpl;
    }

El valor TRANSACTION_eat se usará cuando se llame a través de procesos, así que preste atención aquí.

Análisis lógico:

  • Cree un paquete como parámetro de entrada del método eat remote.
  • Cree un paquete como valor de retorno del método eat remote.
  • Llame al método transact() del objeto IBinder remoto (que en realidad es un objeto proxy, pero podemos considerarlo como un objeto IBinder remoto) y pase el identificador único, los parámetros, la referencia del valor de retorno del método remoto eat y si para sincronizar varios objetos como parámetros para el método transact().
  • Una vez completada la llamada remota, obtenga el resultado de la llamada, maneje los errores y otra información, y finalmente extraiga el valor de retorno del método remoto y regrese directamente.

Análisis de procedimientos de llamadas remotas

Después del análisis anterior, básicamente entendemos todo el proceso de invocación remota de servicios, pero aquí hay otro punto que se implementa dentro del sistema, es decir, el proceso de procesamiento de invocación remota de transacciones y otras lógicas relacionadas. Lo analizaremos en detalle a continuación.

boolean _status = mRemote.transact(Stub.TRANSACTION_eat, _data, _reply, 0);

Llame al método transact del objeto proxy remoto, que se implementa en la clase Binder. La lógica de implementación es la siguiente:

android.os.Binder

public final boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply,
            int flags) throws RemoteException {
        if (false) Log.v("Binder", "Transact: " + code + " to " + this);

        if (data != null) {
            data.setDataPosition(0);
        }
        boolean r = onTransact(code, data, reply, flags);
        if (reply != null) {
            reply.setDataPosition(0);
        }
        return r;
    }

En este punto, el proceso de llamada actual se ha transferido del lado del Cliente al lado de PersonServer, y la capa inferior de Binder implementa el proceso de conversión. Aquí, puede considerarse simplemente como una llamada remota.

El método onTransact se llama aquí para su procesamiento:
com.testaidl.IPerson#Stub

 @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
    {
      java.lang.String descriptor = DESCRIPTOR;
      switch (code)
      {
        case INTERFACE_TRANSACTION:
        {
          reply.writeString(descriptor);
          return true;
        }
        case TRANSACTION_eat:
        {
          data.enforceInterface(descriptor);
          java.lang.String _arg0;
          _arg0 = data.readString();
          boolean _result = this.eat(_arg0);
          reply.writeNoException();
          reply.writeInt(((_result)?(1):(0)));
          return true;
        }
        default:
        {
          return super.onTransact(code, data, reply, flags);
        }
      }
    }

Aquí se leen los parámetros enviados por el cliente, se llama al método eat de la IPerson real y finalmente se devuelve el resultado.

comer código de implementación:
com.example.simpledemo.PersionService

public IBinder onBind(Intent intent) {
        return new IPerson.Stub() {
            @Override
            public boolean eat(String food) throws RemoteException {
                Log.d("PersionService", "this is Server");
                Log.d("PersionService", "Persion eat " + food);
                return true;
            }
        };
    }

La implementación real de la interfaz remota es el objeto IPerson.Stub en la clase PersionService.

resumen

Desde un punto de vista práctico, este artículo analiza en detalle la implementación específica de la invocación de servicio entre procesos, así como el proceso de invocación y el análisis de principios.

El proceso de llamada se puede resumir así:

1. El servicio que proporciona servicios a través de los procesos debe implementarse a través de AIDL (la capa inferior en realidad usa Binder).
2. El lado del cliente vincula un Servicio remoto a través de PersionService.
3. Después de que el enlace sea exitoso, el Cliente obtiene el objeto proxy IBinder local del servicio remoto a través del método onServiceConnected de ServiceConnection.
4. El cliente obtiene el objeto proxy de la interfaz de servicio (IPerson) del lado del cliente a través del objeto proxy IBinder de servicio remoto (si es el mismo proceso, devuelve directamente el objeto de servicio IBidner).
5. El cliente llama al método eat del objeto proxy remoto, y la capa inferior está controlada por Binder y llama al método transact en el lado del servidor.
6. El método transact del lado del servidor llama al método onTransact de IPerson.Stub.
7. El método onTransact del lado del servidor llama a la implementación final del método eat: el objeto IPerson.Stub creado en la clase PersionService.

Supongo que te gusta

Origin blog.csdn.net/ahou2468/article/details/122578120
Recomendado
Clasificación