Skip to content

Latest commit

 

History

History
481 lines (396 loc) · 18.3 KB

File metadata and controls

481 lines (396 loc) · 18.3 KB

使用 AIDL 进行跨进程通信的基本步骤

AIDL(Android Interface Definition Language)是 Android 实现 跨进程通信(IPC) 的核心机制,常用于 Service 与 Client 之间的数据交互(如系统服务调用)。以下是详细实现步骤:


📌 1. 定义 AIDL 接口

在 src/main/aidl/ 目录下创建 .aidl 文件,声明接口方法:
示例:IMyService.aidl

// 包名需与 Java 类一致
package com.example.aidl;

// 定义接口
interface IMyService {
    // 基本类型参数(int, String 等可直接使用)
    int add(int a, int b);

    // 传递自定义对象需实现 Parcelable
    void sendUser(in User user);
}
  • in/out/inout:标记参数方向(输入/输出/双向)。
  • 自定义对象:需实现 Parcelable 并单独定义 AIDL(见步骤 2)。

📌 2. 实现 Parcelable 对象(可选)

若需传递自定义对象(如 User),需完成以下两步:

(1) 定义对象的 AIDL 文件

User.aidl

package com.example.aidl;
parcelable User; // 声明为 Parcelable 类型

(2) Java 类实现 Parcelable 接口

User.java

public class User implements Parcelable {
    private String name;
    private int age;

    // 构造方法、getter/setter 省略...

    // Parcelable 实现
    protected User(Parcel in) {
        name = in.readString();
        age = in.readInt();
    }

    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

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

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

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

📌 3. 实现 Service 端

(1) 创建 Service 并实现 AIDL 接口

public class MyService extends Service {
    private final IMyService.Stub binder = new IMyService.Stub() {
        @Override
        public int add(int a, int b) {
            return a + b;
        }

        @Override
        public void sendUser(User user) {
            Log.d("Service", "收到用户: " + user.getName());
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return binder; // 返回 Stub 对象
    }
}

(2) 在 AndroidManifest.xml 中声明 Service

<service
    android:name=".MyService"
    android:exported="true"  <!-- 允许其他进程绑定 -->
    android:process=":remote" />  <!-- 指定运行在独立进程 -->

📌 4. Client 端绑定 Service 并调用

(1) 绑定 Service

private IMyService myService;
private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // 将 Binder 对象转换为 AIDL 接口
        myService = IMyService.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        myService = null;
    }
};

// 绑定服务
Intent intent = new Intent(this, MyService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);

(2) 调用远程方法

// 调用基本类型方法
int result = myService.add(1, 2);

// 调用自定义对象方法
User user = new User("Alice", 25);
myService.sendUser(user);

(3) 解绑 Service(避免内存泄漏)

unbindService(connection);

📌 5. 关键注意事项

  1. 线程模型:

    • AIDL 方法默认运行在 Binder 线程池(非主线程),需自行处理线程同步。
    • 若需在主线程回调,使用 Handler 或 runOnUiThread。
  2. 异常处理:

    try {
        myService.add(1, 2);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
  3. 权限控制:

    • 在 AndroidManifest.xml 中声明自定义权限:
      <permission android:name="com.example.ACCESS_MY_SERVICE" />
    • Service 端校验权限:
      @Override
      public IBinder onBind(Intent intent) {
          if (checkCallingPermission("com.example.ACCESS_MY_SERVICE") != PERMISSION_GRANTED) {
              return null; // 拒绝绑定
          }
          return binder;
      }
  4. Android 11+ 的包可见性:

    • 若目标 App 为 Android 11+,需在 AndroidManifest.xml 中添加 <queries>:
      <queries>
          <package android:name="com.example.serviceapp" />
      </queries>

📌 6. 完整调用流程图示

+-------------------+       +-------------------+       +-------------------+
|   Client App      |       |     Binder        |       |   Service App     |
|                   |       |    (内核驱动)      |       |                   |
| 1. bindService()  | ----> | 2. 跨进程绑定      | ----> | 3. onBind()       |
|                   |       |                   |       |                   |
| 4. 调用 AIDL 方法  | ----> | 5. 跨进程调用      | ----> | 6. 执行方法并返回  |
|                   |       |                   |       |                   |
+-------------------+       +-------------------+       +-------------------+

📌 7. 与其他 IPC 方案的对比

方案 适用场景 复杂度 性能 支持数据类型
AIDL 高频跨进程调用(如系统服务) 高 高 基本类型 + Parcelable 对象
Messenger 低频单向通信 低 中 Message 对象(Bundle)
ContentProvider 结构化数据共享 中 中 Cursor/文件
Broadcast 全局事件通知 低 低 Intent 数据

📌 8. 总结

  1. 定义 AIDL 接口:声明跨进程方法。
  2. 实现 Parcelable(可选):支持自定义对象传输。
  3. Service 端:继承 Service 并返回 Stub 实现。
  4. Client 端:绑定 Service 并调用 AIDL 方法。
  5. 注意事项:线程安全、异常处理、权限控制。

AIDL 是 Android 高性能 IPC 的基石,适合复杂跨进程通信场景(如系统服务开发)。

通俗解释:AIDL 的服务端与客户端区分

在 AIDL 跨进程通信中,服务端(Server) 和 客户端(Client) 的角色可以类比为 饭店(服务端) 和 顾客(客户端):

角色 类比 在 AIDL 中的表现
服务端 饭店(提供服务的) 1. 定义菜单(AIDL 接口)
2. 实现菜品(Service 中的具体逻辑)
3. 开门营业(注册 Service)
客户端 顾客(享受服务的) 1. 拿到菜单(获取 AIDL 接口)
2. 点菜(调用远程方法)
3. 吃完结账(解绑 Service)

📌 1. 服务端(Server)做什么?

核心任务:提供数据或功能

  • 代码位置:通常是一个独立的 App 或模块(如系统服务)。
  • 关键步骤:
    1. 定义菜单:编写 .aidl 文件(如 IMyService.aidl),声明哪些方法可以被远程调用。
    2. 做菜:在 Service 中实现 AIDL 接口的具体逻辑(如计算、数据库查询)。
    3. 开门营业:在 AndroidManifest.xml 中声明 Service,并设置 android:process 让它运行在独立进程。

示例代码(服务端)

// 服务端:MyService.java
public class MyService extends Service {
    // 实现 AIDL 接口
    private final IMyService.Stub binder = new IMyService.Stub() {
        @Override
        public int add(int a, int b) {
            return a + b; // 实际的服务逻辑
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return binder; // 返回“菜单”
    }
}

📌 2. 客户端(Client)做什么?

核心任务:请求服务

  • 代码位置:另一个 App 或同一 App 的不同进程(如主进程调用后台服务)。
  • 关键步骤:
    1. 拿到菜单:通过 bindService() 绑定服务端的 Service,获取 AIDL 接口的代理对象。
    2. 点菜:调用 AIDL 接口的方法(如 add(1, 2)),就像调用本地方法一样。
    3. 结账:用完服务后解绑,避免资源泄漏。

示例代码(客户端)

// 客户端:MainActivity.java
private IMyService myService;

// 绑定服务
private ServiceConnection connection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // 将 Binder 对象转换为 AIDL 接口(拿到菜单)
        myService = IMyService.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        myService = null;
    }
};

// 点击按钮调用远程方法
button.setOnClickListener(v -> {
    try {
        int result = myService.add(1, 2); // 点菜:调用服务端方法
        Log.d("Client", "结果:" + result);
    } catch (RemoteException e) {
        e.printStackTrace();
    }
});

// 绑定服务(进店点餐)
Intent intent = new Intent(this, MyService.class);
bindService(intent, connection, Context.BIND_AUTO_CREATE);

// 记得在 onDestroy() 中解绑!

📌 3. 一句话总结

  • 服务端:定义功能 + 实现逻辑(像饭店后厨)。
    关键文件:.aidl、Service 实现类、AndroidManifest.xml 声明。
  • 客户端:绑定服务 + 调用方法(像顾客点菜)。
    关键代码:bindService()、ServiceConnection、AIDL 接口调用。

📌 4. 常见疑问解答

Q1:服务端和客户端可以是同一个 App 吗?

可以! 比如:

  • 主进程(客户端)调用后台进程(服务端)的 Service。
  • 通过设置 android:process=":remote" 让 Service 运行在独立进程。

Q2:客户端如何知道服务端有哪些方法?

  • 服务端必须将 .aidl 文件 复制给客户端(保持包名和内容完全一致)。
  • 客户端通过 IMyService.Stub.asInterface(binder) 获取接口后,就能看到所有定义的方法。

Q3:如果服务端崩溃了怎么办?

  • 客户端会触发 onServiceDisconnected(),此时应重试绑定或提示用户。
  • 通过 try-catch 捕获 RemoteException 处理异常。

📌 5. 完整流程图示

服务端(饭店)                    客户端(顾客)
--------------                    --------------
1. 定义菜单(AIDL)
2. 做菜(实现 Service)
3. 开门营业(注册 Service)
                              4. 进店(bindService)
                              5. 点菜(调用 add(1,2))
6. 上菜(返回结果)
                              7. 吃完结账(unbindService)

通过这个类比,你应该能清晰区分 AIDL 中服务端和客户端的角色了!

使用Android接口定义语言(AIDL)进行跨进程通信(IPC),核心是让两个不同进程的应用通过一个预先定义好的接口进行方法调用。整个过程可以分为服务端和客户端两个角色,主要包含以下五个基本步骤:


步骤一:创建 .aidl 接口文件

这是通信的起点,你需要定义一个双方都认可的“通话协议”。

  1. 创建文件:在Android项目的 src/main/ 目录下新建一个 aidl 文件夹,然后在此文件夹中创建以 .aidl 为扩展名的文件(例如 IRemoteService.aidl)。
  2. 定义接口:在 .aidl 文件中,使用类似Java的语法定义可供客户端调用的方法。
    // IRemoteService.aidl
    package com.example.server;
    
    interface IRemoteService {
        int getPid();
        void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
                double aDouble, String aString);
    }
  3. 编译生成Java文件:在你构建项目(Make Project或Rebuild)后,Android Gradle插件会自动根据你的 .aidl 文件,在 build/generated/ 目录下生成一个同名的Java接口文件(如 IRemoteService.java)。这个文件包含了实现IPC通信所需的Stub类、Proxy代理类等核心代码。

注意:.aidl 文件支持的数据类型包括Java基本类型、String、CharSequence、List、Map以及其他AIDL接口或你自定义且实现了 Parcelable 接口的对象。

步骤二:服务端实现接口 (Service端)

服务端需要“接听电话”,并真正执行那些被定义好的方法。

  1. 创建Service:创建一个继承自 Service 的类。
  2. 实现接口方法:在Service内部,创建一个Binder对象。这个Binder对象需要继承上一步编译生成的 IRemoteService.Stub 抽象类,并实现你在 .aidl 中定义的所有方法(如 getPid() 和 basicTypes())。这些方法里写的才是真正的业务逻辑。
    // RemoteService.java
    public class RemoteService extends Service {
        @Override
        public void onCreate() { super.onCreate(); }
    
        @Override
        public IBinder onBind(Intent intent) {
            // 返回实现了AIDL接口的Binder对象
            return binder;
        }
    
        private final IRemoteService.Stub binder = new IRemoteService.Stub() {
            @Override
            public int getPid() throws RemoteException {
                return android.os.Process.myPid();
            }
            @Override
            public void basicTypes(...) throws RemoteException {
                // 实现具体逻辑
            }
        };
    }
  3. 在onBind()中返回:在Service的 onBind() 生命周期方法中,返回上面创建的Binder实例。当客户端绑定服务时,这个Binder对象会被传递给客户端。

步骤三:客户端绑定服务并获取接口 (Client端)

客户端需要“拨号”并建立连接,才能使用服务端的功能。

  1. 复制AIDL文件:为了让客户端也能“认识”这个接口,你需要将服务端定义的 .aidl 文件(注意是整个文件结构,包括包名路径)完整地复制到客户端的 src/main/aidl 目录下。
  2. 创建ServiceConnection:创建一个 ServiceConnection 对象,用于监听与远程服务连接的状态变化。
  3. 获取接口对象:在 onServiceConnected() 回调方法中,你会收到一个 IBinder 对象。这是系统传递过来的、代表远程服务的“电话线”。你需要通过 IRemoteService.Stub.asInterface(service) 将这个 IBinder 对象转换成你可以直接调用的AIDL接口对象。
    // 在Activity或Fragment中
    private IRemoteService remoteService;
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 将IBinder转换为AIDL接口
            remoteService = IRemoteService.Stub.asInterface(service);
            Log.d("Client", "服务连接成功");
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
            remoteService = null;
            Log.d("Client", "服务断开连接");
        }
    };

步骤四:绑定远程服务并调用方法

现在“电话”已经接通,可以开始通话了。

  1. 调用bindService():创建显式Intent(指定服务端应用的包名和Service类的完整路径),然后调用 bindService() 方法启动并绑定远程服务。
    Intent intent = new Intent();
    // 对于Android 5.0及以上版本,必须使用显式Intent
    intent.setComponent(new ComponentName("com.example.server", 
                                            "com.example.server.RemoteService"));
    bindService(intent, connection, Context.BIND_AUTO_CREATE);
  2. 调用远程方法:一旦 onServiceConnected 被触发,你得到了 remoteService 对象,就可以像调用本地方法一样调用远程服务中定义的方法了。
    // 当按钮被点击时
    public void onGetPidClick(View view) {
        if (remoteService != null) {
            try {
                int remotePid = remoteService.getPid();
                Toast.makeText(this, "远程服务PID: " + remotePid, Toast.LENGTH_SHORT).show();
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
    }
    需要注意的是,这些方法是在服务端的Binder线程池中执行的,可能耗时,所以需要注意线程管理,避免在UI线程直接调用可能耗时的操作。

步骤五:在AndroidManifest.xml中声明服务

这是让服务能被外部发现的最后一步。

  1. 声明Service:在服务端应用的 AndroidManifest.xml 文件中,声明你的 RemoteService。
  2. 配置Intent过滤器 (可选但推荐):为了让客户端更容易地找到服务,可以给Service配置一个 <intent-filter>,并指定一个唯一的action名称。同时,如果希望其他应用也能访问,需要将 android:exported 属性设置为 true。
    <service
        android:name=".RemoteService"
        android:enabled="true"
        android:exported="true">
        <intent-filter>
            <action android:name="com.example.server.REMOTE_SERVICE" />
        </intent-filter>
    </service>

总结

使用AIDL进行跨进程通信,就像两个人用对讲机通话:

  1. 制定通话规则(定义 .aidl 接口)。
  2. 一方准备好接收信息并执行动作(服务端实现 Stub 类)。
  3. 另一方获取对方的通话频道(客户端复制AIDL,绑定 Service,通过 Stub.asInterface 获取代理对象)。
  4. 开始通话(通过代理对象调用方法)。