AIDL的入门使用

android中为了能够实现高效率的跨进程通信,实现了一种特殊的IPC(inter-process communication)跨进程通信方式,AIDL(android interface definition language,android接口定义语言),该篇主要以一个示例来讲解aidl在实际开发中如何使用。

这里我们以一个登陆为例,调用远端的登录接口,通过callback返回结果。

对于服务端,开发步骤如下:

  • 1.定义aidl文件

在androidstudio中,右键鼠标->New->AIDL->AIDL File,设置aidl文件的名称,如图所示:
这里写图片描述

创建好aidl后,我们的目录结构如下所示:
这里写图片描述
当前我们的server端的应用包名为:com.aidl.server。在创建aidl文件时,androidstudio会在main目录下,创建一个aidl文件夹,在该aidl文件夹中会创建和应用相同的包,这里为:com.aidl.server,我们的aidl文件就放在这个包下。此处有3个aidl文件,分别为:
ILoginManager.aidl:声明了登录的接口
LoginCallback.aidl:登录结果的回调
LoginInfo.aidl:登录的信息封装,该文件在model文件夹下。

我们看看各自的aidl的内容。
ILoginManager.aidl

package com.aidl.server;

import com.aidl.server.LoginCallback;

interface ILoginManager {
    void login(String name,String pwd,in LoginCallback callback);
}

LoginCallback.aidl

package com.aidl.server;

import com.aidl.server.model.LoginInfo;

interface LoginCallback {

    void result(int errcode, in LoginInfo loginInfo);
}

LoginInfo.aidl:

package com.aidl.server.model;

parcelable LoginInfo;

这里以一个调用远程的登录接口为例,通过一个callback返回结果。

1.在远程server端定义aidl

LoginManager.aidl:

package com.aidl.server;

import com.aidl.server.LoginCallback;

interface ILoginManager {
    void login(String name,String pwd,in LoginCallback callback);
}

LoginCallback.aidl:

package com.aidl.server;

import com.aidl.server.model.LoginInfo;

interface LoginCallback {

    void result(int errcode, in LoginInfo loginInfo);
}

LoginInfo.aidl:

package com.aidl.server.model;

parcelable LoginInfo;

aidl和我们写interface很相似,不过aidl中需要将public这个关键字去掉。

注意一:

AIDL默认支持的数据类型有:

1.Java中八种基本数据类型:byte、short、int、long、char、boolean,float、double

2.String

3.CharSequence

4.List和Map:它们内部中的元素也必须是AIDL支持的数据类型或者是Parcelable,或者是其他aidl自动生成的类

注意二:

在使用aidl跨进程通信的时候,数据的传递是有流向的,从client到server、从server到client,或者两者之间双向传递,分别对应的tag关键字为:in,out,inout。其中基本数据类型、String和CharSequence的默认流向是in,而且不能修改。其他类型必须显示的声明数据的定向tag。这个定向tag只针对参数,不针对方法返回值。在客户端调用远程接口,如果是in,那么server端将能够收到数据,如果是out,server端收不到数据,但是如果在server端修改数据,可以同步到client端,如果是inout则server端和client都可以收到数据。

注意三:

在使用自定义的数据类型的时候,如果使用的是Parcelable,需要申明一个对应的aidl文件,这里如LoginInfo.aidl里面所示。

  • 2.创建实现Parcelable接口的类,对应上一步的LoginInfo.aidl。(可以使用插件一键生成)

    public class LoginInfo implements Parcelable{
        public String name;
        public String pwd;
        public boolean success;
    
    
        @Override
        public int describeContents() {
            return 0;
        }
    
        @Override
        public void writeToParcel(Parcel dest, int flags) {
            dest.writeString(this.name);
            dest.writeString(this.pwd);
            dest.writeByte(this.success ? (byte) 1 : (byte) 0);
        }
    
        public LoginInfo() {
        }
    
        protected LoginInfo(Parcel in) {
            this.name = in.readString();
            this.pwd = in.readString();
            this.success = in.readByte() != 0;
        }
    
        public static final Creator<LoginInfo> CREATOR = new Creator<LoginInfo>() {
            @Override
            public LoginInfo createFromParcel(Parcel source) {
                return new LoginInfo(source);
            }
    
            @Override
            public LoginInfo[] newArray(int size) {
                return new LoginInfo[size];
            }
        };
    }
    
  • 3.编译或者同步工程,上一步创建的aidl文件会自动生成对应的类文件,创建继承Service的类,在onBinde方法中返回一个IBinder对象。创建这个IBinder对象就是创建第一步中aidl文件生成的类中的Stub类对象。该IBinder对象就是server端给client提供服务功能的对象。

比如我们这里需要给client提供ILoginManger里面的功能,在编译工程后会生成一个ILoginManger.java类。该类中有一个Stub类,我们返回的IBinder对象实际上就是这个Stub对象。

自动生成的ILoginManger.java类如下所示:

package com.aidl.server;    
public interface ILoginManager extends android.os.IInterface {
    public static abstract class Stub extends android.os.Binder implements com.aidl.server.ILoginManager {
        private static final java.lang.String DESCRIPTOR = "com.aidl.server.ILoginManager";

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        public static com.aidl.server.ILoginManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.aidl.server.ILoginManager))) {
                return ((com.aidl.server.ILoginManager) iin);
            }
            return new com.aidl.server.ILoginManager.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 {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_login: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    java.lang.String _arg1;
                    _arg1 = data.readString();
                    com.aidl.server.LoginCallback _arg2;
                    _arg2 = com.aidl.server.LoginCallback.Stub.asInterface(data.readStrongBinder());
                    this.login(_arg0, _arg1, _arg2);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.aidl.server.ILoginManager {
            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 void login(java.lang.String name, java.lang.String pwd, com.aidl.server.LoginCallback callback) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(name);
                    _data.writeString(pwd);
                    _data.writeStrongBinder((((callback != null)) ? (callback.asBinder()) : (null)));
                    mRemote.transact(Stub.TRANSACTION_login, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

        static final int TRANSACTION_login = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }
    public void login(java.lang.String name, java.lang.String pwd, com.aidl.server.LoginCallback callback) throws android.os.RemoteException;
}

可以看到Stub类继承自Binder类,实现了ILoginManger接口,即我们自己申明的aidl文件接口。而Bindler类又实现了IBinder接口。所以我们返回的IBinder对象需要实现了Stub类的对象。

我们的ServiceL类如下所示:

public class LoginService extends Service {

    @Override
    public IBinder onBind(Intent intent) {
        return new ILoginManager.Stub() {
            @Override
            public void login(String name, String pwd, LoginCallback callback) throws RemoteException {
                LoginInfo info = new LoginInfo();
                info.name = name;
                info.pwd = pwd;
                if (TextUtils.equals("admin",name) && TextUtils.equals(pwd,"123")) {
                    info.success = true;
                    callback.result(0,info);
                }else {
                    info.success = false;
                    callback.result(-1,info);
                }
            }
        };
    } 
}

服务端需要实现aidl中声明的方法,这里我们通过callback返回结果。

server的开发步骤就这几个,还是比较简单的。下面讲client的开发流程。

  • 1.需要拷贝server端中的所有aidl文件在本地,文件的包名需要和server端一一对应。如果有Parcelable类,client端同样也需要有一个同名的类。

我们的client的aidl的目录结构如下:
这里写图片描述
aidl的目录结构和server端一致。这里我们的client端的包名为:com.aidl.client。这样client和server端都有aidl声明的类了。

  • 2.在我们的activity中调用bindService来绑定远程服务,返回远程IBinder的代理对象(Client是拿不到远程真正的IBinder对象的,除非它们都在同一个进程)。

    public class LoginActivity extends Activity{
        private ILoginManager loginManager;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_login);
    
            findViewById(R.id.login).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (loginManager == null) {
                        return;
                    }
                    try {
                        loginManager.login("admin", "123", new LoginCallback.Stub() {
                            @Override
                            public void result(int errcode, LoginInfo loginInfo) throws RemoteException {
                                if (errcode == 0) {
                                    Toast.makeText(getApplicationContext(),"登录成功" + loginInfo,Toast.LENGTH_SHORT).show();
                                }else {
                                    Toast.makeText(getApplicationContext(),"登录失败" + loginInfo,Toast.LENGTH_SHORT).show();
                                }
                            }
                        });
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            });
    
            Intent intent = new Intent("com.aidl.loginservice");
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                Intent explicitIntent = createExplicitIntent(getApplicationContext(), intent);
                if (explicitIntent != null) {
                    bindService(explicitIntent, connection, Context.BIND_AUTO_CREATE);
                }
            }else {
                bindService(intent, connection, Context.BIND_AUTO_CREATE);
            }
        }
    
        private ServiceConnection connection = new ServiceConnection() {
            @Override
            public void onServiceConnected(ComponentName name, IBinder service) {
                loginManager = ILoginManager.Stub.asInterface(service);
                Toast.makeText(getApplicationContext(),"绑定成功",Toast.LENGTH_SHORT).show();
            }
    
            @Override
            public void onServiceDisconnected(ComponentName name) {
                Toast.makeText(getApplicationContext(),"绑定失败",Toast.LENGTH_SHORT).show();
            }
        };
    
        public Intent createExplicitIntent(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;
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            unbindService(connection);
        }
    }
    

在bindService成功后,IBinder对象在ServiceConnection的onServiceConnected方法中返回,这里我们需要调用ILoginManger.Stub.asInterface()方法将其转换为ILoginManger对象。

注意:

在5.0以上bindService只能显示绑定,我们是通过server端中的intent-filter来识别server端的,如果我们使用如下方式来bingService:
Intent intent = new Intent("com.aidl.loginservice");bindService(context,con,Context.BIND_AUTO_CREATE),在21以上版本是会抛出异常的,其中”com.aidl.loginservice”是server提供的intent-filter参数。

在ContextImpl中,bindService的时候会调用如下方法:

private void validateServiceIntent(Intent service) {
    if (service.getComponent() == null && service.getPackage() == null) {
        if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
            IllegalArgumentException ex = new IllegalArgumentException(
                    "Service Intent must be explicit: " + service);
            throw ex;
        } else {
            Log.w(TAG, "Implicit intents with startService are not safe: " + service
                    + " " + Debug.getCallers(2, 3));
        }
    }
}

我们可以看到,在21版本以上(包含21),如果我们的intent中component和package都为空的话就会抛出异常。我们的解决办法当然就是构造一个component或者package了。我们的构造方式如下,通过packagemanager来找到server端的packageName和className,然后创建一个component给intent就可以了。

public Intent createExplicitIntent(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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值