Android中AIDL使用案例

  • 创建辅助文件 [日志文件、进程文件]
  • 创建 aidl 文件
  • 创建独立进程的远程服务 service
  • 启动远程服务 service
  • 在 aidl 中使用继承 Parcelable 接口的数据

图文详解 Android Binder跨进程通信的原理

一、创建辅助文件

  • 创建辅助文件 Logger,用于输出日志
// Logger.kt
package com.remote.service.util

import android.util.Log

const val DEFAULT_TAG = "JshRemoteService"

private fun log(type: Int, tag: String, vararg args: Any) =
    Log.println(type, tag, args.joinToString(";"))

fun logA(vararg args: Any) = logTA(DEFAULT_TAG, *args)
fun logTA(tag: String, vararg args: Any) = log(Log.ASSERT, tag, *args)

fun logE(vararg args: Any) = logTE(DEFAULT_TAG, *args)
fun logTE(tag: String, vararg args: Any) = log(Log.ERROR, tag, *args)

fun logD(vararg args: Any) = logTD(DEFAULT_TAG, *args)
fun logTD(tag: String, vararg args: Any) = log(Log.DEBUG, tag, *args)
  • 创建辅助文件 ProcessUtil ,用于获取进程信息
import android.app.ActivityManager;
import android.app.Application;
import android.content.Context;
import android.os.Build;
import android.text.TextUtils;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.lang.reflect.Method;
import java.util.List;

public class ProcessUtil {
    /**
     * @return 当前进程名
     */
    @Nullable
    public static String getCurrentProcessName(@NonNull Context context) {
        //1)通过Application的API获取当前进程名
        String currentProcessName = getCurrentProcessNameByApplication();
        if (!TextUtils.isEmpty(currentProcessName)) {
            return currentProcessName;
        }

        //2)通过反射ActivityThread获取当前进程名
        currentProcessName = getCurrentProcessNameByActivityThread();
        if (!TextUtils.isEmpty(currentProcessName)) {
            return currentProcessName;
        }

        //3)通过ActivityManager获取当前进程名
        currentProcessName = getCurrentProcessNameByActivityManager(context);

        return currentProcessName;
    }


    /**
     * 通过Application新的API获取进程名,无需反射,无需IPC,效率最高。
     */
    public static String getCurrentProcessNameByApplication() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            return Application.getProcessName();
        }
        return null;
    }

    /**
     * 通过反射ActivityThread获取进程名,避免了ipc
     */
    public static String getCurrentProcessNameByActivityThread() {
        String processName = null;
        try {
            final Method declaredMethod = Class.forName("android.app.ActivityThread", false, Application.class.getClassLoader())
                    .getDeclaredMethod("currentProcessName", (Class<?>[]) new Class[0]);
            declaredMethod.setAccessible(true);
            final Object invoke = declaredMethod.invoke(null, new Object[0]);
            if (invoke instanceof String) {
                processName = (String) invoke;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return processName;
    }

    /**
     * 通过ActivityManager 获取进程名,需要IPC通信
     */
    public static String getCurrentProcessNameByActivityManager(@NonNull Context context) {
        if (context == null) {
            return null;
        }
        int pid = android.os.Process.myPid();
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        if (am != null) {
            List<ActivityManager.RunningAppProcessInfo> runningAppList = am.getRunningAppProcesses();
            if (runningAppList != null) {
                for (ActivityManager.RunningAppProcessInfo processInfo : runningAppList) {
                    if (processInfo.pid == pid) {
                        return processInfo.processName;
                    }
                }
            }
        }
        return null;
    }
}

二、创建 aidl 文件

1、创建 aidl 文件目录:src -> main -> aidl -> [packageName] ->[AidlFileName].aidl
android studio 快速创建

创建AIDL文件 IAidlService.aidl 目录结构
2、在 aidl 文件中编写 service 提供给 client 的可调用方法。

  示例代码,编写完 aidl 文件需要make module '[aidl所在模块]',来生成aidl文件对应的Java文件。
\color{#FF0000}{注意:aidl文件中不要有中文(包括注释),有可能导致无法正确生成aidl对应的java文件}

// IAidlService.aidl
package com.remote.service;

// Declare any non-default types here with import statements

interface IAidlService {
    /**
     * 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, char aChar, String aString);
}
// AIDL中支持以下的数据类型
// 1. 基本数据类型
// 2. String 和CharSequence
// 3. List 和 Map ,List和Map 对象的元素必须是AIDL支持的数据类型;
// 4. AIDL自动生成的接口(需要导入-import)
// 5. 实现android.os.Parcelable 接口的类(需要导入-import)

编写 aild 文件需要注意事项:

  • AIDL支持的基本数据类型 (int、long、boolean、float、double、char)、String和CharSequence,集合接口类型List和Map,这些不需要import 导入。
  • 在AIDL中使用其他AIDL接口类型,需要import,即使是在相同包结构下。
  • AIDL允许传递实现Parcelable接口的类,需要import。
  • 需要特别注意的是,对于非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置。
    AIDL只支持接口方法,不能公开static变量。
aidl生成的java文件所在的位置

三、创建独立进程的远程服务 service

  • 编写远程服务 RemoteService.kt
// RemoteService.kt
package com.remote.service.remote

import android.app.Service
import android.content.Intent
import android.os.IBinder
import com.remote.service.IAidlService
import com.remote.service.util.ProcessUtil
import com.remote.service.util.logA

class RemoteService : Service() {
    // 实现aidl中定义的方法
    private val binder = object : IAidlService.Stub() {
        override fun basicTypes(
            anInt: Int, aLong: Long, aBoolean: Boolean,
            aFloat: Float, aDouble: Double, aChar: Char, aString: String?
        ) {
            logA(
                """
                Remote Service -> 
                    【当前线程:${ProcessUtil.getCurrentProcessName(this@RemoteService)}】
                    【参数:$anInt, $aLong, $aBoolean, $aFloat, $aDouble, $aChar, ${aString ?: "null"}】
                """.trimIndent()
            )
        }
    }

    override fun onCreate() {
        super.onCreate()
        logA("Remote Service -> onCreate")
    }

    override fun onBind(intent: Intent?): IBinder? = binder

    override fun onUnbind(intent: Intent?): Boolean {
        logA("Remote Service -> onUnbind")
        return super.onUnbind(intent)
    }

    override fun onDestroy() {
        super.onDestroy()
        logA("Remote Service -> onDestroy")
    }
}
  • AndroidManifest.xml文件中注册 service
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.remote.service">

    <application>
        .......................

        <service
            android:name=".remote.RemoteService"
            // enabled 是否可以被系统实例化,
            // 默认为 true 因为父标签 也有 enable 属性,
            // 所以必须两个都为默认值 true 的情况下服务才会被激活,否则不会激活。
            android:enabled="true"
            // exported 是否支持其它应用调用当前组件。 
            // 默认值:如果包含有intent-filter 默认值为true;
            // 没有intent-filter默认值为false。
            android:exported="false" 
            android:process=":remote">
            <intent-filter>
                //该Service可以响应带有com.remote.service.IAidlService这个action的Intent。
                //此处Intent的action必须写成“服务器端包名.aidl文件名”
                <action android:name="com.remote.service.IAidlService" />
            </intent-filter>
        </service>
    </application>

</manifest>

注意:
1、action 中的 name 需写成 【服务器端包名.aidl文件名】
2、android:process=":remote",代表在应用程序里,当需要该service时,会自动创建新的进程。而如果是android:process="remote",没有【“:”分号】的,则创建全局进程,不同的应用程序共享该进程。
3、设置了 android:process 属性将组件运行到另一个进程,相当于另一个应用程序,所以在另一个线程中也将新建一个 Application 的实例。因此,每新建一个进程 Application 的 onCreate 都将被调用一次。 如果在 Application 的 onCreate 中有许多初始化工作并且需要根据进程来区分的,那就需要特别注意了。

四、启动远程服务 service

// 示例代码
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        logA("当前进程名字 ${ProcessUtil.getCurrentProcessName(this)}")
    }

    private val serviceConnection = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName?) = Unit
        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            flag = true
            binder = IAidlService.Stub.asInterface(service)
            logA("remote service -> 已经绑定服务")
        }
    }

    var flag: Boolean = false
    private var binder: IAidlService? = null

    fun bindRemoteService(v: View?) {
        v ?: return
        logA("当前进程名字 ${ProcessUtil.getCurrentProcessName(this)}")
        if (flag) return
        //通过Intent指定服务端的服务名称和所在包,与远程Service进行绑定
        //参数与服务器端的action要一致,即"服务器包名.aidl接口文件名"
        val intent = Intent("com.remote.service.IAidlService").also {
            //Android5.0后无法只通过隐式Intent绑定远程Service
            //需要通过setPackage()方法指定包名
            it.`package` = "com.remote.service"
        }
        bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
    }

    fun unbindRemoteService(v: View?) {
        v ?: return
        if (!flag) return
        unbindService(serviceConnection)
        flag = false
    }

    fun doServiceBasicTypes(v: View?) {
        v ?: return
        binder?.basicTypes(1, 2L, false, 1.0f, 2.0, 'a', "String")
    }

}
场景一、service 和 client 在同一个 project 中。
  • 使用上述代码运行后结果:

场景二、service 和 client 分属于不同的 project 中。
  • 将service项目中的 aidl 文件夹原封不动的拷贝到client项目中的对应位置
    service 和 client 中 aidl 位置对比
  • 对上面 serviceAndroidManifest.xml 文件进行修改,把 <service>标签中的 android:exported 属性设置为 true。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.remote.service">

    <application>
        .......................

        <service
            android:name=".remote.RemoteService"
            android:enabled="true"
            android:exported="true" 
            android:process=":remote">
            <intent-filter>
                <action android:name="com.remote.service.IAidlService" />
            </intent-filter>
        </service>
    </application>

</manifest>
  • 使用上述示例代码运行后结果:

五、在 aidl 中使用继承 Parcelable 接口的数据

  • 在 java -> [packageName] 下创建继承 Parcelable 的数据类(Person)
    数据类位置示例
// 数据类代码示例
package com.remote.service.bean

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

data class Person(val name: String, var age: Int) : Parcelable {
    constructor(parcel: Parcel) : this(
        parcel.readString() ?: "",
        parcel.readInt()
    )

    override fun writeToParcel(parcel: Parcel, flags: Int) {
        parcel.writeString(name)
        parcel.writeInt(age)
    }

    override fun describeContents(): Int {
        return 0
    }

    companion object CREATOR : Parcelable.Creator<Person> {
        override fun createFromParcel(parcel: Parcel): Person {
            return Person(parcel)
        }

        override fun newArray(size: Int): Array<Person?> {
            return arrayOfNulls(size)
        }
    }
}
  • 在 aidl 文件夹下对应位置(包名路径一致)添加一个 Person.aidl 文件
    aidl 中 Person.aidl 文件位置示例
// Person.aidl 文件内容示例
package com.remote.service.bean;

parcelable Person;
  • 修改 IAidlService.aidl 文件,增加对 Person 对象的调用方法
package com.remote.service;

// Declare any non-default types here with import statements
import com.remote.service.bean.Person;

interface IAidlService {
    /**
     * 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, char aChar, String aString);

    // Demonstrates Parcelable type that you can use as parameters and return values in AIDL.
    void setMaster(in Person person);
}

注意:
1、import 导入 Parcelable 类型数据的路径
2、需要有方向指示设置;包括in、out和inout,in表示由客户端设置,out表示由服务端设置,inout是两者均可设置

  • 由于 IAidlService.aidl 增加了新的方法,对应的 service 也要增加相应方法
class RemoteService : Service() {
    // 实现aidl中定义的方法
    private val binder = object : IAidlService.Stub() {
        ..........
        override fun setMaster(person: Person?) {
            logA(
                """
                Remote Service -> 
                    【当前线程:${ProcessUtil.getCurrentProcessName(this@RemoteService)}】
                    【参数:${person.toString()}】
                """.trimIndent()
            )
        }
    }
   .........
}
  • 启动服务示例代码,增加一个 setMaster 的调用方法
// 示例代码
class MainActivity : AppCompatActivity() {
    ...........
    fun doServiceSetMaster(v: View?) {
        v ?: return
        binder?.setMaster(Person("封雪彦", 43))
    }
}
场景一、service 和 client 在同一个 project 中。
  • 使用上述代码运行后结果:

场景二、service 和 client 分属于不同的 project 中。
  • 将service项目中的 aidl 文件夹原封不动的拷贝到client项目中的对应位置
    service 和 client 中 aidl 位置对比
  • 将service项目中的 Person.kt 文件夹原封不动(包名路径不变)的拷贝到client项目中的对应位置

    service 和 client 中 Person.kt 位置对比

  • 对上面 serviceAndroidManifest.xml 文件进行修改,把 <service>标签中的 android:exported 属性设置为 true。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.remote.service">

    <application>
        .......................

        <service
            android:name=".remote.RemoteService"
            android:enabled="true"
            android:exported="true" 
            android:process=":remote">
            <intent-filter>
                <action android:name="com.remote.service.IAidlService" />
            </intent-filter>
        </service>
    </application>

</manifest>
  • 使用上述启动服务示例代码运行后结果:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,039评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,426评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,417评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,868评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,892评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,692评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,416评论 3 419
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,326评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,782评论 1 316
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,957评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,102评论 1 350
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,790评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,442评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,996评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,113评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,332评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,044评论 2 355

推荐阅读更多精彩内容