美文网首页
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>
    
    • 使用上述启动服务示例代码运行后结果:

    相关文章

      网友评论

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

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