Android Service AIDL

Android 中的各个应用程序都运行在自己的进程中,进程之间一般是无法直接进行数据交换的

而为了实现跨进程,Android 提供了上一章节提到的 Binder 机制,而这个机制使用的接口语言就是: AIDL (Android Interface Definition Language)

AIDL 语法很简单,只是定义两个进程间的通信接口而已

而生成符合通信协议的 Java 代码则是由 Android SDK 的 platform-tools/aidl.exe 工具生成

生成对应的接口文件在: gen 目录下,一般是 Xxx.java 的接口

在该接口中包含一个 Stub 的内部类,该类中实现了在该类中实现了 IBinder 接口与自定义的通信接口

Sub 类会作为远程 Service 的回调类——实现了 IBinder 接口,所以可作为 Service 的 onBind() 方法的返回值

AIDL 接口

开始编写 AIDL 接口文件前,我们先了解一些注意事项

  1. 接口名需要与 aidl 文件名相同

  2. 接口和方法前面 不要加访问权限修饰符 public ,private,protected 等,也不能用 static final

  3. AIDL 默认支持的类型包括 Java基本类型 , String , List , Map , CharSequence ,除此之外的其它类型都 需要 import 声明,对于使用自定义类型作为参数或者返回值, 自定义类型需要实现 Parcelable 接口

  4. 自定义类型和 AIDL 生成的其它接口类型在 aidl 描述文件中,应该显式 import,即便在该类和定义的包在同一个包中

Android Studio 创建

  1. Android Studio 创建 AIDL 需要在 main 目录下新建一个 aidl 文件夹

    在 app 目录上点右键,然后选择 New->Folder->AIDL FOLDER

  2. 接着定义一个和 aidl 包名相同的包

  3. 然后创建一个 aidl 文件

    在包名上点右键,然后选择 New->AIDL 文件

  4. 最后点击 Build->Make Project 编译

AIDL 实现两个进程间的简单通信

先来看看最后的效果图


创建服务端

  1. 创建一个 空的 Android 项目 cn.twle.android.AIDLServer

  2. 在 app 目录下创建 aidl 目录

  3. 在 aidl 目录下新建包 cn.twle.android.aidl

  4. 在 cn.twle.android.aidl 包下新建文件 ILanguage.aidl

    package cn.twle.android.aidl;
    
    interface ILanguage {
        String queryLanguage(int num);
    }
    
  5. 点击 Build->Make Project 编译代码

    打开 ILanguage.java 可以看到里面的代码如下

    /*
     * This file is auto-generated.  DO NOT MODIFY.
     * Original file: /Android/AIDLServer/app/src/main/aidl/cn/twle/android/aidl/ILanguage.aidl
     */
    package cn.twle.android.aidl;
    public interface ILanguage extends android.os.IInterface
    {
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements cn.twle.android.aidl.ILanguage
    {
    private static final java.lang.String DESCRIPTOR = "cn.twle.android.aidl.ILanguage";
    /** Construct the stub at attach it to the interface. */
    public Stub()
    {
    this.attachInterface(this, DESCRIPTOR);
    }
    /**
     * Cast an IBinder object into an cn.twle.android.aidl.ILanguage interface,
     * generating a proxy if needed.
     */
    public static cn.twle.android.aidl.ILanguage asInterface(android.os.IBinder obj)
    {
    if ((obj==null)) {
    return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin!=null)&&(iin instanceof cn.twle.android.aidl.ILanguage))) {
    return ((cn.twle.android.aidl.ILanguage)iin);
    }
    return new cn.twle.android.aidl.ILanguage.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_queryLanguage:
    {
    data.enforceInterface(DESCRIPTOR);
    int _arg0;
    _arg0 = data.readInt();
    java.lang.String _result = this.queryLanguage(_arg0);
    reply.writeNoException();
    reply.writeString(_result);
    return true;
    }
    }
    return super.onTransact(code, data, reply, flags);
    }
    private static class Proxy implements cn.twle.android.aidl.ILanguage
    {
    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.lang.String queryLanguage(int num) throws android.os.RemoteException
    {
    android.os.Parcel _data = android.os.Parcel.obtain();
    android.os.Parcel _reply = android.os.Parcel.obtain();
    java.lang.String _result;
    try {
    _data.writeInterfaceToken(DESCRIPTOR);
    _data.writeInt(num);
    mRemote.transact(Stub.TRANSACTION_queryLanguage, _data, _reply, 0);
    _reply.readException();
    _result = _reply.readString();
    }
    finally {
    _reply.recycle();
    _data.recycle();
    }
    return _result;
    }
    }
    static final int TRANSACTION_queryLanguage = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }
    public java.lang.String queryLanguage(int num) throws android.os.RemoteException;
    }
    

    我们只要关注 asInterface(IBinder) 和我们定义的接口中的 queryLanguage() 方法

    该方法会把 IBinder 类型的对象转换成 ILanguage 类型的,必要时生成一个代理对象返回结果

  6. 在 MainActivity.java 同一目录下新建文件 MsLanguageService.java

    继承 Service 类,同时也自定义了一个 LanguageQueryBinder 类用来继承 ILanguage.Stub 类

    也就是实现了 IPerson 接口和 IBinder 接口

    然后实例化自定义的 Stub 类,并重写 Service 的 onBind() 方法,返回一个 binder 对象

    package cn.twle.android.aidlserver;
    
    import android.app.Service;
    import android.content.Intent;
    import android.os.IBinder;
    import android.os.RemoteException;
    import cn.twle.android.aidl.ILanguage.Stub;
    
    public class MsLanguageService extends Service {
    
        private IBinder binder = new LanguageQueryBinder();
        private String[] names = {"python","php","java","kotin","c","swift"};
    
        private String query(int num)
        {
            if(num > 0 && num < 6){
                return names[num - 1];
            }
            return null;
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return binder;
        }
    
        private final class LanguageQueryBinder extends Stub{
            @Override
            public String queryLanguage(int num) throws RemoteException {
                return query(num);
            }
        }
    }
    
  7. 然后修改 AndroidManifest.xml 文件中注册 Service

    <service android:name=".MsLanguageService">
        <intent-filter>
            <action android:name="cn.twle.android.aidlserver.MsLanguageService" />
            <category android:name="android.intent.category.DEFAULT" />
        </intent-filter>
    </service>
    

这里并没有提供 Activity界面,但是改应用提供的 Service 可以供其它 app 来调用

客户端

  1. 创建一个 空的 Android 项目 cn.twle.android.AIDLClient

  2. 重复上面的 [2-5] 步骤创建 ILanguage.java

  3. 先编译一下

  4. 修改 activity_main.xml

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >
    
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="请输入要查询的语言" />
    
        <EditText
            android:id="@+id/ipt_search"
            android:inputType="number"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>
        <Button 
            android:id="@+id/btn_query"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="查询"/>
    
        <TextView 
            android:id="@+id/txt_show"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    
    </LinearLayout>
    
  5. 修改 MainActivity.java

    1. 自定义 LanguageConnection 类实现 ServiceConnection 接口

    2. 以 LanguageConnection 对象作为参数,调用 bindService() 绑定远程 Service

      bindService(service,conn,BIND_AUTO_CREATE);
      
      第三个参数是设置如果服务没有启动的话,自动创建
      
    3. 和本地Service不同,绑定远程 Service 的 ServiceConnection 并不能直接获取 Service 的 onBind() 方法返回IBinder 对象,只能返回 onBind() 方法所返回的 代理对象

      需要做如下处理

      iLanguage = ILanguage.Stub.asInterface(service)
      

      再接着完成初始化

    MainActivity.java

    package cn.twle.android.aidlclient;
    
    import android.content.ComponentName;
    import android.content.Intent;
    import android.content.ServiceConnection;
    import android.os.Bundle;
    import android.os.IBinder;
    import android.os.RemoteException;
    import android.support.v7.app.AppCompatActivity;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.TextView;
    
    import cn.twle.android.aidl.ILanguage;
    
    public class MainActivity extends AppCompatActivity implements View.OnClickListener{
    
        private EditText ipt_search;
        private Button btn_query;
        private TextView txt_show;
        private ILanguage iLanguage;
        private LanguageConnection conn = new LanguageConnection();
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            bindViews();
            //绑定远程Service
            Intent service = new Intent("cn.twle.android.aidlserver.MsLanguageService");
            service.setPackage("cn.twle.android.aidlserver");
    
            bindService(service, conn, BIND_AUTO_CREATE);
            btn_query.setOnClickListener(this);
        }
    
        private void bindViews() {
            ipt_search = (EditText) findViewById(R.id.ipt_search);
            btn_query = (Button) findViewById(R.id.btn_query);
            txt_show = (TextView) findViewById(R.id.txt_show);
        }
    
        @Override
        public void onClick(View v) {
            String number = ipt_search.getText().toString();
            int num = Integer.valueOf(number);
            try {
                txt_show.setText(iLanguage.queryLanguage(num));
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            ipt_search.setText("");
        }
    
        private final class LanguageConnection implements ServiceConnection {
            public void onServiceConnected(ComponentName name, IBinder service) {
                iLanguage = ILanguage.Stub.asInterface(service);
    
            }
            public void onServiceDisconnected(ComponentName name) {
                iLanguage = null;
            }
        }
    }
    

接下来先启动 AIDLServivce,然后再启动 AIDLClient

当然也可以直接启动 AIDLClient,也会获得同样效果

Android 基础教程

关于   |   FAQ   |   我们的愿景   |   广告投放   |  博客

♥   简单教程,简单编程 - IT 入门首选站

Copyright © 2013-2022 简单教程 twle.cn All Rights Reserved.