title: What is an Android Binder "Transaction?"
body: <p>I'm getting an <code>TransactionTooLargeException</code> when sending messages between two Android processes running from a single APK. Each message only contains small amounts of data, <a href="https://developer.android.com/reference/android/os/TransactionTooLargeException" rel="noreferrer">much smaller than the 1 mb total (as specified in the docs)</a>.</p>
  
  <p>I created a test app (code below) to play around with this phenomenon, and noticed three things:</p>
  
  <ol>
  <li><p>I got a <code>android.os.TransactionTooLargeException</code> if each message was over 200 kb.</p></li>
  <li><p>I got a <code>android.os.DeadObjectException</code> if each message was under 200kb</p></li>
  <li><p>Adding a <code>Thread.sleep(1)</code> seems to have solved the issue. I cannot get either exception with a <code>Thread.sleep</code></p></li>
  </ol>
  
  <p><a href="https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/jni/android_util_Binder.cpp#L769" rel="noreferrer">Looking through the Android C++ code</a>, it seems like the <code>transaction</code> fails for an unknown reason and interpreted as one of those exceptions</p>
  
  <h3>Questions</h3>
  
  <ol>
  <li>What is a "<code>transaction</code>"?</li>
  <li>What defines what goes in a transaction? Is it a certain number of events in a given time? Or just a max number/size of events?</li>
  <li>Is there a way to "Flush" a transaction or wait for a transaction to finish?</li>
  <li>What's the proper way to avoid these errors? (Note: breaking it up into smaller pieces will simply throw a different exception)</li>
  </ol>
  
  <hr>
  
  <hr>
  
  <h1>Code</h1>
  
  <p><strong>AndroidManifest.xml</strong></p>
  
  <pre><code>&lt;?xml version="1.0" encoding="utf-8"?&gt;
  &lt;manifest package="com.example.boundservicestest"
            xmlns:android="http://schemas.android.com/apk/res/android"&gt;
  
      &lt;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"&gt;
          &lt;activity android:name=".MainActivity"&gt;
              &lt;intent-filter&gt;
                  &lt;action android:name="android.intent.action.MAIN"/&gt;
  
                  &lt;category android:name="android.intent.category.LAUNCHER"/&gt;
              &lt;/intent-filter&gt;
          &lt;/activity&gt;
          &lt;service android:name=".BoundService" android:process=":separate"/&gt;
      &lt;/application&gt;
  
  &lt;/manifest&gt;
  </code></pre>
  
  <p><strong>MainActivity.kt</strong></p>
  
  <pre><code>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 -&gt;
                  val bundle = Bundle().apply {
                      putByteArray("array", ByteArray(bundleSize))
                  }
                  myServiceConnection.sendMessage(i, bundle)
                  // uncommenting this line stops the exception from being thrown
  //                Thread.sleep(1)
              }
          }
      }
  }
  </code></pre>
  
  <p><strong>MyServiceConnection.kt</strong></p>
  
  <pre><code>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)
      }
  }
  </code></pre>
  
  <p><strong>BoundService.kt</strong></p>
  
  <pre><code>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
      }
  }
  </code></pre>
  
  <p><strong>build.gradle*</strong></p>
  
  <pre><code>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'
  }
  </code></pre>
  
  <p><strong>Stacktrace</strong></p>
  
  <pre><code>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) 
  </code></pre>