一文了解 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 来传输大数据了。

参考

相关推荐
小趴菜82272 小时前
安卓接入Kwai广告源
android·kotlin
2501_916013743 小时前
iOS 混淆与 App Store 审核兼容性 避免被拒的策略与实战流程(iOS 混淆、ipa 加固、上架合规)
android·ios·小程序·https·uni-app·iphone·webview
程序员江同学4 小时前
Kotlin 技术月报 | 2025 年 9 月
android·kotlin
码农的小菜园5 小时前
探究ContentProvider(一)
android
时光少年6 小时前
Compose AnnotatedString实现Html样式解析
android·前端
hnlgzb7 小时前
安卓中,kotlin如何写app界面?
android·开发语言·kotlin
jzlhll1237 小时前
deepseek kotlin flow快生产者和慢消费者解决策略
android·kotlin
火柴就是我7 小时前
Android 事件分发之动态的决定某个View来处理事件
android
一直向钱7 小时前
FileProvider 配置必须针对 Android 7.0+(API 24+)做兼容
android
zh_xuan7 小时前
Android 消息循环机制
android