Android IPC_AIDL

文章目录

    • 简介
    • 简单使用
    • 传输自定义类

简介

AIDL(Android Interface definition language)是Android中IPC(Inter-Process Communication)方式中的一种,在 Android 中,一个进程通常无法访问另一个进程的内存。因此,为进行通信,进程需将其对象分解成可供操作系统理解的原语,并将其编组为可供您操作的对象。编写执行该编组操作的代码较为繁琐,Android提供了AIDL来方便开发者进行进程间通信。

简单使用

  • 准备两个项目
    这边是Player(服务端)创建服务提供功能给Browser(客户端)使用
    在这里插入图片描述
  • 服务端:新建aidl文件(注意包名路径)
    aidl文件定义了服务端提供了哪些功能给客户端
    在这里插入图片描述
    在这里插入图片描述
    建好的aidl文件长这样
package com.dean.player;interface IPlayer {//aidl传输数据支持类型有:八大基本类型、String、CharSequence、List、Map、实现了Parcelable接口的自定义类型void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);
}
  • 服务端:点击build下面的Make Project
    点击后gradle会根据aidl文件自动生成IPlayer.java 类,这里面有个内部Stub内部类
    在这里插入图片描述
  • 服务端:编写可绑定服务,binder对象需要实现自动生成的IPlayer里面的内部类Stub
package com.dean.playerimport android.app.Service
import android.content.Intent
import android.os.IBinder
import android.os.Process
import android.util.Log
import android.widget.Toastclass PlayerService : Service() {val LOG_TAG = PlayerService::class.java.simpleNameoverride fun onBind(intent: Intent?): IBinder? {//将binder提供出去return binder}//继承自Stub,实现里面所有方法来处理客户端请求private val binder = object : IPlayer.Stub() {override fun basicTypes(anInt: Int,aLong: Long,aBoolean: Boolean,aFloat: Float,aDouble: Double,aString: String?) {Log.d(LOG_TAG, "Pid ${Process.myPid()} receive browser client Data $anInt, $aLong, $aBoolean, $aFloat, $aDouble, $aString")}}
}
  • 服务端:编写服务别忘了在AndroidManifest中添加
<service android:name=".PlayerService"><intent-filter><action android:name="android.intent.action.PlayerService" /></intent-filter>
</service>

自此服务端就完成了,下面来写客户端

  • 客户端:将服务端的aidl拷贝过来(包括路径),也要点击build的make project,这时就表示可以使用服务端提供的功能了
  • 客户端:和绑定服务一样正常使用服务。
package com.dean.browserimport android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.ServiceConnection
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.IBinder
import android.os.Process
import android.util.Log
import android.view.View
import androidx.databinding.DataBindingUtil
import com.dean.browser.databinding.ActivityMainBinding
import com.dean.player.IPlayerclass MainActivity : AppCompatActivity() {val LOG_TAG = MainActivity::class.java.simpleNamelateinit var mBinding : ActivityMainBindingvar mIPlayer: IPlayer? = nullval mIPlayerCon = object : ServiceConnection {override fun onServiceConnected(name: ComponentName?, service: IBinder?) {mIPlayer = IPlayer.Stub.asInterface(service)}override fun onServiceDisconnected(name: ComponentName?) {mIPlayer = null}}override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)mBinding.handleEvent = HandleEvent()val intent = Intent("android.intent.action.PlayerService")intent.`package` = "com.dean.player"	//服务端项目包名bindService(intent, mIPlayerCon, Context.BIND_AUTO_CREATE)}inner class HandleEvent{/*** 向播放器发送播放指令*/fun sendPlayerCommand(view: View?){Log.d(LOG_TAG, "Pid ${Process.myPid()} Browser start send command")mIPlayer?.basicTypes(1, 2L, true, 3.0F, 5.0, "aa")}}
}<?xml version="1.0" encoding="utf-8"?>
<layout><data><variablename="handleEvent"type="com.dean.browser.MainActivity.HandleEvent" /></data><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:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发送播放指令"android:onClick="@{handleEvent::sendPlayerCommand}"app:layout_constraintLeft_toLeftOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintRight_toRightOf="parent"/></androidx.constraintlayout.widget.ConstraintLayout>
</layout>
  • 运行:需要先打开服务端项目,再打开客户端,点击按钮
客户端日志:
MainActivity: Pid 1367 Browser start send command服务端日志:
Pid 830 receive browser client Data 1, 2, true, 3.0, 5.0, aa

传输自定义类

  • 服务端:自定义类,并且实现Parcelable接口
package com.dean.playerimport android.os.Parcel
import android.os.Parcelableclass Command(key: String, params: HashMap<String, String>) : Parcelable {var key = keyvar params = params//String类是由最上层的BootStrap ClassLoader来加载的,为了安全上层是无法获取这个ClassLoader的,这边的classloader直接传了HashMap的,因为双亲委托最终还是到BootStrap ClassLoader来找String类。constructor(parcel: Parcel) : this(parcel.readString() ?: "", parcel.readHashMap(HashMap::class.java.classLoader) as HashMap<String, String>)companion object CREATOR : Parcelable.Creator<Command> {/*** 根据传来parcel构建出Command实例*/override fun createFromParcel(parcel: Parcel): Command {return Command(parcel)}override fun newArray(size: Int): Array<Command?> {return arrayOfNulls(size)}}/*** dest用于写入参数* flags有两种值:   1   当前对象需要作为返回值返回,不可立即释放该对象*                 0   其余情况,一般传0*/override fun writeToParcel(dest: Parcel?, flags: Int) {//parcel操作共享内存,在两个进程间传输数据,这边的write顺序需要和上面的read顺序一样dest?.writeString(key)dest?.writeMap(params as Map<*, *>?)}/*** 描述当前 Parcelable 实例的对象类型* 如果对象中有文件描述符,返回Parcelable.CONTENTS_FILE_DESCRIPTOR* 其他情况会返回一个位掩码,正常返回0即可*/override fun describeContents(): Int {return 0}
}
  • 服务端:为自定义类新建一个aidl:Command.aidl,这边和之前的aidl文件放在一起了
package com.dean.player;
parcelable Command;
  • 服务端:在之前的对外暴露的接口增加自定义类相关功能
package com.dean.player;
//需要手动引入自定义类
import com.mh.kotlins.Command;
interface IPlayer {long getPlayerPosition(int playerID);void sendCommand(in Command command);
}
  • 客户端:将相同的Command类和aidl文件拷贝过来(注意包名),make project,然后就可以使用服务端提供的功能了
inner class HandleEvent{/*** 向播放器发送播放指令*/fun sendPlayerCommand(view: View?){Log.d(LOG_TAG, "Pid ${Process.myPid()} Browser start send command")val params = HashMap<String, String>()params["a"] = "a"params["b"] = "b"params["c"] = "c"params["d"] = "d"val command = Command("open", params)mIPlayer?.sendCommand(command)}/*** 获取当前播放位置*/fun getPlayerPosition(view: View?){Log.d(LOG_TAG, "Pid ${Process.myPid()} Browser start get player position")Log.d(LOG_TAG, "player ${playerID} current position is ${mIPlayer?.getPlayerPosition(playerID)}")playerID = playerID.inc()}}
  • 运行结果
服务端日志打印:
D/PlayerService: 1 player return position 1000
D/PlayerService: 2 player return position 2000
D/PlayerService: 3 player return position 3000
D/PlayerService: 4 player return position 4000
D/PlayerService: 5 player return position 5000
D/PlayerService: 6 player return position 6000D/PlayerService: handle command key is open
D/PlayerService: command params key is a value is a
D/PlayerService: command params key is b value is b
D/PlayerService: command params key is c value is c
D/PlayerService: command params key is d value is d

注:

  1. 如果是在一个项目中的并且是单线程(不需要高并发)的可以用Messenger,Messenger相对简单
  2. aidl支持RPC,如果使用了aidl就要处理好多线程,默认情况下RPC 调用是同步调用,所以不能在Activity 的主线程调用该服务端功能,否则一旦该功能是耗时的就会引起ANR
  3. AIDL文件可以分为两类。一类用来声明实现了Parcelable接口的数据类型,还有一类是用来定义接口方法,告诉客户端提供了哪些功能
  4. 定向Tag:分为in、out、inout三种,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。基本数据类型、String、CharSequence或者其他AIDL文件定义的方法接口的定向TAG默认并且必须是in,所以之前都没加,其他类型在使用时必须加上,不然没法传值,比如:
interface Player {//这边用in,是客户端传给服务端的参数void addParams(in Map data);
}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部