一文了解 Android 5 到 16 期间跨进程通信(IPC) 的使用

跨进程通信方式

Android是在Linux内核基础之上运行,因此Linux中存在的IPC机制在Android中基本都能使用,如:

  1. 管道:在创建时分配一个page大小的内存,缓存区大小比较有限;
  2. 信号: 不适用于信息交换,更适用于进程中断控制,比如非法内存访问,杀死某个进程等;
  3. 信号量:常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  4. 共享内存:无须复制,共享缓冲区直接付附加到进程虚拟地址空间,速度快;但进程间的同步问题操作系统无法实现,必须各进程利用同步工具解决;
  5. 消息队列:信息复制两次,额外的CPU消耗;不合适频繁或信息量大的通信;
  6. 套接字:作为更通用的接口,传输效率低;

除了上面 Linux 的 IPC 机制外,Android 还提供了 Messenger、AIDL、ContentProvider、MemoryFile、SharedMemory 这几种 IPC 方式。其中 Messenger、AIDL、ContentProvider 内部都是由 Binder 实现,而 MemoryFile、SharedMemory 则是共享内存的方式实现的。下面分别介绍如何通过它们来实现跨进程通信。

Messenger

Messenger可以翻译为信使,顾名思义,通过它可以在不同进程中传递Message对象,在Message中放入我们需要传递的数据,就可以轻松地实现数据的进程间传递了。Messenger是一种轻量级的IPC方案,它的底层实现是AIDL。Messenger的使用如下:

  • 客户端
kotlin 复制代码
class MessengerActivity : AppCompatActivity() {

    companion object {
        private const val TAG = "Messenger"
    }

    // 用于与服务通信的Messenger对象,通过它向服务发送消息
    private var serviceMessenger: Messenger? = null

    // 客户端的Messenger对象,用于接收服务返回的消息
    // 内部关联了ClientHandler,处理从服务端收到的消息
    private val clientMessenger = Messenger(ClientHandler())

    // 客户端的消息处理器,继承自Handler
    private class ClientHandler : Handler(Looper.getMainLooper()) {
        // 处理接收到的消息
        override fun handleMessage(msg: Message) {
            // 根据消息的what字段判断消息类型
            when (msg.what) {
                // 处理服务端的回复消息(类型2)
                2 -> Log.d("Messenger", "客户端收到回复:${msg.data.getString("reply")}")
                // 处理服务端主动推送的消息(类型3)
                3 -> Log.d("Messenger", "服务端主动推送的消息")
                // 可以添加更多消息类型的处理
                else -> super.handleMessage(msg)
            }
        }
    }

    // 服务连接对象,用于监听与服务的连接状态
    private val connection = object : ServiceConnection {
        // 当服务连接成功时调用
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // 通过服务返回的IBinder创建Messenger,用于和服务通信
            serviceMessenger = Messenger(service)
            // 连接成功后发送测试消息
            sendMsg(-1)
            sendMsg(0)
        }

        // 当服务意外断开连接时调用(正常关闭不会触发)
        override fun onServiceDisconnected(className: ComponentName) {
            // 清空服务端Messenger,避免空引用
            serviceMessenger = null
        }
    }

    // 向服务端发送消息的方法
    // what参数用于标识消息类型
    private fun sendMsg(what: Int) {
        // 创建消息对象,指定消息类型what
        val msg = Message.obtain(null, what).apply {
            // 设置消息数据(Bundle)
            data = Bundle().apply {
                // 这里可以添加要发送的数据,键值对形式
                // putString("msg", StringUtils.getOneMStringContent()) // 注释掉的是可能的大字符串测试
                putString("msg", "客户端数据") // 实际发送的测试数据
            }
            // 设置回复的Messenger,服务端通过这个回复客户端
            replyTo = clientMessenger
        }
        try {
            // 通过服务端的Messenger发送消息
            serviceMessenger?.send(msg)
        } catch (e: RemoteException) {
            // 捕获远程调用异常(如服务已断开)
            e.printStackTrace()
        }
    }

    // 活动创建时调用
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // 设置布局文件
        setContentView(R.layout.activity_messenger)

        // 绑定服务:创建意图,指定要绑定的服务
        Intent(this, MessengerService::class.java).also { intent ->
            // 绑定服务,参数:意图、连接对象、绑定选项(自动创建服务)
            bindService(intent, connection, BIND_AUTO_CREATE)
        }

        // 给按钮添加点击事件
        findViewById<Button>(R.id.add_user).setOnClickListener {
            // 点击时发送类型为1的消息
            sendMsg(1)
        }
    }

    // 活动销毁时调用
    override fun onDestroy() {
        super.onDestroy()
        // 解除与服务的绑定,避免内存泄漏
        unbindService(connection)
    }
}
  • 服务端
kotlin 复制代码
class MessengerService : Service() {

    companion object {
        private const val TAG = "Messenger"
    }

    // 用于保存客户端的Messenger引用,以便服务端主动向客户端发送消息
    private var clientMessenger: Messenger? = null

    // 服务端的消息处理器,用于处理客户端发送的消息
    private val handler = object : Handler(Looper.getMainLooper()) {
        // 处理接收到的消息
        override fun handleMessage(msg: Message) {
            // 根据消息的what字段区分不同类型的消息
            when (msg.what) {
                // 消息类型-1:绑定客户端的Messenger
                -1 -> {
                    Log.d(TAG, "绑定客户端 Messenger ${msg.replyTo}")
                    // 保存客户端的Messenger,用于后续主动推送消息
                    clientMessenger = msg.replyTo
                }
                // 消息类型0:普通消息接收
                0 -> {
                    // 从消息数据中获取客户端发送的内容
                    val msgStr = msg.data.getString("msg")
                    Log.d(TAG, "服务端收到:$msgStr")
                }
                // 消息类型1:需要回复的消息
                1 -> {
                    // 获取客户端发送的内容
                    val msgStr = msg.data.getString("msg")
                    Log.d(TAG, "服务端收到:$msgStr")

                    // 获取客户端的Messenger,用于回复消息
                    val clientMessenger = msg.replyTo

                    // 创建回复消息,类型为2
                    val replyMsg = Message.obtain(null, 2).apply {
                        // 设置回复数据
                        data = Bundle().apply {
                            putString("reply", "已收到消息")
                        }
                    }
                    try {
                        // 向客户端发送回复
                        clientMessenger.send(replyMsg)
                    } catch (e: RemoteException) {
                        e.printStackTrace()
                    }
                }
                // 可以添加更多消息类型的处理逻辑
            }
        }
    }

    // 服务端的Messenger,关联上面定义的handler,用于接收客户端消息
    private val serviceMessenger = Messenger(handler)

    // 服务创建时调用
    override fun onCreate() {
        super.onCreate()
        // 创建协程作用域,在IO线程中执行定时任务
        CoroutineScope(Dispatchers.IO).launch {
            // 循环执行,每秒向客户端主动推送一次消息
            while (true) {
                delay(1000) // 延迟1秒
                try {
                    // 创建主动推送的消息,类型为3
                    val replyMsg = Message.obtain(null, 3).apply {
                        data = Bundle().apply {
                            putString("reply", "服务端主动推送的消息")
                        }
                    }
                    // 通过之前保存的clientMessenger向客户端发送消息
                    clientMessenger?.send(replyMsg)
                } catch (e: RemoteException) {
                    // 捕获异常(如客户端已断开连接)
                    e.printStackTrace()
                }
            }
        }
    }
    
    // 当客户端绑定服务时调用,返回服务端的IBinder
    override fun onBind(intent: Intent): IBinder {
        // 返回serviceMessenger的binder,客户端通过它与服务通信
        return serviceMessenger.binder
    }
}

从上面的代码可以发现,Messenger是以串行的方式处理客户端发来的消息,如果大量的消息同时发送到服务端,服务端仍然只能一个个处理,如果有大量的并发请求,那么用Messenger就不太合适了。同时,Messenger的作用主要是为了传递消息,很多时候我们可能需要跨进程调用服务端的方法,这种情形用Messenger就无法做到了,但是我们可以使用AIDL来实现跨进程的方法调用。

注意:Messenger 进行跨进程通信的大小限制为 1M

AIDL

在使用 AIDL 之前,我们需要在模块的 build.gradle 文件中声明配置,如下所示:

ini 复制代码
android { 
    ...
    buildFeatures {
        aidl = true
    }
}

然后在 main 目录下创建 aidl 目录,在 aidl 目录中创建相应的 aidl 文件。如下所示:

  • IUserManager.aidl
java 复制代码
package com.example.ipc;
import com.example.ipc.User; // 显式导入
import com.example.ipc.IUserChangeCallback;
// 需要通过 Android studio 的 aidl 来生成 package 才行
interface IUserManager {
    void addUser(in User user); // in:数据从客户端到服务端
    List<User> getUserList();
    void registerCallback(IUserChangeCallback callback);
    void unregisterCallback(IUserChangeCallback callback);
}
  • IUserChangeCallback.aidl
java 复制代码
package com.example.ipc;
import com.example.ipc.User;

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

interface IUserChangeCallback {
    void changed(in User user);
}
  • User.aidl
go 复制代码
// User.aidl
package com.example.ipc;
parcelable User;

需要注意,创建的 User.aidl 必须要存在一个对应的数据类。这里的数据类是也叫 User。如下所示。

java 复制代码
// 注意,包名要和 User.aidl 相同,否则会出现找不到的错误。
package com.example.ipc;

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

import androidx.annotation.NonNull;

public final class User implements Parcelable {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public User(Parcel in) {
        name = in.readString();
        age = in.readInt();
    }

    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(@NonNull Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }

    @NonNull
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }


    // 构造方法、describeContents、writeToParcel 等方法
}

aidl文件位置如下所示:

创建好 aidl 文件后,我们就可以像在同一进程调用方法一样来使用它们了。代码如下所示:

  • 客户端
kotlin 复制代码
class AidlActivity : AppCompatActivity() {
    private var userManager: IUserManager? = null

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            userManager = IUserManager.Stub.asInterface(service)
            userManager?.registerCallback(object : IUserChangeCallback.Stub() {
                override fun changed(user: User?) {
                    Log.d("AidlActivity", "客户端收到的用户信息: $user")
                }

            })
        }

        override fun onServiceDisconnected(className: ComponentName) {
            userManager = null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_aidl)

        // 绑定服务
        Intent(this, AidlService::class.java).also { intent ->
            bindService(intent, connection, BIND_AUTO_CREATE)
        }

        findViewById<Button>(R.id.add_user).setOnClickListener(this::addUser)


    }

    fun addUser(view: View) {
        try {
            //userManager?.addUser(User(StringUtils.getOneMStringContent(), 25))
            userManager?.addUser(User("客户端创建的用户", 25))
        } catch (e: RemoteException) {
            e.printStackTrace()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        unbindService(connection)
    }
}
  • 服务端
kotlin 复制代码
class AidlService : Service() {

    companion object {
        private const val TAG = "AidlService"
    }

    // 线程安全的用户列表,用于存储用户数据
    private val _userList = CopyOnWriteArrayList<User>()

    // 远程回调列表,用于管理客户端注册的回调接口
    // RemoteCallbackList专门用于跨进程回调管理
    private val _callbackList = RemoteCallbackList<IUserChangeCallback>()

    // 实现AIDL接口的Binder对象,客户端通过它调用服务端方法
    private val binder = object : IUserManager.Stub() {
        // 实现AIDL中定义的添加用户方法
        @Throws(RemoteException::class)
        override fun addUser(user: User) {
            // 将用户添加到列表
            _userList.add(user)
            // 打印当前列表中的所有用户,用于调试
            _userList.forEach {
                Log.d(TAG, "addUser: $it")
            }
        }

        // 实现AIDL中定义的获取用户列表方法
        @Throws(RemoteException::class)
        override fun getUserList(): MutableList<User> {
            return _userList
        }

        // 注册回调接口,客户端通过此方法注册以接收通知
        override fun registerCallback(callback: IUserChangeCallback?) {
            _callbackList.register(callback)
        }

        // 解除注册回调接口
        override fun unregisterCallback(callback: IUserChangeCallback?) {
            _callbackList.unregister(callback)
        }

    }

    // 服务创建时调用
    override fun onCreate() {
        super.onCreate()
        // 创建协程作用域,在IO线程中执行定时任务
        CoroutineScope(Dispatchers.IO).launch {
            // 循环执行,每秒向所有注册的客户端发送一次回调通知
            while (true) {
                delay(1000) // 延迟1秒
                
                // 使用RemoteCallbackList时,必须首先调用beginBroadcast(),最后调用finishBroadcast().得成对出现
                val callbackCount = _callbackList.beginBroadcast()
                // 遍历所有注册的回调
                for (i in 0 until callbackCount) {
                    try {
                        // 调用每个回调的changed方法,发送一个新创建的User对象
                        _callbackList.getBroadcastItem(i)?.changed(User("服务端创建的 User", 18))
                    } catch (e: RemoteException) {
                        e.printStackTrace()
                    }
                }
                _callbackList.finishBroadcast()
            }
        }
    }

    // 当客户端绑定服务时调用,返回AIDL的Binder对象
    override fun onBind(intent: Intent): IBinder {
        return binder
    }
}

注意:AIDL 和 Messenger 一样,进行跨进程通信的大小限制为 1M。这是因为它们内部都是通过 Binder 实现的。

ContentProvider

ContentProvider 是 Android 四大组件之一。虽然 ContentProvider 内部也是由Binder 实现通信的,但是它却没有数量量的限制,可能是内部进行了处理。ContentProvider 的使用如下:

  • ContentProvider 端
kotlin 复制代码
class BigDataContentProvider: ContentProvider() {

    companion object {
        private const val TAG = "BigDataContentProvider"
        // 定义 authority,作为 ContentProvider 的唯一标识
        const val AUTHORITY: String = "com.example.myapp.provider"
        const val BIG_STRING_DATA: Int = 1
        const val FILE: Int = 2
        // 定义 Uri 匹配器,用于匹配不同的 Uri 请求
        @JvmStatic
        val sUriMatcher: UriMatcher = UriMatcher(UriMatcher.NO_MATCH).apply {
            // 注册 Uri 模式
            addURI(AUTHORITY, "data", BIG_STRING_DATA)
            addURI(AUTHORITY, "files/*", FILE)
        }
    }


    // 初始化 Provider(如数据库连接)
    override fun onCreate(): Boolean {
        return true
    }

    // 查询数据
    override fun query(
        uri: Uri, projection: Array<String?>?, selection: String?,
        selectionArgs: Array<String?>?, sortOrder: String?
    ): Cursor? {
        Log.d(TAG, "query: uri = $uri")
        when (sUriMatcher.match(uri)) {
            BIG_STRING_DATA -> {
                val columns = arrayOf("_id", "content")
                val cursor = MatrixCursor(columns)
                cursor.addRow(arrayOf<Any>(0, StringUtils.getOneMStringContent()))
                return cursor
            }
            else -> {
                Log.e(TAG, "query: ")
            }
        }
        // 返回查询结果 Cursor
        return null
    }

    // 核心方法:返回文件描述符,支持跨进程高效传输
    @Throws(FileNotFoundException::class)
    override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
        Log.d(TAG, "openFile: uri = $uri context = $context")
        val context = context ?: return null
        return runCatching {
            // 注意,获取手机存储的文件对象。注意,apk 包内部的文件,比如 assets 下
            // 的文件是无法传递出去的
            val file = getFile(context)
            Log.d(TAG, "openFile: file = $file ")
            // 打开文件并返回文件描述符
            val result = ParcelFileDescriptor.open(file, MODE_READ_ONLY)
            Log.d(TAG, "openFile: result = $result")
            result
        }.onFailure {
            Log.e(TAG, "openFile: ",it )
        }.getOrNull()

    }


    // 获取数据类型
    override fun getType(uri: Uri): String? {
        Log.d(TAG, "getType: uri = $uri")
        return when (sUriMatcher.match(uri)) {
            BIG_STRING_DATA -> "vnd.android.cursor.dir/vnd.com.example.data"
            FILE -> "application/octet-stream"
            else -> null
        }
    }

    // 插入数据
    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        // 处理插入逻辑
        return null
    }

    // 删除数据
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String?>?): Int {
        // 处理删除逻辑
        return 0
    }

    // 更新数据
    override fun update(
        uri: Uri, values: ContentValues?, selection: String?,
        selectionArgs: Array<String?>?
    ): Int {
        // 处理更新逻辑
        return 0
    }

}

AndroidManifest.xml 配置如下:

ini 复制代码
<provider
    android:name=".contentprovider.BigDataContentProvider"
    android:process=":remoteProvider"
    android:authorities="com.example.myapp.provider"
    android:exported="true"
/>
  • 客户端
kotlin 复制代码
class ContentProviderActivity : AppCompatActivity() {

    companion object {
        private const val TAG = "ContentProviderActivity"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_memory_file_client)

        findViewById<Button>(R.id.add_user).setOnClickListener {
            getBigString()

            getFile()
        }
    }

    private fun getFile() {
        // 获取一张图片
        val uri = Uri.parse("content://com.example.myapp.provider/files/xxx.jpg")
        try {
            // 通过 ContentResolver 打开输入流(底层使用文件描述符)
            val inputStream = contentResolver.openInputStream(uri) ?: run {
                Log.e(TAG, "getFile: null", ) 
                return
            }
            val bitmap = BitmapFactory.decodeStream(inputStream)
            Log.d(TAG, "getFile: $bitmap")
            findViewById<ImageView>(R.id.serviceView).setImageBitmap(bitmap)
        } catch (e: Exception) {
            Log.e(TAG, "getFile: ", e)
        }
    }

    private fun getBigString() {
        // 获取超过 1 M 的字符串数据
        val cursor = contentResolver.query(
            Uri.parse("content://com.example.myapp.provider/data"),
            arrayOf("_id", "content"),
            null, null, null
        )

        if (cursor != null) {
            try {
                // 逐条读取,避免一次性获取所有数据
                while (cursor.moveToNext()) {
                    val content = cursor.getString(cursor.getColumnIndexOrThrow("content"))
                    // 打印大量数据
                    Log.d(TAG, "content: $content")
                }
            } finally {
                cursor.close() // 务必关闭 Cursor 释放资源
            }
        }
    }

}

MemoryFile 和 SharedMemory

在 Android 中,我们还可以使用 MemoryFile 来实现共享内存。在 Android framework 层,对于大数据(超过1M)就是通过 MemoryFile 来实现跨进程通信的。其中 MemoryFile 内部就是由 SharedMemory 实现的。

要使用 MemoryFile ,我们就需要先使用 aidl 来传递 ParcelFileDescriptor 对象,通过它来实现共享内存。aidl 文件如下所示:

java 复制代码
// IMemoryManager.aidl
package com.example.ipc;
import com.example.ipc.IMemoryCallback;
// Declare any non-default types here with import statements

interface IMemoryManager {
     void client2server(in ParcelFileDescriptor pfd);
     void registerCallback(IMemoryCallback callback);
     void unregisterCallback(IMemoryCallback callback);
}


// IMemoryCallback.aidl
package com.example.ipc;

interface IMemoryCallback {
    void server2client(in ParcelFileDescriptor pfd);
}
  • 客户端
kotlin 复制代码
class MemoryFileClientActivity : AppCompatActivity() {

    companion object {
        private const val TAG = "MemoryFileDemo"
    }

    private val handler = Handler(Looper.getMainLooper())
    private var memoryManager: IMemoryManager? = null
    private val memoryCallback = object : IMemoryCallback.Stub() {
        override fun server2client(pfd: ParcelFileDescriptor?) {
            handler.post {
                val fd = pfd?.fileDescriptor
                if (fd != null) {
                    val inputStream = FileInputStream(fd)
                    val bitmap = BitmapFactory.decodeStream(inputStream)
                    findViewById<ImageView>(R.id.serviceView).setImageBitmap(bitmap)
                }
            }
        }

    }

    private val connection = object : ServiceConnection {
        override fun onServiceConnected(className: ComponentName, service: IBinder) {
            // https://github.com/kongpf8848/aidldemo
            memoryManager = IMemoryManager.Stub.asInterface(service)
            memoryManager?.registerCallback(memoryCallback)
        }

        override fun onServiceDisconnected(className: ComponentName) {
            memoryManager?.unregisterCallback(memoryCallback)
            memoryManager = null
        }
    }


    private fun readFromMemory() {
        var pfd: ParcelFileDescriptor? = null
        try {
            val inputStream = assets.open("server1.jpg") // 读取assets目录下文件
            val byteArray = inputStream.readBytes() // 将inputStream转换成字节数组

            val memoryFile = MemoryFile("image", byteArray.size) // 创建MemoryFile
            memoryFile.writeBytes(byteArray, 0, 0, byteArray.size) // 向MemoryFile中写入字节数组

            // 反射获取 FileDescriptor
            val fdField: Method = MemoryFile::class.java.getDeclaredMethod("getFileDescriptor")
            fdField.isAccessible = true
            val fd = fdField.invoke(memoryFile) as FileDescriptor // 获取MemoryFile对应的FileDescriptor
            pfd = ParcelFileDescriptor.dup(fd) // 根据FileDescriptor创建ParcelFileDescriptor

            memoryManager?.client2server(pfd)
        } catch (e: Exception) {
            Log.e(TAG, "读取共享内存失败", e)
        } finally {
            pfd?.close()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_memory_file_client)

        // 绑定服务(跨进程)
        Intent(this, MemoryFileService::class.java).also { intent ->
            bindService(intent, connection, BIND_AUTO_CREATE)
        }

        findViewById<Button>(R.id.add_user).setOnClickListener {
            readFromMemory()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        unbindService(connection)
    }
}
  • 服务端
kotlin 复制代码
class MemoryFileService : Service() {

    companion object {
        private const val TAG = "MemoryFileDemo"
    }
    private val callbacks = RemoteCallbackList<IMemoryCallback>()
    private var memoryFile: MemoryFile? = null

    private val hookFdUtils: HookFdUtils by lazy {
        HookFdUtils()
    }


    private val binder = object : IMemoryManager.Stub() {
        override fun client2server(pfd: ParcelFileDescriptor?) {
            val pfd = pfd ?: return
            /**
             * 从ParcelFileDescriptor中获取FileDescriptor
             */
            val fileDescriptor = pfd.fileDescriptor

            /**
             * 根据FileDescriptor构建InputStream对象
             */
            val fis = FileInputStream(fileDescriptor)
            runCatching {
                // 把大数据传递回去
                val count = callbacks.beginBroadcast()
                for (i in 0 until count) {
                    val callback = callbacks.getBroadcastItem(i)
                    val byteArray = fis.readBytes()
                    Log.d(TAG, "把传递过来的 ${byteArray.size} byte 数据传递回去")
                    memoryFile?.close()
                    memoryFile = MemoryFile("server_image", byteArray.size)
                    val memoryFile = memoryFile ?: return
                    memoryFile.writeBytes(byteArray, 0, 0, byteArray.size)

                    val fdField: Method = MemoryFile::class.java.getDeclaredMethod("getFileDescriptor")
                    fdField.isAccessible = true
                    val fd = fdField.invoke(memoryFile) as FileDescriptor // 获取MemoryFile对应的FileDescriptor

                    val pfd= ParcelFileDescriptor.dup(fd)
                    callback.server2client(pfd)
                }
                callbacks.finishBroadcast()
            }.onFailure {
                Log.e(TAG, "server2client error: ${it.message}", it)
            }
        }

        override fun registerCallback(callback: IMemoryCallback?) {
            callbacks.register(callback)
        }

        override fun unregisterCallback(callback: IMemoryCallback?) {
            callbacks.unregister(callback)
        }

    }

    override fun onBind(intent: Intent): IBinder {
        return binder
    }

    override fun onDestroy() {
        super.onDestroy()
        // 释放资源
        memoryFile?.close()
    }
}

从上面的代码可以看到,我们需要通过反射的方式来获取 MemoryFile 所对应的 FileDescriptor。这是因为 MemoryFile 的 getFileDescriptor 方法是@hide 方法,无法直接调用,因此使用反射。但是在 Android 9 以后,Google 限制访问 @hide 方法,导致 MemoryFile 法反射调用MemoryFile 的 getFileDescriptor 方法,从而无法跨进程访问。

解决方向有:可以使用 JNI 来反射调用MemoryFile 的 getFileDescriptor 方法。代码如下所示:

首先,设置 ndk 的配置:

ini 复制代码
android {
    externalNativeBuild {
        cmake {
            path = file("src/main/cpp/CMakeLists.txt")
            version = "3.22.1"
        }
    }
}

然后创建 cpp 目录,再该目录下创建 CMakeLists.txt 和 HookFdUtils.cpp。

  • CMakeLists.txt
scss 复制代码
cmake_minimum_required(VERSION 3.22.1)


project("hookfdbinary")


add_library(hookfdbinary SHARED
        HookFdUtils.cpp)

target_link_libraries(hookfdbinary
        android
        log)
  • HookFdUtils.cpp
scss 复制代码
#include <jni.h>
#include <android/log.h>
#include <string>

#define LOG_TAG "HookFdUtils"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

// 实现getMemoryFileFd方法
jobject getMemoryFileFd(JNIEnv* env, jobject thiz, jobject memoryFile) {
    if (memoryFile == nullptr) {
        LOGE("memoryFile is null");
        return nullptr;
    }

    // 获取MemoryFile类的Class对象
    jclass memoryFileClass = env->GetObjectClass(memoryFile);
    if (memoryFileClass == nullptr) {
        LOGE("Failed to get MemoryFile class");
        return nullptr;
    }

    // 获取getFileDescriptor方法ID (返回FileDescriptor对象,无参数)
    jmethodID getFdMethod = env->GetMethodID(
            memoryFileClass,
            "getFileDescriptor",
            "()Ljava/io/FileDescriptor;"
    );

    if (getFdMethod == nullptr) {
        LOGE("Failed to get getFileDescriptor method");
        env->DeleteLocalRef(memoryFileClass);
        return nullptr;
    }

    // 调用getFileDescriptor方法获取FileDescriptor对象
    jobject fileDescriptor = env->CallObjectMethod(memoryFile, getFdMethod);

    // 释放局部引用
    env->DeleteLocalRef(memoryFileClass);

    return fileDescriptor;
}


// 方法映射表:Java方法名 -> 方法签名 -> C++函数指针
static const JNINativeMethod gMethods[] = {
        {
                "getMemoryFileFd",  // Java方法名
                "(Landroid/os/MemoryFile;)Ljava/io/FileDescriptor;",  // 方法签名
                (void*)getMemoryFileFd  // 对应C++函数
        }
};

// 要注册的类名(完整包名+类名)
static const char* const kClassName = "com/example/ipc/HookFdUtils";

// JNI_OnLoad:库加载时自动调用,完成注册
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = nullptr;
    jint result = -1;

    // 获取JNI环境
    if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        LOGE("Failed to get JNI environment");
        return result;
    }

    if (env == nullptr) {
        LOGE("env is null");
        return result;
    }

    // 查找要注册的类
    jclass clazz = env->FindClass(kClassName);
    if (clazz == nullptr) {
        LOGE("Failed to find class: %s", kClassName);
        return result;
    }

    // 注册方法
    if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods)/sizeof(gMethods[0])) < 0) {
        LOGE("Failed to register natives");
        env->DeleteLocalRef(clazz);
        return result;
    }

    env->DeleteLocalRef(clazz);

    // 返回支持的JNI版本
    return JNI_VERSION_1_6;
}
  • HookFdUtils
java 复制代码
public class HookFdUtils {

    static {
        System.loadLibrary("hookfdbinary");
    }

    public native FileDescriptor getMemoryFileFd(MemoryFile memoryFile);

}

这样就可以使用 HookFdUtils 来代替java反射来调用MemoryFile 的 getFileDescriptor 方法了。代码如下所示:

kotlin 复制代码
private val hookFdUtils: HookFdUtils by lazy {
    HookFdUtils()
}

val fd = hookFdUtils.getMemoryFileFd(memoryFile)

这样就可以在 Android 9 以上的版本中使用 MemoryFile 来传输大数据了。

参考

相关推荐
啊森要自信2 小时前
【MySQL 数据库】MySQL用户管理
android·c语言·开发语言·数据库·mysql
黄毛火烧雪下2 小时前
(二)Flutter插件之Android插件开发
android·flutter
2501_916007472 小时前
iOS 上架技术支持全流程解析,从签名配置到使用 开心上架 的实战经验分享
android·macos·ios·小程序·uni-app·cocoa·iphone
sakoba4 小时前
MySQL的json处理相关方法
android·学习·mysql·json
神仙别闹4 小时前
Android 端 2D 横屏动作冒险类闯关游戏
android·游戏
坏小虎4 小时前
Android App Startup 库使用说明文档,初始化不再用Application了...
android
lichong95111 小时前
Android studio 修改包名
android·java·前端·ide·android studio·大前端·大前端++
爱学习的大牛12313 小时前
MVVM 架构 android
android·mvvm
alexhilton16 小时前
理解retain{}的内部机制:Jetpack Compose中基于作用域的状态保存
android·kotlin·android jetpack
꒰ঌ 安卓开发໒꒱17 小时前
Mysql 坏表修复
android·mysql·adb