Android中AIDL使用案例

2020-12-22  本文已影响0人  狼性代码人
  • 创建辅助文件 [日志文件、进程文件]
  • 创建 aidl 文件
  • 创建独立进程的远程服务 service
  • 启动远程服务 service
  • 在 aidl 中使用继承 Parcelable 接口的数据

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

一、创建辅助文件

// 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)
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生成的java文件所在的位置

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

// 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")
    }
}
<?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 中。
<?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 接口的数据

// 数据类代码示例
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)
        }
    }
}
// Person.aidl 文件内容示例
package com.remote.service.bean;

parcelable 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是两者均可设置

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()
            )
        }
    }
   .........
}
// 示例代码
class MainActivity : AppCompatActivity() {
    ...........
    fun doServiceSetMaster(v: View?) {
        v ?: return
        binder?.setMaster(Person("封雪彦", 43))
    }
}
场景一、service 和 client 在同一个 project 中。
场景二、service 和 client 分属于不同的 project 中。
<?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>
上一篇 下一篇

猜你喜欢

热点阅读