web-dev-qa-db-ja.com

Androidバインダー「トランザクション」とは何ですか? "

2つのAndroidプロセスが単一のAPKから実行されているプロセス間でメッセージを送信すると、TransactionTooLargeExceptionが発生します。各メッセージには少量のデータしか含まれていません 1 mbの合計(ドキュメントで指定)

この現象を試すためのテストアプリ(以下のコード)を作成しましたが、3つのことに気付きました。

  1. 各メッセージが200 kbを超える場合、_Android.os.TransactionTooLargeException_を受け取りました。

  2. 各メッセージが200kb未満の場合、_Android.os.DeadObjectException_を受け取りました

  3. Thread.sleep(1)を追加することで問題は解決したようです。 _Thread.sleep_で例外を取得できません

Android C++コード を見ると、transactionが不明な理由で失敗し、これらの例外の1つとして解釈されているようです

ご質問

  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)
            }
        }
    }
}
_

MyServiceConnection.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)
    }
}
_

BoundService.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
    }
}
_

build.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'
}
_

Stacktrace

_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) 
_
16
Jason

1)「トランザクション」とは何ですか?

クライアントプロセスがサーバープロセス(この場合はservice?.send(message))を呼び出すと、マーシャリングされたデータ(Parcels)とともに呼び出すメソッドを表すコードが転送されます。この呼び出しはトランザクションと呼ばれます。クライアントバインダーオブジェクトはtransact()を呼び出しますが、サーバーバインダーオブジェクトはonTransact()メソッドでこの呼び出しを受け取ります。 This および This を確認します。

2)トランザクションで何が行われるかを定義するものは何ですか?一定の時間内に一定数のイベントですか?または、イベントの最大数/サイズだけですか?

一般的には、Binderプロトコルによって決定されます。プロキシ(クライアント)とスタブ(サービス)を利用します。プロキシは、高レベルのJava/C++メソッド呼び出し(要求)を受け取り、それらをパーセル(マーシャリング)に変換して、トランザクションをバインダーカーネルドライバーとブロックに送信します。一方、(サービスプロセス内の)スタブは、バインダーカーネルドライバーをリッスンし、コールバックの受信時にパーセルを非マーシャリングして、サービスが理解できる豊富なデータ型/オブジェクトにします。

Android Binder framwork sendの場合、transact()を介したデータは Parcel (Parcelオブジェクトでサポートされているすべてのタイプのデータを送信できることを意味します。)、バインダートランザクションバッファーに格納されます。バインダートランザクションバッファーの固定サイズには制限があり、現在1Mbです。これは、プロセスで進行中のすべてのトランザクションで共有されます。したがって、各メッセージが200 kbを超える場合、実行中のトランザクションが5以下になると、超過してTransactionTooLargeExceptionをスローする制限。したがって、個々のトランザクションの大部分が中程度のサイズであっても、進行中のトランザクションが多い場合、この例外がスローされる可能性があります。アクティビティを使用すると、DeadObjectException例外が発生します。別のプロセスで実行されているサービスがリクエストの実行中に停止した場合。Androidでプロセスを強制終了する理由はたくさんあります。詳しくは このブログ をご覧ください。

3)トランザクションを「フラッシュ」するか、トランザクションが完了するのを待つ方法はありますか?

transact()への呼び出しは、デフォルトでonTransact()がリモートスレッドで実行されるまで(プロセス2で実行)、クライアントスレッド(プロセス1で実行)をブロックします。したがって、トランザクションAPIは本質的に同期ですAndroid。 transact()呼び出しをブロックしたくない場合は、 IBinder.FLAG_ONEWAY フラグを渡すことができます(Flag to transact(int、Parcel、Parcel、int) )戻り値を待たずにすぐに戻るには、カスタムIBinder実装を実装する必要があります。

4)これらのエラーを回避する適切な方法は何ですか? (注:細かく分割すると、別の例外がスローされます)

  1. 一度にトランザクション数を制限しません。本当に必要なトランザクションを実行します(一度に実行中のすべてのトランザクションのメッセージサイズは1MB未満でなければなりません)。
  2. 他のAndroid実行中のコンポーネントが実行されている必要があります)プロセス(アプリプロセス以外)を確認します。

注:-Android異なるプロセス間でデータを送信するためにParcelをサポートします。Parcelには、IPC(ここでは、特定のタイプまたは一般的なParcelableインターフェースを記述するためのさまざまなメソッドを使用し、ライブIBinderオブジェクトへの参照を使用します。これにより、反対側がParcelの元のIBinderに接続されたプロキシIBinderを受信します。

サービスをアクティビティにバインドする適切な方法は、アクティビティonStart()でサービスをバインドし、アクティビティのライフサイクルであるonStop()でバインドを解除することです。

あなたの場合、MyServiceConnectionクラスのAdd onメソッド:-

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

そしてあなたの活動クラスで:-

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

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

これがお役に立てば幸いです。

5

1。「トランザクション」とは何ですか?

リモートプロシージャコール中に、呼び出しの引数と戻り値は、バインダートランザクションバッファーに格納されたParcelオブジェクトとして転送されます。引数または戻り値が大きすぎてトランザクションバッファーに収まらない場合、呼び出しは失敗し、 TransactionTooLargeException がスローされます。

2。トランザクションで何が行われるかを定義するものは何ですか?一定の時間内に一定数のイベントですか?または、イベントの最大数/サイズだけですか?唯一のサイズバインダートランザクションバッファーには、現在1Mbの制限された固定サイズがあり、プロセスで進行中のすべてのトランザクションによって共有されます。

3。トランザクションを「フラッシュ」する方法、またはトランザクションが終了するのを待つ方法はありますか?

番号

4。これらのエラーを回避する適切な方法は何ですか? (注:細かく分割すると、別の例外がスローされます)

私の理解によると、あなたのメッセージオブジェクトは画像のバイトアレイまたは1MB以上のサイズを持つ何かを持っているかもしれません。 Bundleでバイト配列を送信しないでください。

オプション1:画像の場合、バンドルを介してURIを渡す必要があると思います。ピカソを使用すると、キャッシュを使用して画像を何度もダウンロードすることがなくなります。

オプション2 [非推奨]バイト配列を圧縮します。必要なサイズまで圧縮されない可能性があるためです。)

//Convert to byte array
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArr = stream.toByteArray();

Intent in1 = new Intent(this, Activity2.class);
in1.putExtra("image",byteArr);

次に、アクティビティ2で:

byte[] byteArr = getIntent().getByteArrayExtra("image");
Bitmap bmp = BitmapFactory.decodeByteArray(byteArr, 0, byteArr.length);

オプション3 [推奨]ファイルの読み取り/書き込みを使用し、バンドル経由でURIを渡す

ファイルを書き込む:

private void writeToFile(String data,Context context) {
    try {
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(context.openFileOutput("filename.txt", Context.MODE_PRIVATE));
        outputStreamWriter.write(data);
        outputStreamWriter.close();
    }
    catch (IOException e) {
        Log.e("Exception", "File write failed: " + e.toString());
    } 
}

ファイルを読む:

private String readFromFile(Context context) {

    String ret = "";

    try {
        InputStream inputStream = context.openFileInput("filename.txt");

        if ( inputStream != null ) {
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String receiveString = "";
            StringBuilder stringBuilder = new StringBuilder();

            while ( (receiveString = bufferedReader.readLine()) != null ) {
                stringBuilder.append(receiveString);
            }

            inputStream.close();
            ret = stringBuilder.toString();
        }
    }
    catch (FileNotFoundException e) {
        Log.e("login activity", "File not found: " + e.toString());
    } catch (IOException e) {
        Log.e("login activity", "Can not read file: " + e.toString());
    }

    return ret;
}

オプション4(gsonを使用)オブジェクトの書き込み

[YourObject] v = new [YourObject]();
Gson gson = new Gson();
String s = gson.toJson(v);

FileOutputStream outputStream;

try {
  outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
  outputStream.write(s.getBytes());
  outputStream.close();
} catch (Exception e) {
  e.printStackTrace();
}

それを読み戻す方法:

 FileInputStream fis = context.openFileInput("myfile.txt", Context.MODE_PRIVATE);
 InputStreamReader isr = new InputStreamReader(fis);
 BufferedReader bufferedReader = new BufferedReader(isr);
 StringBuilder sb = new StringBuilder();
 String line;
 while ((line = bufferedReader.readLine()) != null) {
     sb.append(line);
 }

 String json = sb.toString();
 Gson gson = new Gson();
 [YourObject] v = gson.fromJson(json, [YourObject].class);
4
aanshu