Android7.0バインダーの新しいデータ転送制限TransactionTooLargeException:データパーセルサイズxxxバイト原因と解決策
Android7 0 Binders New Data Transfer Restrictions Transactiontoolargeexception
元のリンク: https://www.kaelli.com/20.html
最近、Buglyで報告された問題がより頻繁に発生しているように見えることがわかりました。問題の原因分析と解決策を記録します。
まず、Buglyのエラーログを表示します。
java.lang.RuntimeException:android.os.TransactionTooLargeException: data parcel size 587588 bytes android.app.ActivityThread$StopInfo.run(ActivityThread.java:3939) ...... Caused by: android.os.TransactionTooLargeException:data parcel size 587588 bytes android.os.BinderProxy.transactNative(Native Method) android.os.BinderProxy.transact(Binder.java:619) android.app.ActivityManagerProxy.activityStopped(ActivityManagerNative.java:3748) android.app.ActivityThread$StopInfo.run(ActivityThread.java:3931) android.os.Handler.handleCallback(Handler.java:751) android.os.Handler.dispatchMessage(Handler.java:95) android.os.Looper.loop(Looper.java:154) android.app.ActivityThread.main(ActivityThread.java:6285) java.lang.reflect.Method.invoke(Native Method) com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:939) com.android.internal.os.ZygoteInit.main(ZygoteInit.java:829)
問題は表面的には明らかであるように見えます。パケットが大きすぎるため、TransactionTooLargeException例外です。ただし、ここには紛らわしい場所もいくつかあります。データの最初の587,588バイトは小さくはありませんが、大きすぎません。データはインテントを介して送信されます。特定のサイズ制限は異なります。現在、システムのバージョンが異なると異なる場合があります。意見は1MB以下です。 公式文書 そのような説明があります:
The Binder transaction failed because it was too large. During a remote procedure call, the arguments and the return value of the call are transferred as Parcel objects stored in the Binder transaction buffer. If the arguments or the return value are too large to fit in the transaction buffer, then the call will fail and TransactionTooLargeException will be thrown. The Binder transaction buffer has a limited fixed size, currently 1Mb, which is shared by all transactions in progress for the process. Consequently this exception can be thrown when there are many transactions in progress even when most of the individual transactions are of moderate size.
ただし、512Kを超えることはできないと実験して考えた人もいます。さて、512Kの値を見ると、データが大きすぎるようです。しかし実際には、収集されたエラーログには200〜300 Kがあり、非常に混乱しています。
第二に、私を驚かせた一つの事実があります。このTransactionTooLargeExceptionが異常であるという事実は、Android Nバージョンに集中しています。つまり、バージョン番号は24と25のみで、24未満または25を超えています。このアプリは最低16と互換性がありますが、非常に古い4.0、4.1、4.4電話、このエラーは報告されていません。明らかに、Android Nには特別な場所がいくつかあるはずです。TransactionTooLargeExceptionはBinderがデータを渡す例外であるため、対応するソースコードを理解する必要があります。
/frameworks/base/core/jni/android_util_Binder.cpp内 signalExceptionForError 関数では、FAILED_TRANSACTIONの間違ったブランチに対して、次のコードがあります。
case FAILED_TRANSACTION: { ALOGE('!!! FAILED BINDER TRANSACTION !!! (parcel size = %d)', parcelSize) const char* exceptionToThrow char msg[128] // TransactionTooLargeException is a checked exception, only throw from certain methods. // FIXME: Transaction too large is the most common reason for FAILED_TRANSACTION // but it is not the only one. The Binder driver can return BR_FAILED_REPLY // for other reasons also, such as if the transaction is malformed or // refers to an FD that has been closed. We should change the driver // to enable us to distinguish these cases in the future. if (canThrowRemoteException && parcelSize > 200*1024) { // bona fide large payload exceptionToThrow = 'android/os/TransactionTooLargeException' snprintf(msg, sizeof(msg)-1, 'data parcel size %d bytes', parcelSize) } else { // Heuristic: a payload smaller than this threshold 'shouldn't' be too // big, so it's probably some other, more subtle problem. In practice // it seems to always mean that the remote process died while the binder // transaction was already in flight. exceptionToThrow = (canThrowRemoteException) ? 'android/os/DeadObjectException' : 'java/lang/RuntimeException' snprintf(msg, sizeof(msg)-1, 'Transaction failed on small parcel remote process probably died') } jniThrowException(env, exceptionToThrow, msg) } break
明らかに、ここでは例外がスローされますが、それでも紛らわしい場所があります。送信されたデータが200Kを超え、エラーが報告されます。そして非常に重要なのは、このコードは7.0ではなく6.0のソースコードです! (もちろん、7.0以降のソースコードは基本的にここでは同じロジックです。)しかし、このエラーは6.0電話では報告されていません。 7.0で何が起こったのかを確認する必要があるようです。 変化する :
Many platform APIs have now started checking for large payloads being sent across Bindertransactions, and the system now rethrows TransactionTooLargeExceptions as RuntimeExceptions, instead of silently logging or suppressing them. One common example is storing too much data in onSaveInstanceState(), which causes ActivityThread.StopInfo to throw a RuntimeException when your app targets Android 7.0.
7.0の変更ログには、バインダーのデータ転送に新しい制限があることが記載されていることがわかります。データ量が比較的多い場合、TransactionTooLargeExceptionsがスローされますが、使用されるデータ量に関する具体的な説明はありません。この問題を引き起こしました。これが理由であるはずです。バインダーのデータ量を制限することは、当然、パフォーマンスを考慮するためです。理解できますが、具体的な制限を直接説明していない理由がわかりません。もちろん、構成が異なる携帯電話の制限は異なる場合があります。
問題の原因を知って、次のステップは解決策です。
- 方法1:もちろん、最も簡単で簡単な方法は、データの量を少なくすることです。
大量のデータを渡す場合、通常はオブジェクトを渡します。これは通常、SerilizableまたはParcelableインターフェイスによって実装されます。単純なクラスオブジェクトの場合、次のようになります。
public class Person implements Serilizable { public String name public int age }
つまり、他にカスタムクラスが埋め込まれていないため、あまり多くの処理を行う必要はないと言ったほうがよいでしょう。それでもエラーが報告される場合は、データが多すぎます。いくつかの役に立たないフィールドを破棄することを検討するか、方法2と3を直接調べることができます。次のように、1つのレイヤーまたは複数のレイヤーがネストされている場合。
public class Person implements Serilizable { public String name public int age public Parents parents public class Parents implements Serilizable { public Person father public Person mother } }
この時点で、それをJson文字列に変換すると、サイズが大幅に削減されます(クラスの構造が複雑になるほど、ボリュームが削減されます)。私の通常のメソッドはnewGson()。toJson(object)で、文字列をIntentに入れ、受信した場所でオブジェクトに変換します。
- 方法2:メソッドが文字列に変換された後、制限を超えた場合はどうすればよいですか?これが信じられないことだとは思わないでください。 100万を超えるデータを含む複雑なページの場合は、文字列をローカルファイルに書き込んでから、受信した場所でファイルを読み取ります。 (非同期の使用、およびファイルが書き込まれた後、受信場所にジャンプすることを確認する方法に注意してください)
- バインダーのバッファーには、データのサイズに大きな制限があり(1M以下、15スレッドがこのスペースを共有します。実際、操作するスレッド内では、データが200Kを超えてハングする可能性があります)、方法を変更するだけです。 EventBusを使用します(またはRxJavaでイベント転送を実装します)。 EventBusを使用すると、スティッキーイベントpostStickyEventを使用して、次のアクティビティで受け取ることができます(最後のremoveStickyEvent操作を忘れないでください)。
要約すると、問題がひどくない場合は、問題を分析して解決する能力を養う必要があります。開発作業が長い間行われた後は、コードを書くだけでは不十分です。問題を分析して解決する能力は明らかにもっと重要です。それは個人の成長に非常に役立ちます。それ以外の場合は、「コードファーマー」しか実行できません...