美文网首页
Android中AIDL使用案例

Android中AIDL使用案例

作者: 狼性代码人 | 来源:发表于2020-12-22 21:48 被阅读0次
  • 创建辅助文件 [日志文件、进程文件]
  • 创建 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>
  • 使用上述启动服务示例代码运行后结果:

相关文章

  • AIDL

    Android中AIDL的基本用法Android 中AIDL的使用与理解Android AIDL使用详解彻底明白A...

  • Android中的AIDL

    Android中的AIDL使用详解 AIDL使用解析 Android进阶之AIDL的使用详解

  • Android中AIDL使用案例

    创建辅助文件 [日志文件、进程文件]创建 aidl 文件创建独立进程的远程服务 service启动远程服务 ser...

  • Android Aidl 的使用

    Android Aidl 的使用 Binder框架 -- android AIDL 的使用 Binder框架 – ...

  • Android AIDL 进程间通信使用笔记

    Android AIDL 进程间通信使用笔记 AIDL AIDL是Android Interface defini...

  • Android中AIDL的工作原理

    在上一篇文章中Android中AIDL的使用详解介绍了AIDL的使用流程,这篇文章我们说说AIDL的工作原理。 I...

  • Android-服务(AIDL通讯)

    本文学习目标 学会使用AIDL与远程服务进行通讯 AIDL介绍 AIDL是Android中IPC(Inter-Pr...

  • IPC相关

    Android权限之sharedUserId、签名(实例说明) Android 中AIDL的使用与理解 Binde...

  • AIDL解析

    AIDL是Android中IPC方式中的一种。 AIDL的使用 创建aidl后缀的文件,服务端和客户端各保存一份,...

  • Android AIDL

    AIDL原理是什么?如何优化多模块都使用AIDL的情况? AIDL(Android Interface Defin...

网友评论

      本文标题:Android中AIDL使用案例

      本文链接:https://www.haomeiwen.com/subject/gmfsnktx.html