Comprehensive analysis of Service - in-depth understanding of AIDL

This article mainly sorts out the relevant content of remote services in Service, focusing on learning the related usage of adil. If the foundation of Servcie is not solid, it is recommended to read the comprehensive analysis of Servcie-Comprehensive analysis of local services

AIDL in brief

The definition of AIDL is the Android Interface Definition Language, which is the Android interface definition language. Yes, AIDL is a language, then it contains a series of syntax definitions and various usages of it.

Why did Android design this language?

The following is an introduction to AIDL in the official documentation

Note: Using AIDL is necessary only if you allow clients from different applications to access your service for IPC and want to handle multithreading in your service. If you do not need to perform concurrent IPC across different applications, you should create your interface by implementing a Binder or, if you want to perform IPC, but do not need to handle multithreading, implement your interface using a Messenger. 

Note: AIDL is only required if you allow clients from different applications to access your IPC service and want to handle multithreading in the service. If you don't need to perform concurrent IPC between different applications, you should create your interface by implementing a Binder, or, if you want to perform IPC, but don't need to deal with multithreading, you can use a Messenger to implement your interface.

It can be seen that AIDL is suitable for inter-process communication in Android, and the scenario when concurrent IPC needs to be performed. While Messenger communicates between processes in Android, it is synchronous and cannot handle concurrency.

AIDL syntax

The types supported in AIDL are as follows:

  1.  All basic data types in Java, char, byte, short, int, long, float, double, boolean
  2. String type
  3. CharSequence
  4. List, the data elements in the List must be able to be supported by AIDL
  5. Map: only supports HashMap, each element in it must be supported by AIDL, including key and value
  6. Parcelable: An object that implements the Parcelable interface
  7. AIDL Interfaces: All AIDL interfaces themselves are also available in AIDL files.
  8. Directional tag: where in means that data can only flow from the client to the server, out means that data can only flow from the server to the client, and inout means that the data can flow in both directions between the server and the client.

AIDL writing process

The implementation of AIDL is divided into the following steps:

  1. Create an entity class to implement the Parcelable interface for serialization and deserialization
  2. Create AIDL file
  3. Create a service on the server side, create a Binder object and return
  4. Implement the ServiceConnection object on the client side, and obtain the AIDL class entity class provided by the server side for interaction

AIDL specific implementation

  • Create NBAStar class to implement Parcelable interface for serialization and deserialization
public class NBAStar implements Parcelable {
    private String name;
    private int age;
    private String team;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getTeam() {
        return team;
    }

    public void setTeam(String team) {
        this.team = team;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeString(team);
    }


    public static final Creator<NBAStar> CREATOR = new Creator<NBAStar>() {
        @Override
        public NBAStar createFromParcel(Parcel source) {
            NBAStar NBAStar = new NBAStar();
            NBAStar.setName(source.readString());
            NBAStar.setAge(source.readInt());
            NBAStar.setTeam(source.readString());
            return NBAStar;
        }

        @Override
        public NBAStar[] newArray(int size) {
            return new NBAStar[size];
        }
    };

    @Override
    public String toString() {
        return name + "," + age + "," + team + ";";
    }
}
Parcelable is the serial number interface provided by android. The implementation of Parcelable mainly does three things: serialization, description, and deserialization, respectively implementing the writeToParcel() method, describeContents() method, and createFromParcel() in the inner class Creator. ) method and newArray() method, the specific serialization will not be explained here.

  • Create AIDL file


The created directory is shown in the figure above, in which the IAidlInterfase.aidl file is the AIDL interface file, which is used for client calls

// IAidlInterface.aidl
package review.heal.com.review;

// Declare any non-default types here with import statements
import review.heal.com.review.NBAStar;

interface IAidlInterface {
   List<NBAStar> getAllStars();

   void addStar(in NBAStar NBAStar);
}

The NBAStar class that implements the Parcelable interface needs an entity mapping .aidl file NBAStar.aidl, and the entity class and type need to be declared in NBAStar.aidl

// IAidlInterface.aidl
package review.heal.com.review;

// Declare any non-default types here with import statements

parcelable NBAStar;

Note: The package name of NBAStar.aidl should be consistent with the package name of the entity class NBAStar

After creating the above files, select Build->Make Project in Android Studio, an error will appear at this time

Error:Execution failed for task ':app:compileDebugJavaWithJavac'.
> Unable to find source java class:
 '/Users/Shared/app/AndroidApp/remoteService/app/src/main/aidl/review/heal/com/review/NBAStar.java'

出现这个错误的原因,是因为在Android Studio中使用Gradle来构建Android项目,而Gradle会默认使用sourceSet来配置不同文件的访问路径,Gradle默认将Java文件访问路径设置在Java包下,若Java文件被放置在aidl包下,那么Android Studio将无法找到此java文件。问题的解决方法实在build.gradle中添加下面语句

 sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'src/main/aidl']
        }
    }

意思是将Java代码的访问路径设置成Java包和aidl包,这样便能在aidl包中找到java文件了。

此时再在Android Studio中选择Build->Make Project,Android Studio就会帮我们生成IAidlInterface文件。


以下代码由系统自动生成,这些代码用来设定基于IAidlInterface接口的Binder IPC连接,以便客户端与远程服务通信。

public interface IAidlInterface extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements review.heal.com.review.IAidlInterface {
        private static final java.lang.String DESCRIPTOR = "review.heal.com.review.IAidlInterface";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an review.heal.com.review.IAidlInterface interface,
         * generating a proxy if needed.
         */
        public static review.heal.com.review.IAidlInterface asInterface(android.os.IBinder obj) {
            ......
            return new review.heal.com.review.IAidlInterface.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 {
            ......
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements review.heal.com.review.IAidlInterface {
            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 java.util.List<review.heal.com.review.NBAStar> getAllStars() throws android.os.RemoteException {
                ......
                return _result;
            }

            @Override
            public void addStar(review.heal.com.review.NBAStar NBAStar) throws android.os.RemoteException {
                ......
            }
        }

        static final int TRANSACTION_getAllStars = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_addStar = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
    }
//所有的返回值前都不需要加任何东西,不管是什么数据类型

    public java.util.List<review.heal.com.review.NBAStar> getAllStars() throws android.os.RemoteException;

    public void addStar(review.heal.com.review.NBAStar NBAStar) throws android.os.RemoteException;
}

  • 在服务端创建service,创建Binder对象并返回
public class NBAStarAidlService extends Service {
    private List<NBAStar> nbaStars;

    private IBinder iBinder = new IAidlInterface.Stub() {
        //返回所有NBAStar
        @Override
        public List<NBAStar> getAllStars() throws RemoteException {
            return nbaStars;
        }

        //增加NBAStar
        @Override
        public void addStar(NBAStar NBAStar) throws RemoteException {
            nbaStars.add(NBAStar);
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        nbaStars = new ArrayList<>();
        NBAStar nbaStar = new NBAStar();
        nbaStar.setName("科比");
        nbaStar.setAge(30);
        nbaStar.setTeam("洛杉矶湖人");
        nbaStars.add(nbaStar);
        return iBinder;
    }
}

同时要在AndroidMainfest文件中声明服务

 <service android:name=".NBAStarAidlService" android:process=":remote">
            <intent-filter>
                <action android:name="review.heal.com.review.IAidlInterface"/>
            </intent-filter>
        </service>
The main thing the server does is to implement the Stub interface of IAidlInterface, and return IBinder in the onBind() method, and the client obtains the IBinder object for interaction.

  • Implement the ServiceConnection object on the client side, and obtain the AIDL class entity class provided by the server side for interaction

First, you need to transplant the IAidlInterfase.aidl, NBAStar.aidl and NBAStar entity classes of the server to the client. To ensure that the package name is the same as that of the server , select Build->Make Project in Android Studio to generate the IAidlInterface file.

Then instantiate the ServiceConnection and get the server-side IBinder object

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            iAidlInterface = IAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

通过IAidlInterface的Stub抽象类调用asInterface()方法,可以将IBinder对象转换成IAidlInterface的实例,调用bindService()方法绑定远程服务

bindService(new Intent(IAidlInterface.class.getName()), serviceConnection, Context.BIND_AUTO_CREATE);

在Android5.0之后,上面的绑定调用会提示 Service Intent must be explicit  错误,也就是说,在Android5.0之后,服务必须显式的开启。以下方法是一个隐调用转换为显式调用

    public static Intent createExplicitFromImplicitIntent(Context context, Intent implicitIntent) {
        PackageManager pm = context.getPackageManager();
        List<ResolveInfo> resolveInfo = pm.queryIntentServices(implicitIntent, 0);

        if (resolveInfo == null || resolveInfo.size() != 1) {
            return null;
        }

        ResolveInfo serviceInfo = resolveInfo.get(0);
        String packageName = serviceInfo.serviceInfo.packageName;
        String className = serviceInfo.serviceInfo.name;
        ComponentName component = new ComponentName(packageName, className);

        Intent explicitIntent = new Intent(implicitIntent);

        explicitIntent.setComponent(component);

        return explicitIntent;
    }
       bindService(new Intent(createExplicitFromImplicitIntent(MainActivity.this, new Intent(IAidlInterface.class.getName()))), serviceConnection, Context.BIND_AUTO_CREATE)

这样客户端便完成了远程绑定服务端,调用IAidlInterface的getAllStars()方法

try {
         if (iAidlInterface != null) {
          Log.e("aidl", "接收aidl数据:" + iAidlInterface.getAllStars());
          }
     } catch (RemoteException e) {
          Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
     }

调用结果:

review.heal.com.remoteservice E/aidl: Receive aidl data: [Kobe, 30, Los Angeles Lakers;]

Call the addStar() method of IAidlInterface and then call the getAllStars() method

try {
       if (iAidlInterface != null) {
           NBAStar nbaStar = new NBAStar();
           nbaStar.setName("McGrady");
           nbaStar.setAge(31);
           nbaStar.setTeam("Houston Rockets");
           iAidlInterface.addStar(nbaStar);
        }
     } catch (RemoteException e) {
        Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
     }

Call result:

review.heal.com.remoteservice E/aidl: Receive aidl data: [Kobe, 30, Los Angeles Lakers;, McGrady, 31, Houston Rockets;]
The above is the whole process of AIDL realizing cross-process communication



Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324784038&siteId=291194637