天天看點

你不知道的Bundle

Bundle基本概念

     在Android中,Bundle主要用于傳遞資料,它是以鍵值對的形式儲存資料。我們經常使用Bundle在Activity之間傳遞資料,資料類型可以是基本類型或它們對應的數組,也可以是對象或對象數組。當Bundle傳遞的是對象或對象數組時,必須實作Serializable或Parcelable接口。

     根據Android的設計,同一應用的Activity可能會運作在不同程序中,是以處理資料傳遞時可能就會出現跨程序的情況。那麼如何在程序間傳遞對象呢?在Android中是通過Binder來實作程序間通信的。Parcel是一個存放讀取資料的容器,Android系統中的Binder通信就使用了Parcel類來進行用戶端與服務端資料的互動,而且AIDL的資料也是通過Parcel來互動的。在Java空間和C++都實作了Parcel,由于它在C/C++中,直接使用了記憶體來讀取資料,是以它更有效率。

Bundle資料傳輸異常

     Bundle的使用并不是我們看到的那麼簡單,我們在使用Bundle時會存在一些限制。下面就通過一個例子來示範這個問題。

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Bundle bundle = new Bundle();
                int[] array = new int[1024 * 1024];
                bundle.putIntArray("array", array);
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                intent.putExtras(bundle);
                startActivity(intent);
            }
        });
    }
}
           

     上面的例子中,我們通過Bundle傳遞資料給SecondActivity。由于傳遞的是整型數組,總共傳遞了4MB的資料到SecondActivity。運作程式後發現SecondActivity并沒有啟動,日志中出現了一個錯誤:

com.viclee.myapplication E/JavaBinder: !!! FAILED BINDER TRANSACTION !!!
           

     從内容可以看出這個異常與Binder有關,産生這個異常的原因是由于系統對使用Binder交換資料的buffer大小進行了限制,大小為1M。

     那麼,假如把傳遞的資料量減少,比如傳輸資料量減少為512kB,是否可以安全的傳遞資料呢?

     再次運作程式我們發現程式發生了異常,這一次的異常内容如下:

F/ActivityManager( 1068): android.os.TransactionTooLargeException
F/ActivityManager( 1068): at android.os.BinderProxy.transactNative(Native Method)
F/ActivityManager( 1068): at android.os.BinderProxy.transact(Binder.java:504)
F/ActivityManager( 1068): at android.app.ApplicationThreadProxy.scheduleLaunchActivity(ApplicationThreadNative.java:808)
F/ActivityManager( 1068): at com.android.server.am.ActivityStackSupervisor.realStartActivityLocked(ActivityStackSupervisor.java:1297)
F/ActivityManager( 1068): at com.android.server.am.ActivityStackSupervisor.attachApplicationLocked(ActivityStackSupervisor.java:632)
F/ActivityManager( 1068): at com.android.server.am.ActivityManagerService.attachApplicationLocked(ActivityManagerService.java:6572)
F/ActivityManager( 1068): at com.android.server.am.ActivityManagerService.attachApplication(ActivityManagerService.java:6637)
F/ActivityManager( 1068): at android.app.ActivityManagerNative.onTransact(ActivityManagerNative.java:491)
F/ActivityManager( 1068): at com.android.server.am.ActivityManagerService.onTransact(ActivityManagerService.java:2502)
F/ActivityManager( 1068): at com.android.server.am.HwActivityManagerService.onTransact(HwActivityManagerService.java:232)
F/ActivityManager( 1068): at android.os.Binder.execTransact(Binder.java:454)
           

     這次又是因為什麼呢?網上有人說這是由于整個應用共享了Binder用來進行資料交換的buffer。是以,我們通過Bundle在Activity之間傳遞的資料大小比1M還要小。

     至于說如何避免這個問題,我們盡量不要在intent中直接傳遞較大的資料,如果有這種需求,可以考慮靜态傳遞,比如檔案或共享記憶體。

Bundle原理分析

     通過檢視Bundle的源碼,我們發現Bundle内部是由ArrayMap實作的,而不是HashMap。Android為什麼要這麼設計呢?

     首先看一下它們的實作。

     ArrayMap内部是使用兩個數組進行資料存儲,一個數組記錄key的hash值,另一個數組記錄value值,内部使用二分法對key進行排序,并使用二分法進行添加、删除、查找資料,是以它隻适合于小資料量操作,在資料量較大的情況下它的性能将會退化。而HashMap内部則是數組+連結清單的結構,在資料量較少的情況下,HashMap的Entry Array比ArrayMap占用更多的記憶體。

      由于使用Bundle的場景大多數為小資料量,是以相比之下,使用ArrayMap儲存資料在操作速度和記憶體占用上都具有優勢,是以使用Bundle來傳遞資料,可以保證更快的速度和更少的記憶體占用。

       歡迎關注我的公衆号一起交流學習

你不知道的Bundle

繼續閱讀