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.player

import android.app.Service
import android.content.Intent
import android.os.IBinder
import android.os.Process
import android.util.Log
import android.widget.Toast

class PlayerService : Service() {
    val LOG_TAG = PlayerService::class.java.simpleName

    override 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.browser

import 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.IPlayer

class MainActivity : AppCompatActivity() {
    val LOG_TAG = MainActivity::class.java.simpleName
    lateinit var mBinding : ActivityMainBinding
    var mIPlayer: IPlayer? = null

    val 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>
        <variable
            name="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">

        <Button
            android: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.player

import android.os.Parcel
import android.os.Parcelable

class Command(key: String, params: HashMap<String, String>) : Parcelable {
    var key = key
    var 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 6000

D/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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值