Android在kts中简单使用AIDL
AIDL相信做Android都有所了解,跨进程通信会经常使用,这里就不展开讲解原理跨进程通信的方式了,最近项目换成kts的方式,于是把aidl也换成了统一的方式,其中遇到了很多问题,这里记录一下,直接上代码.
1.在groovy创建aidl文件:
在Groovy中是可以直接创建aidl文件的
2.生成的aidl文件如下:
arduino
interface IMyService {
/**
* 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);
int add(int a, int b);
}
3.创建一个服务端Service:
MyService
java
package com.example.aidltestdemo;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
import androidx.annotation.Nullable;
/**
* @author: njb
* @date: 2025/3/20 17:47
* @desc: 描述
*/
public class MyService extends Service {
private final IMyService.Stub mBinder = new IMyService.Stub() {
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
}
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
4.注册服务:
ini
<service
android:name=".MyService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="com.example.aidltestdemo.IMyService" />
</intent-filter>
</service>
5.客户端代码:
typescript
package com.example.aidltestdemo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import com.google.android.material.snackbar.Snackbar;
public class MainActivity extends AppCompatActivity {
private IMyService mService;
private TextView textView;
private static final String TAG = "AIDlDemo";
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IMyService.Stub.asInterface(service);
try {
int result = mService.add(10086, 10010);
Log.d(TAG,"---获取到的数据为---: " + result);
Snackbar.make(textView,"获取到的数据为: " + result,Snackbar.LENGTH_SHORT).show();
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
mService = null;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initService() {
Intent intent = new Intent();
intent.setAction("com.example.aidltestdemo.IMyService");
intent.setPackage("com.example.aidltestdemo");
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
private void initView() {
textView = findViewById(R.id.textView);
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
initService();
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(mConnection);
}
}
6.布局文件:
ini
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="aidl简单测试"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
7.在kts中使用aidl:
如果是使用kts的方式默认创建aidl文件时提示没有配置创建不了,这就和之前有很大的区别。
8.在build.gradle添加配置:
再次创建aidl发现按钮是可以点击的,配置是有效果的。
9.新创建后的aidl文件:
9.1 服务端代码如下:
kotlin
package com.example.aidldemo.server
import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.os.RemoteException
import com.example.aidldemo.aidl.IMyTestServer
/**
* @author: njb
* @date: 2025/3/19 23:24
* @desc: 描述
*/
class MyTestService : Service() {
private val mBinder: IMyTestServer.Stub = object : IMyTestServer.Stub() {
@Throws(RemoteException::class)
override fun basicTypes(
anInt: Int,
aLong: Long,
aBoolean: Boolean,
aFloat: Float,
aDouble: Double,
aString: String
) {
}
@Throws(RemoteException::class)
override fun add(a: Int, b: Int): Int {
return a + b
}
}
override fun onBind(intent: Intent): IBinder? {
return mBinder
}
}
9.2 注册服务:
ini
<service android:name=".server.MyTestService"
android:process=":remote"
android:enabled="true"
android:exported="true">
9.3 客户端代码:
kotlin
package com.example.aidldemo.aidl
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.util.Log
import android.view.View
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.example.aidldemo.R
import com.example.aidldemo.server.MyTestService
import com.google.android.material.snackbar.Snackbar
class MainActivity : AppCompatActivity(){
private lateinit var mTvBind: TextView
private val TAG = MainActivity::class.java.name
private var testServer: IMyTestServer ?=null
private val mConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
testServer = IMyTestServer.Stub.asInterface(service)
try {
testServer?.let {
val result: Int = it.add(188, 288)
Log.d(TAG, "--结果为--: $result")
Snackbar.make(mTvBind,"--获取到的数据为--: $result",Snackbar.LENGTH_SHORT).show()
}
} catch (rme: RemoteException) {
rme.printStackTrace()
}
}
override fun onServiceDisconnected(name: ComponentName) {
try {
testServer = null
} catch (e: RemoteException) {
e.printStackTrace()
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mTvBind = findViewById(R.id.tv_bind)
mTvBind.setOnClickListener{
val intent = Intent(
this@MainActivity,
MyTestService::class.java
)
bindService(intent, mConnection, BIND_AUTO_CREATE)
}
}
override fun onDestroy() {
super.onDestroy()
if(testServer != null && testServer!!.asBinder().isBinderAlive){
testServer = null
}
unbindService(mConnection)
}
}
10.新建一个Book服务:
arduino
// ICarManager.aidl
package com.example.aidldemo.aidl;
import com.example.aidldemo.aidl.Book;
import com.example.aidldemo.aidl.IOnNewBookAddListener;
// Declare any non-default types here with import statements
interface IBookManager {
/**
* 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);
List<Book> getBookList();
void addNewBook(in Book book);
void registerListener(IOnNewBookAddListener listener);
void unregisterListener(IOnNewBookAddListener listener);
}
11.新增Book列表数据接口:
arduino
// IOnNewCarAddListener.aidl
package com.example.aidldemo.aidl;
import com.example.aidldemo.aidl.Book;
// Declare any non-default types here with import statements
interface IOnNewBookAddListener {
/**
* 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 onNewBookAdd(in Book newBook);
}
12.IBook接口:
arduino
interface IBook {
/**
* 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);
}
13.注册服务:
ini
<service android:name=".server.BookManagerService"
android:process=":remote"
android:enabled="true"
android:exported="true">
</service>
14.客户端Book测试代码:
kotlin
package com.example.aidldemo.aidl
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.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.AppCompatTextView
import com.example.aidldemo.R
import com.example.aidldemo.databinding.ActivityMainBinding
import com.example.aidldemo.server.BookManagerService
/**
* @author: njb
* @date: 2025/3/22 19:27
* @desc: 描述
*/
class BookTestActivity :AppCompatActivity(){
private val TAG: String = "TestActivity"
private var bookManager: IBookManager? = null
private lateinit var binding:ActivityMainBinding
private val mConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, service: IBinder) {
bookManager = IBookManager.Stub.asInterface(service)
try {
bookManager?.let {
val books = it.getBookList()
if (books != null && books.isNotEmpty()) {
Log.d(TAG, " -- onServiceConnected -- book:$books")
}
it.registerListener(clientListener)
}
} catch (e: RemoteException) {
e.printStackTrace()
}
}
override fun onServiceDisconnected(name: ComponentName) {
try {
bookManager?.unregisterListener(clientListener)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
}
private val clientListener: IOnNewBookAddListener = object : IOnNewBookAddListener.Stub() {
@Throws(RemoteException::class)
override fun basicTypes(
anInt: Int,
aLong: Long,
aBoolean: Boolean,
aFloat: Float,
aDouble: Double,
aString: String
) {
}
@Throws(RemoteException::class)
override fun onNewBookAdd(newBook: Book) {
Log.d(TAG, " -- onNewBookAdd -- newBook:$newBook")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tvBind.setOnClickListener{
val intent = Intent(this@BookTestActivity, BookManagerService::class.java)
bindService(intent, mConnection, BIND_AUTO_CREATE)
}
binding.tvGet.setOnClickListener{
try {
val books = bookManager!!.bookList
if (books != null && books.isNotEmpty()) {
Log.d(TAG, " -- onClick -- books:$books")
}
} catch (e: Exception) {
e.printStackTrace()
}
}
binding.tvAdd.setOnClickListener{
try {
bookManager?.let {
it.addNewBook(Book(1003, "计算机组成原理"))
it.addNewBook(Book(1004, "操作系统"))
}
} catch (e: RemoteException) {
e.printStackTrace()
}
}
}
override fun onDestroy() {
super.onDestroy()
if (bookManager != null && bookManager!!.asBinder().isBinderAlive) {
try {
bookManager?.unregisterListener(clientListener)
} catch (e: RemoteException) {
e.printStackTrace()
}
}
unbindService(mConnection)
}
}
15.普通实体类序列化:
typescript
package com.example.aidldemo.aidl;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.NonNull;
/**
* @author: njb
* @date: 2025/3/19 23:22
* @desc: 描述
*/
public class Book implements Parcelable {
private int id;
private String name;
public Book(int id, String name){
this.id = id;
this.name = name;
}
protected Book(Parcel in) {
id = in.readInt();
name = in.readString();
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public static final Creator<Book> CREATOR = new Creator<Book>() {
@Override
public Book createFromParcel(Parcel in) {
return new Book(in);
}
@Override
public Book[] newArray(int size) {
return new Book[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(id);
dest.writeString(name);
}
@NonNull
@Override
public String toString() {
return String.format("[id:%d, name:%s]",id, name);
}
}
16.使用Parcelize序列化:
16.1 在app的build.gradle目录添加插件配置:
scss
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
id ("kotlin-parcelize")
}
16.2 实体类使用Parcelize方式:
和普通的方式对比发现代码简洁了很多,而且使用很方便,大大减少了开发人员的工作,当然我们要搞懂其原理才能达到事半功倍的效果,要不然只是会使用,而不知道为啥这么使用及使用他的优势和原理,对自身成长有限.
kotlin
@Parcelize
data class Book(
val id: Int,
val name:String
):Parcelable
16.3 在libs.versions.toml添加如下插件:
ini
jetbrains-kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
16.4 在项目build.gradle添加配置:
bash
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
alias(libs.plugins.jetbrains.kotlin.parcelize) apply false
}
16.5 在app的build.gradle配置:
scss
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.jetbrains.kotlin.parcelize)
}
17.Parcelable的优势如下:
- 高效性能:相比Serializable,Parcelable在序列化和反序列化过程中更加高效,因为它避免了反射的开销。
- 跨进程传输:Parcelable适用于在不同进程之间传输数据,例如在Android中,我们可以将Parcelable对象传递给另一个应用程序组件。
- 灵活性:Parcelable允许我们选择性地序列化对象的某些字段,而不是整个对象,这在某些情况下可以提高性能和减少传输的数据量。
18.遇到问题如下:
18.1 项目编译失败:
创建的Book服务和Book实例不在一个目录导致编译失败
18.2 把两个类放同一目录编译成功:
18.3 添加Parcelize插件依赖报错:
18.4 在build.gradle.kts添加以下配置:
scss
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
id ("kotlin-parcelize")
}
18.5 使用Version Catalog方式:
ini
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
jetbrains-kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
bash
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.jetbrains.kotlin.android) apply false
alias(libs.plugins.jetbrains.kotlin.parcelize) apply false
}
scss
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.jetbrains.kotlin.parcelize)
}
19.实现的效果如下:
20.日志打印如下:
yaml
o D Installing profile for com.example.aidldemo
2025-03-23 20:26:39.912 4722-4722 TestActivity com.example.aidldemo D -- onServiceConnected -- book:[Book(id=1001, name=Java入门到精通)]
---------------------------- PROCESS STARTED (4797) for package com.example.aidldemo ----------------------------
2025-03-23 20:26:42.911 4722-4741 TestActivity com.example.aidldemo D -- onNewBookAdd -- newBook:Book(id=1002, name=C++程序设计)
2025-03-23 20:27:24.033 4722-4722 TestActivity com.example.aidldemo D -- onClick -- books:[Book(id=1001, name=Java入门到精通), Book(id=1002, name=C++程序设计)]
2025-03-23 20:27:39.776 4722-4722 TestActivity com.example.aidldemo D -- onNewBookAdd -- newBook:Book(id=1003, name=计算机组成原理)
2025-03-23 20:27:39.777 4722-4722 TestActivity com.example.aidldemo D -- onNewBookAdd -- newBook:Book(id=1004, name=操作系统)
21.总结:
- 使用kts的方式需要先使用buildFeatures配置aidl.
- 使用Parcelize序列化数据很简单方便.
- 要注意创建aidl文件的目录和包名这些在同一目录.
- 使用Version Catalog方式要主要依赖配置的方式.
- 感兴趣的小伙伴可以自己尝试一下,在后面的文章中会讲解aidl中如何传输大文件.