Android在kts中怎么使用AIDL

前言

在 Android 中使用 Kotlin Script (KTS) 配置 AIDL,核心逻辑和 Java 环境一致(AIDL 本质是跨进程通信的接口定义),只是 build.gradle 配置从 Groovy 改成 KTS 语法,且 Kotlin 代码调用 AIDL 时需注意少量语法差异。以下是 完整步骤(含配置、编写 AIDL、跨进程调用),用大白话 + 实操代码说明:

一、前置知识

  • AIDL 支持的数据类型:基本类型(int/long/boolean 等)、String、Parcelable(自定义对象)、List/Map(元素需是支持类型)。
  • 跨进程场景:比如 App 内两个进程通信,或两个独立 App 通信(需暴露相同 AIDL 接口)。
  • 本文以 App 内两个进程通信 为例(最常用场景),KTS 配置适配 Android Gradle Plugin 7.0+。

二、步骤 1:创建 AIDL 文件(定义跨进程接口)

AIDL 文件需放在 src/main/aidl/ 目录下(和 Java/Kotlin 代码目录同级),目录结构要和包名一致。

1.1 新建 AIDL 目录结构

在 Android Studio 中:

  • 右键 app/src/main → New → Folder → AIDL Folder → 直接点击 Finish(自动生成 aidl 目录)。
  • aidl 目录下,新建和你 Kotlin 代码一致的 包名目录 (比如你的代码包是 com.example.aidldemo,就新建 aidl/com/example/aidldemo/)。
1.2 编写 AIDL 接口文件

在上述包名下,右键 → New → AIDL → AIDL File,命名为 IMyAidlInterface.aidl,内容如下(定义两个跨进程方法:传参、返回值):

aidl

复制代码
// IMyAidlInterface.aidl
package com.example.aidldemo; // 必须和包名一致

// 定义跨进程接口
interface IMyAidlInterface {
    // 1. 无参数,返回字符串
    String getServiceInfo();
    
    // 2. 带参数(基本类型),返回计算结果
    int add(int a, int b);
    
    // 3. 若要传自定义对象,需额外创建 Parcelable 类型的 AIDL(下文补充)
}
1.3 (可选)传自定义对象(Parcelable)

如果需要跨进程传递自定义对象(比如 User),需做两步:

  1. 新建 User.aidl(声明 Parcelable 类型):

    aidl

    复制代码
    // User.aidl
    package com.example.aidldemo;
    parcelable User; // 关键字 parcelable(注意不是 Java 的 Parcelable)
  2. 新建 User.kt(实现 Parcelable 接口):

    kotlin

    复制代码
    package com.example.aidldemo
    
    import android.os.Parcelable
    import kotlinx.parcelize.Parcelize
    
    // 用 Kotlin 插件 @Parcelize 自动实现 Parcelable(需依赖 kotlin-parcelize)
    @Parcelize
    data class User(val name: String, val age: Int) : Parcelable

三、步骤 2:KTS 配置(build.gradle.kts)

需在 app/build.gradle.kts 中配置 AIDL 相关依赖和编译规则,核心是确保 AIDL 文件被正确编译成 Kotlin 可调用的代码。

完整 KTS 配置(关键部分标红)

kotlin

复制代码
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("kotlin-parcelize") // 可选:用于 @Parcelize 自动实现 Parcelable
}

android {
    namespace = "com.example.aidldemo"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.aidldemo"
        minSdk = 21
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }

    // 1. 配置 AIDL 编译(KTS 语法,替代 Groovy 的 aidl {})
    buildFeatures {
        aidl = true // 启用 AIDL 编译(AGP 7.0+ 需显式开启)
    }

    // 2. 若 AIDL 引用了其他模块的 AIDL 文件,需添加依赖(本文单模块可省略)
    // dependencies {
    //     aidl(project(":otherModule"))
    // }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

dependencies {
    // 基础依赖(不用改)
    implementation("androidx.core:core-ktx:1.12.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.11.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
配置说明
  • buildFeatures { aidl = true }:AGP 7.0+ 后必须显式开启 AIDL 编译,否则 AIDL 文件不会生成对应代码。
  • kotlin-parcelize 插件:简化自定义对象的 Parcelable 实现,不用手动写 writeToParcelcreateFromParcel

四、步骤 3:编译 AIDL,生成对应 Kotlin 可调用代码

配置完成后,点击 Android Studio 顶部的 Sync Now(同步 Gradle),然后 Rebuild Project(重建项目):

  • 编译成功后,会在 app/build/generated/aidl_source_output_dir/debug/compileDebugAidl/out/com/example/aidldemo/ 目录下生成 IMyAidlInterface.kt 文件(自动生成,无需修改)。
  • 该文件包含 Stub(服务端实现)和 Proxy(客户端调用),是跨进程通信的核心。

五、步骤 4:实现服务端(Service)

服务端是提供跨进程接口的一方,需创建一个 Service,并在其中实现 AIDL 接口。

4.1 新建 Service 类(MyAidlService.kt

kotlin

复制代码
package com.example.aidldemo

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.util.Log

class MyAidlService : Service() {
    // 1. 实现 AIDL 接口(Stub 是自动生成的内部类)
    private val aidlInterface = object : IMyAidlInterface.Stub() {
        // 实现 AIDL 中定义的方法
        override fun getServiceInfo(): String {
            Log.d("AIDL", "服务端:收到客户端请求,返回服务信息")
            return "我是 AIDL 服务端,当前进程:${android.os.Process.myPid()}"
        }

        override fun add(a: Int, b: Int): Int {
            Log.d("AIDL", "服务端:收到加法请求,a=$a, b=$b")
            return a + b
        }
    }

    // 2. 绑定服务时,返回 AIDL 接口实例(客户端通过这个实例调用方法)
    override fun onBind(intent: Intent): IBinder {
        return aidlInterface
    }
}
4.2 在 AndroidManifest.xml 中注册 Service(关键:声明跨进程)

必须给 Service 配置 android:process 属性,指定独立进程(否则就是同一进程,无需 AIDL):

xml

复制代码
<manifest ...>
    <application ...>
        <!-- 注册 AIDL 服务 -->
        <service
            android:name=".MyAidlService"
            android:enabled="true"
            android:exported="true" <!-- 允许外部绑定(同 App 内也需开启) -->
            android:process=":remote"> <!-- 独立进程名(:开头表示私有进程) -->
            <!-- 隐式启动的 Action(客户端绑定用) -->
            <intent-filter>
                <action android:name="com.example.aidldemo.AIDL_SERVICE" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </service>
    </application>
</manifest>

六、步骤 5:实现客户端(调用跨进程接口)

客户端是调用服务端接口的一方(比如 Activity),需通过 bindService 绑定服务端,获取 AIDL 接口实例后调用方法。

5.1 客户端 Activity(MainActivity.kt

kotlin

复制代码
package com.example.aidldemo

import android.content.ComponentName
import android.content.Intent
import android.content.ServiceConnection
import android.os.Bundle
import android.os.IBinder
import android.util.Log
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    private lateinit var tvResult: TextView
    private lateinit var btnCall: Button
    private lateinit var btnAdd: Button

    // AIDL 接口实例(客户端通过它调用服务端方法)
    private var aidlInterface: IMyAidlInterface? = null

    // 服务绑定连接对象
    private val serviceConnection = object : ServiceConnection {
        // 绑定成功时回调(获取 AIDL 接口实例)
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            Log.d("AIDL", "客户端:绑定服务成功")
            // 将 IBinder 转换为 AIDL 接口实例(自动生成的 asInterface 方法)
            aidlInterface = IMyAidlInterface.Stub.asInterface(service)
        }

        // 服务断开时回调
        override fun onServiceDisconnected(name: ComponentName?) {
            Log.d("AIDL", "客户端:服务断开连接")
            aidlInterface = null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        tvResult = findViewById(R.id.tv_result)
        btnCall = findViewById(R.id.btn_call)
        btnAdd = findViewById(R.id.btn_add)

        // 1. 绑定服务端(启动服务并绑定)
        val intent = Intent()
        intent.action = "com.example.aidldemo.AIDL_SERVICE" // 和 Manifest 中 Action 一致
        intent.setPackage(packageName) // 包名(避免隐式意图安全问题)
        bindService(intent, serviceConnection, BIND_AUTO_CREATE)

        // 2. 调用 AIDL 接口的 getServiceInfo 方法
        btnCall.setOnClickListener {
            aidlInterface?.let {
                try {
                    val result = it.getServiceInfo() // 跨进程调用(同步)
                    tvResult.text = "调用结果:\n$result\n客户端进程:${android.os.Process.myPid()}"
                } catch (e: Exception) {
                    tvResult.text = "调用失败:${e.message}"
                }
            } ?: tvResult.text = "服务未绑定"
        }

        // 3. 调用 AIDL 接口的 add 方法
        btnAdd.setOnClickListener {
            aidlInterface?.let {
                try {
                    val sum = it.add(10, 20) // 跨进程传参
                    tvResult.text = "10 + 20 = $sum"
                } catch (e: Exception) {
                    tvResult.text = "加法失败:${e.message}"
                }
            } ?: tvResult.text = "服务未绑定"
        }
    }

    // 4. 页面销毁时解绑服务(避免内存泄漏)
    override fun onDestroy() {
        super.onDestroy()
        unbindService(serviceConnection)
    }
}
5.2 客户端布局(activity_main.xml

简单布局,用于触发调用和显示结果:

xml

复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="20dp">

    <Button
        android:id="@+id/btn_call"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="调用服务端接口"
        android:layout_marginBottom="20dp"/>

    <Button
        android:id="@+id/btn_add"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="计算 10+20"
        android:layout_marginBottom="20dp"/>

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="18sp"/>

</LinearLayout>

七、运行测试

  1. 启动 App,点击按钮:
    • 日志会显示客户端和服务端的进程 ID(不同 PID,说明跨进程成功)。
    • 文本框会显示服务端返回的结果,加法会返回 30。
  2. 关键验证:
    • 客户端进程 ID ≠ 服务端进程 ID(通过 Process.myPid() 查看)。
    • 调用 getServiceInfo 能拿到服务端的进程信息,说明跨进程通信成功。

八、关键注意事项

  1. 线程问题 :AIDL 方法默认在服务端的 Binder 线程池 中执行,不是主线程!如果服务端方法有耗时操作(比如网络请求、数据库查询),直接写会阻塞 Binder 线程,需在服务端开启子线程处理。
  2. 异常处理 :跨进程调用可能出现断开连接、超时等异常,必须用 try-catch 包裹(否则会崩溃)。
  3. 自定义对象 :如果传 Parcelable 对象,AIDL 文件和 Kotlin 实体类的 包名必须完全一致 ,否则会报 ClassNotFoundException
  4. 权限控制:如果是两个独立 App 通信,服务端需在 Manifest 中配置权限,客户端需申请权限(避免恶意绑定)。

总结

KTS 中使用 AIDL 的核心是 "AIDL 定义接口 + KTS 配置编译 + 服务端实现 Stub + 客户端绑定调用" ,和 Java 环境的区别仅在于 Gradle 配置语法和 Kotlin 代码的语法细节(比如 object : IMyAidlInterface.Stub() 替代 Java 的匿名内部类)。

相关推荐
用户69371750013841 小时前
6.Kotlin 流程控制:循环控制:while 与 do/while
android·后端·kotlin
老华带你飞2 小时前
个人健康系统|健康管理|基于java+Android+微信小程序的个人健康系统设计与实现(源码+数据库+文档)
android·java·vue.js·微信小程序·论文·毕设·个人健康系统
2501_915909063 小时前
iOS App 测试工具全景指南,构建从开发、性能到系统级调试的多工具协同测试体系
android·测试工具·ios·小程序·uni-app·iphone·webview
钮钴禄·爱因斯晨3 小时前
Python常见的文件操作
android·数据库·python
用户2018792831673 小时前
希腊字母"Έ"显示不全的奇妙冒险
android
Entropless3 小时前
Kotlin 可以预判你的预判?Kotlin 高级特性 之 Contracts
android·kotlin
雨白4 小时前
深入理解 Flow 的终端操作符
android
杜子不疼.5 小时前
【C++】深入解析AVL树:平衡搜索树的核心概念与实现
android·c++·算法
介一安全6 小时前
【Frida Android】实战篇7:SSL Pinning 证书绑定绕过 Hook 教程阶段总结
android·网络安全·逆向·安全性测试·frida