首页 > 解决方案 > 什么是 Android Binder“事务”?

问题描述

TransactionTooLargeException在从单个 APK 运行的两个 Android 进程之间发送消息时,我得到了一个消息。每条消息仅包含少量数据,远小于 1 mb 的总数(如文档中所述)

我创建了一个测试应用程序(下面的代码)来解决这种现象,并注意到三件事:

  1. android.os.TransactionTooLargeException如果每条消息超过 200 kb,我会得到一个。

  2. android.os.DeadObjectException如果每条消息小于 200kb,我会得到一个

  3. 添加一个Thread.sleep(1)似乎已经解决了这个问题。我不能得到任何一个例外Thread.sleep

查看 Android C++ 代码,似乎由于transaction未知原因而失败并被解释为这些异常之一

问题

  1. 什么是“ transaction”?
  2. 什么定义了事务中的内容?在给定时间内是否有一定数量的事件?或者只是最大数量/大小的事件?
  3. 有没有办法“刷新”交易或等待交易完成?
  4. 避免这些错误的正确方法是什么?(注意:将其分解成更小的部分只会引发不同的异常)


代码

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.example.boundservicestest"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <service android:name=".BoundService" android:process=":separate"/>
    </application>

</manifest>

MainActivity.kt

class MainActivity : AppCompatActivity() {

    private lateinit var sendDataButton: Button
    private val myServiceConnection: MyServiceConnection = MyServiceConnection(this)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        myServiceConnection.bind()

        sendDataButton = findViewById(R.id.sendDataButton)

        val maxTransactionSize = 1_000_000 // i.e. 1 mb ish
        // Number of messages
        val n = 10
        // Size of each message
        val bundleSize = maxTransactionSize / n

        sendDataButton.setOnClickListener {
            (1..n).forEach { i ->
                val bundle = Bundle().apply {
                    putByteArray("array", ByteArray(bundleSize))
                }
                myServiceConnection.sendMessage(i, bundle)
                // uncommenting this line stops the exception from being thrown
//                Thread.sleep(1)
            }
        }
    }
}

我的服务连接.kt

class MyServiceConnection(private val context: Context) : ServiceConnection {
    private var service: Messenger? = null

    fun bind() {
        val intent = Intent(context, BoundService::class.java)
        context.bindService(intent, this, Context.BIND_AUTO_CREATE)
    }

    override fun onServiceConnected(name: ComponentName, service: IBinder) {
        val newService = Messenger(service)
        this.service = newService
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        service = null
    }

    fun sendMessage(what: Int, extras: Bundle? = null) {
        val message = Message.obtain(null, what)
        message.data = extras
        service?.send(message)
    }
}

绑定服务.kt

internal class BoundService : Service() {
    private val serviceMessenger = Messenger(object : Handler() {
        override fun handleMessage(message: Message) {
            Log.i("BoundService", "New Message: ${message.what}")
        }
    })

    override fun onBind(intent: Intent?): IBinder {
        Log.i("BoundService", "On Bind")
        return serviceMessenger.binder
    }
}

构建.gradle*

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 27
    defaultConfig {
        applicationId "com.example.boundservicestest"
        minSdkVersion 19
        targetSdkVersion 25
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:27.1.1'
}

堆栈跟踪

07-19 09:57:43.919 11492-11492/com.example.boundservicestest E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.boundservicestest, PID: 11492
    java.lang.RuntimeException: java.lang.reflect.InvocationTargetException
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:448)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807)
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 
     Caused by: android.os.DeadObjectException: Transaction failed on small parcel; remote process probably died
        at android.os.BinderProxy.transactNative(Native Method)
        at android.os.BinderProxy.transact(Binder.java:764)
        at android.os.IMessenger$Stub$Proxy.send(IMessenger.java:89)
        at android.os.Messenger.send(Messenger.java:57)
        at com.example.boundservicestest.MyServiceConnection.sendMessage(MyServiceConnection.kt:32)
        at com.example.boundservicestest.MainActivity$onCreate$1.onClick(MainActivity.kt:30)
        at android.view.View.performClick(View.java:6294)
        at android.view.View$PerformClick.run(View.java:24770)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6494)
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:807) 

标签: androidtransactionsandroid-serviceipcandroid-binder

解决方案


1)什么是“交易”?

当客户端进程调用服务器进程(在我们的例子中service?.send(message))时,它会传输表示要调用的方法的代码以及编组数据(Parcels)。此调用称为事务。客户端 Binder 对象调用transact(),而服务器 Binder 对象在方法中接收此调用onTransact()。检查这个这个

2) 什么定义了交易中的内容?在给定时间内是否有一定数量的事件?或者只是最大数量/大小的事件?

一般来说,它由 Binder 协议决定。它们使用代理(由客户端)和存根(由服务)。代理接受您的高级 Java/C++ 方法调用(请求)并将它们转换为 Parcels(编组)并将事务提交给 Binder Kernel Driver 并阻止。另一方面,存根(在服务进程中)监听 Binder 内核驱动程序,并在收到回调时将 Parcel 解组为服务可以理解的丰富数据类型/对象。

在 Android Binder 框架中通过 transact() 发送的数据是一个Parcel(意味着我们可以发送 Parcel 对象支持的所有类型的数据。),存储在 Binder 事务缓冲区中。Binder 事务缓冲区有一个有限的固定大小,当前为 1Mb,由该进程正在进行的所有事务共享。因此,如果每条消息超过 200 kb,则 5 个或更少的正在运行的事务将导致 limit 超过并抛出TransactionTooLargeException。因此,当有许多事务正在进行时,即使大多数单个事务的大小适中,也可能会引发此异常。一个活动会看到DeadObjectException如果它使用在另一个进程中运行的服务,该服务在执行请求的过程中死亡。在 Android 中杀死进程有很多原因。查看此博客以获取更多信息。

3)有没有办法“刷新”交易或等待交易完成?

transact()默认情况下阻塞客户端线程(在 process1 中运行)的调用,直到onTransact()它在远程线程中执行(在 process2 中运行)完成。因此,事务 API 在 Android 中本质上是同步的。如果您不希望 transact() 调用阻塞,那么您可以传递IBinder.FLAG_ONEWAY标志(标志到transact(int, Parcel, Parcel, int))立即返回,而无需等待任何返回值。您必须实施您为此定制的 IBinder 实现。

4)避免这些错误的正确方法是什么?(注意:将其分解成更小的部分只会引发不同的异常)

  1. 一次限制交易数量。做真正必要的事务(一次所有正在进行的事务的消息大小必须小于 1MB)。
  2. 确保必须在其中运行其他 Android 组件的进程(应用进程除外)。

注意:- Android 支持 Parcel 在不同进程之间发送数据。一个 Parcel 可以包含将在 IPC 另一侧未展平的展平数据(使用此处的各种方法来编写特定类型或通用 Parcelable 接口),以及对将导致另一侧接收的活动 IBinder 对象的引用与 Parcel 中的原始 IBinder 连接的代理 IBinder。

将服务与活动绑定的正确方法是在活动 onStart() 上绑定服务并在 onStop() 中取消绑定,这是活动的可见生命周期。

在您的情况下,在MyServiceConnection类中添加方法:-

fun unBind() { context.unbindService(this) }

在您的活动课程中:-

override fun onStart() {
        super.onStart()
        myServiceConnection.bind()
    }

    override fun onStop() {
        super.onStop()
        myServiceConnection.unBind()
    }

希望这会帮助你。


推荐阅读