AIDL学习总结:使用AIDL实现跨进程通信



前言

  本文是对AIDL跨进程通信的学习和总结,主要介绍了AIDL的基本概念和使用方法,通过一个简单的Demo来展示AIDL跨进程通信,是对自己前几天学习的简要总结。

AIDL

基本概念

  AIDL(Android Interface Definition Language)是一种接口定义语言,用于生成可以在Android设备上两个进程之间进行进程通信,AIDL内部主要通过Binder机制来实现, 适用于进程之间交互频繁、通信数据量小的场景。
  在使用AIDL进行跨进程通信的时候,通常将请求通信的一方称之为客户端(Client),客户端的主要工作就是发送数据;而接收通信数据的一方称之为服务端(Server),服务端主要工作是处理客户端发送过来的数据,并通过回调(Callback)的方式返回客户端数据,实现双向通信。

AIDl数据类型

  AIDL支持以下数据类型:
  Java的8种基本类型,即int、long、char等。
  String和CharSequence
  List:其中的每个元素都必须是AIDL支持的。客户端实际接收的具体类始终是ArrayList,但生成的方法使用的是List接口。
  Map:其中的每个元素都必须是AIDL支持的。客户端实际接收的具体类始终是HashMap,但生成的方法使用的是Map接口。
  Parcelable:必须要显式import,即使它跟.aidl是同一个包下。
  AIDL接口:必须要显式import,即使它跟.aidl是同一个包下。
  AIDL中的方法:可有零、一或多个参数,可有返回值或void。

  AIDL中除了基本数据类型(默认为in,不能是其他方向),其他类型的参数必须标上方向:
  in,表示输入型参数,由客户端赋值;
  out,表示输出型参数,由服务端赋值;
  inout,表示输入输出型参数,可由客户端或服务端赋值;
  AIDL只expose方法,不会expose静态变量。

第一个Demo:Client向Server发送数据

  第一个Demo是Client输入用户名和密码,并通过AIDL接口发送给Server,简单展示了AIDL的基本用法。

Server端工程

  新建工程AIDLServer,作为服务端App,在main文件夹下建立一个aidl文件夹,用于存放aidl文件,新建aidl文件IMyAidlInterface.aidl:
在这里插入图片描述
  文件内容如下:

// IMyAidlInterface.aidl
package com.jason.aidlserver;

import com.jason.aidlserver.ICallback;
// Declare any non-default types here with import statements

interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    void login(String userName,String passWord);
}

  其中,方法basicTypes是自动生成的,如果不需要的话,完全可以删掉。方法login是我们自定义的,用于发送用户名和密码。生成之后,点击Make Project按钮,生成AIDL对应的.java文件:
在这里插入图片描述
  如果点开这个文件的话,会看到AS对.aidl文件中的interface的具体实现,我们在这里不再对源码进行解读,你只需要知道,里面的Stub抽象类是实现AIDL接口核心部分,它继承自Binder(这也直接印证了AIDL接口依赖于Binder实现) 。
  然后在我们的Java源文件包中新建一个Service,这里命名为AIDLService,用于实现接收Client的数据:

package com.jason.aidlserver;

public class AIDLService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        return new MyBinder();
    }

    public interface OnLoginListener {
        void login(String userName, String passWord);
    }

    private OnLoginListener onLoginListener;

    public void setOnLoginListener(OnLoginListener onLoginListener) {
        this.onLoginListener = onLoginListener;
    }

    class MyBinder extends IMyAidlInterface.Stub {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, 
        	float aFloat, double aDouble, String aString) throws RemoteException {

        }

        @Override
        public void login(String userName, String passWord) throws RemoteException {
            if (onLoginListener != null) {
                onLoginListener.login(userName, passWord);
            }
        }
        
        public AIDLService getService() {
            return AIDLService.this;
        }
    }
}

  这里为了方便阅读,隐去了一些不必要的代码,完全的工程已经上传到GitHub,可以参看文末的链接。在这个Service中,主要是通过实现Stub抽象类来扩展其中的login方法,同时与Service的IBinder进行绑定,用于下一步的服务链接。此外,我们还设置了一个OnLoginListener接口,把实现部分抽离出来,提供了一个接口。在MainActivity代码中,存在如下实现:

public class MainActivity extends AppCompatActivity implements OnLoginListener{

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.text_view);
        Intent intent = new Intent(this,AIDLService.class);
        bindService(intent,mAIDLConnection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection mAIDLConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            MyBinder binder = (MyBinder) iBinder;
            AIDLService aidlService = binder.getService();
            aidlService.setOnLoginListener(MainActivity.this);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    private Handler mHandler = new Handler();

    @Override
    public void login(final String userName, final String passWord) {
        mHandler.post(()->{
            textView.setText("Message from client:\nuserName:"+userName+"\npassWord:"+passWord);
            Toast.makeText(this,"login successful!",Toast.LENGTH_LONG).show();
        });
    }
}

  MainActivity代码主要做了两件事:连接服务实现AIDL接口。其中,连接服务需要在AndroidManifest文件中进行注册,如下:

		<service android:name=".AIDLService"
            android:exported="true">
            <intent-filter>
                <action android:name="com.jason.aidlserver"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </service>

  而AIDL接口的实现需要注意,虽然这里实现在了MainActivity中,但是真正调用这段代码的场景未必在主线程中,因此,如果代码中存在UI操作的话,需要用一个主线程的handler来实现相关UI操作。至此,Server的工作告一段落。

Client端工程

  首先,与Server相同,第一步是创建AIDL文件,需要特别注意的是:AIDL的文件名与包名和Server必须相同,因此,我们直接把Server端的AIDL文件夹整体复制粘贴到Client的对应位置即可。弄完了之后,记得Make Project一下
  Client端的实现要简单的多,主要做了两件事:连接Server端的Service发送数据,因此,Client的代码实现为:


public class MainActivity extends AppCompatActivity {

   ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        intent = new Intent();
        intent.setAction("com.jason.aidlserver");
        intent.setPackage("com.jason.aidlserver");
        bindService(intent, new ConnectCallBack(), Context.BIND_AUTO_CREATE);

        
        loginButton = findViewById(R.id.login);
        loginButton.setOnClickListener(v -> {
            login();
        });

        display = findViewById(R.id.display);
    }

    public void login() {
        if (iMyAidlInterface != null) {
            try {
                userName = userNameEditText.getText().toString();
                passWord = passWordEditText.getText().toString();
                iMyAidlInterface.login(userName, passWord);// 【AIDL接口调用】
            } catch (Exception e) {

            }
        } 
    }

    class ConnectCallBack implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
        }

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

  同样的,为了显示方便,隐去了不必要的代码(不然太长了……)。
  注意到,这里的服务连接主要是通过Intent+bindService 的方式,其中,setPackage设置的是Server APK的包名,而不是Server服务的包名。设置不正确会导致bindService为false,setAction就是我们刚才在AndroidManifest中注册的Action。至此,Client的工作就完成了。我们看一下最终实现效果:

在这里插入图片描述
  从Client中输入用户名和密码后,顺利的传入到了Server端,并显示了出来。至此,我们的第一个AIDL跨进程通信Demo完成了。

第二个Demo:Server回调Client接口

  但是显然,这个Demo存在一个缺陷:这个AIDL通信是单工通信,我们无法从Server向Client发送数据。
  如何实现全双工通信呢?常用的做法是再写一个AIDL接口,然后作为参数放在调用的另一个AIDL方法中,这个过程称之为回调。听起来有点抽象,但是实际实现起来很简单。

Client端回调AIDL

  Client工程中,在AIDL文件夹中生成一个ICallback.aidl文件:
在这里插入图片描述
  内容如下:

// ICallback.aidl
package com.jason.aidlserver;

// Declare any non-default types here with import statements
interface ICallback {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void server2client(in String data);
}

  然后,修改另一个AIDL文件:

// IMyAidlInterface.aidl
package com.jason.aidlserver;

import com.jason.aidlserver.ICallback;
// Declare any non-default types here with import statements

interface IMyAidlInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);

    void login(String userName,String passWord,ICallback icb);
}

  主要变化是login多了一个ICallback参数,同样的,Make Project一下,生成对应的接口文件。然后在MainActivity做对应的修改:

public class MainActivity extends AppCompatActivity {
    ...
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        intent = new Intent();
        intent.setAction("com.jason.aidlserver");
        intent.setPackage("com.jason.aidlserver");
        bindService(intent, new ConnectCallBack(), Context.BIND_AUTO_CREATE);

        loginButton = findViewById(R.id.login);
        loginButton.setOnClickListener(v -> {
            login();
        });

        display = findViewById(R.id.display);
    }

    public void login() {
       
        if (iMyAidlInterface != null) {
            try {
                userName = userNameEditText.getText().toString();
                passWord = passWordEditText.getText().toString();
                iMyAidlInterface.login(userName, passWord, iCallback);
            } catch (Exception e) {

            }
        } 
    }

    class ConnectCallBack implements ServiceConnection {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            iMyAidlInterface = IMyAidlInterface.Stub.asInterface(iBinder);
        }

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

    private ICallback iCallback = new ICallback.Stub() {
        @Override
        public void server2client(String code) throws RemoteException {
                display.setText(code);
        }
    };
}

  不难发现,改动仅在于ICallback的实现上,这段代码实现在Client端,但是却是由Server调用的(和刚才反过来了)。

Server端回调AIDL

  与刚才相同,还是先创建一个一模一样的AIDL文件,Make Project一下即可。然后,先修改AIDLService中的内容:

		@Override
        public void login(String userName, String passWord, ICallback iCallback) throws RemoteException {
            if (onLoginListener != null) {
                onLoginListener.login(userName, passWord);
                iCallback.server2client("Message from Server:\nuserName:"+userName+"\npassWord:"+passWord);
            }
        }

  Server端改动比较小,因为Server只需要调用就行了,所以这里仅列出了改动的地方,因为login方法定义改变了,因此这里也要对应的改动,然后,我们在这里调用iCallback的server2client方法(当然也可以在其他地方调用,只要iCallback非null即可),然后Server的工作就完成了。看一下最终的效果:
在这里插入图片描述
  为了显示效果,这里加了一个TextView,可以看出,Client向Server发送完用户名和密码后,Server也向Client返回了一串字符串,并在Client中的TextView显示了出来。至此,一个简单的全双工AIDL通信完成。

发布了222 篇原创文章 · 获赞 558 · 访问量 38万+

猜你喜欢

转载自blog.csdn.net/CV_Jason/article/details/101778307