【Android開發】Bundle機制詳解

在同一個地方跌倒兩次,才能體會到“好記性不如爛筆頭”!

一、Bundle簡介

??bundle在Android開發中非常常見,它的作用主要時用于傳遞數據;它所保存的數據是以key-value(鍵值對)的形式存在的,也就是說bundle是保存數據的容器,內部使用了Arraymap去存儲數據,也提供了很多get,put方法。
??bundle傳遞的數據包括:string、int、boolean、byte、float、long、double等基本類型或它們對應的數組,也可以是對象或對象數組。當bundle傳遞的是對象或對象數組時,必須實現Serialiable或Parcelable接口。
??bundle主要用于以下3個場合:
??1. Activity狀態數據的保存與恢復,涉及到兩個回調:①void onSaveInstanceState(Bundle outState);② void onCreate(Bundle savedInstanceState);
??2. Fragment的setArguments方法:void setArgument(Bundle args);
??3. 消息機制中的Message的setData方法:void setData(Bundle data)。

二、Bundle源碼解析

  • 首先看下Bundle的聲明:
    public final class Bundle extends BaseBundle implements Cloneable, Parcelable
    ??從聲明中我們可以看出:①它使用了final進行修飾,所以不可以被繼承;②它實現了兩個接口Cloneable和Parcelable,這就意味著它必須實現以下方法:
    ??1. public Object clone()
    ??2. public int describeContents()
    ??3. public void writeToParcel(Parcel parcel, int flags)
    ??4. public void readFromParcel(Parcel parcel)
    ??5. public static final Parcelable.Creator<Bundle> CREATOR = new Parcelable.Creator<Bundle>()
  • 再看bundle的內存結構:
    ArrayMap<String, Object> mMap = null
    ??它使用的是ArrayMap,這個集合類存儲的也是鍵值對,但是與Hashmap不同的是,hashmap采用的是“數組+鏈表”的方式存儲,而Arraymap中使用的是兩個數組進行存儲,一個數組存儲key,一個數組存儲value,內部的增刪改查都將會使用二分查找來進行,這個和SparseArray差不多,只不過sparseArray的key值只能是int型的,而Arraymap可以是map型,所以在數據量不大的情況下可以使用這兩個集合代替hashmap去優化性能;

三、Bundle繼承的方法

??Bundle操作的基本數據類型如下表所示,它們都繼承自BaseBundle (From class android.os.BaseBundle )

返回類型 函數 函數說明
void clear() Removes all elements from the mapping of this Bundle.
boolean containsKey(String key) Returns true if the given key is contained in the mapping of this Bundle.
object get(String key) Returns the entry with the given key as an object.
boolean getBoolean(String key, boolean defaultValue) Returns the value associated with the given key, or defaultValue if no mapping of the desired type exists for the given key.
boolean getBoolean(String key) Returns the value associated with the given key, or false if no mapping of the desired type exists for the given key.
boolean[] getBooleanArray(String key) Returns the value associated with the given key, or null if no mapping of the desired type exists for the given key or a null value is explicitly associated with the key.
double getDouble(String key, double defaultValue) Returns the value associated with the given key, or defaultValue if no mapping of the desired type exists for the given key.
double getDouble(String key) Returns the value associated with the given key, or 0.0 if no mapping of the desired type exists for the given key.
double[] getDoubleArray(String key) Returns the value associated with the given key, or null if no mapping of the desired type exists for the given key or a null value is explicitly associated with the key.
int getInt(String key) Returns the value associated with the given key, or 0 if no mapping of the desired type exists for the given key.
int getInt(String key, int defaultValue) Returns the value associated with the given key, or defaultValue if no mapping of the desired type exists for the given key.
int[] getIntArray(String key) Returns the value associated with the given key, or null if no mapping of the desired type exists for the given key or a null value is explicitly associated with the key.
long getLong(String key) Returns the value associated with the given key, or 0L if no mapping of the desired type exists for the given key.
long getLong(String key, long defaultValue) Returns the value associated with the given key, or defaultValue if no mapping of the desired type exists for the given key.
long[] getLongArray(String key) Returns the value associated with the given key, or null if no mapping of the desired type exists for the given key or a null value is explicitly associated with the key.
String getString(String key) Returns the value associated with the given key, or null if no mapping of the desired type exists for the given key or a null value is explicitly associated with the key.
String getString(String key, String defaultValue) Returns the value associated with the given key, or defaultValue if no mapping of the desired type exists for the given key or if a null value is explicitly associated with the given key.
String[] getStringArray(String key) Returns the value associated with the given key, or null if no mapping of the desired type exists for the given key or a null value is explicitly associated with the key.
boolean isEmpty() Returns true if the mapping of this Bundle is empty, false otherwise.
Set<String> keySet() Returns a Set containing the Strings used as keys in this Bundle.
void putAll(PersistableBundle bundle) Inserts all mappings from the given PersistableBundle into this BaseBundle.
void putBoolean(String key, boolean value) Inserts a Boolean value into the mapping of this Bundle, replacing any existing value for the given key.
void putBooleanArray(String key, boolean[] value) Inserts a boolean array value into the mapping of this Bundle, replacing any existing value for the given key.
void putDouble(String key, double value) Inserts a double value into the mapping of this Bundle, replacing any existing value for the given key.
void putDoubleArray(String key, double[] value) Inserts a double array value into the mapping of this Bundle, replacing any existing value for the given key.
void putInt(String key, int value) Inserts an int value into the mapping of this Bundle, replacing any existing value for the given key.
void putIntArray(String key, int[] value) Inserts an int array value into the mapping of this Bundle, replacing any existing value for the given key.
void putLong(String key, long value) Inserts a long value into the mapping of this Bundle, replacing any existing value for the given key.
void putLongArray(String key, long[] value) Inserts a long array value into the mapping of this Bundle, replacing any existing value for the given key.
void putString(String key, String value) Inserts a String value into the mapping of this Bundle, replacing any existing value for the given key.
void putStringArray(String key, String[] value) Inserts a String array value into the mapping of this Bundle, replacing any existing value for the given key.
void remove(String key) Removes any entry with the given key from the mapping of this Bundle.
int size() Returns the number of mappings contained in this Bundle.

四、構造方法

  • Constructs a new, empty Bundle.
    Bundle()
  • Constructs a new, empty Bundle that uses a specific ClassLoader for instantiating Parcelable and Serializable objects.
    Bundle(ClassLoader loader)
  • Constructs a new, empty Bundle sized to hold the given number of elements. The Bundle will grow as needed.
    Bundle(Int capacity)
  • Constructs a Bundle containing a copy of the mappings from the given Bundle. Does only a shallow copy of the original Bundle.
    Bundle(Int b)
  • Constructs a Bundle containing a copy of the mappings from the given PersistableBundle. Does only a shallow copy of the PersistableBundle.
    Bundle(PersistableBundle b)

五、實戰練習

1. 在Activity to Activity傳遞數據時使用Bundle

① 當傳遞簡單數據時

??新建一個MainActivity,對應的布局文件比較簡單,就是一個Button,點擊這個按鈕后,程序跳轉到SecondActivity,并將傳遞的數據在SecondActivity的TextView中顯示出來。這樣,就使用Bundle實現了數據在Activity之間的傳遞。

package com.example.bundletest;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class MainActivity extends AppCompatActivity {
    //聲明控件對象
    private Button mButton;

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

        //獲取控件的對象
        mButton = findViewById(R.id.button);

        //為Button綁定監聽器
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                /**
                 * 存入數據
                 */
                //實例化一個Bundle
                Bundle bundle = new Bundle();
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);

                //設置數據
                String name = "Trump";
                int num = 123;

                //把數據放入到Bundle容器中
                bundle.putString("Name", name);
                bundle.putInt("Num", num);

                //把Bundle容器中的數據放到Intent中
                intent.putExtra("Message", bundle);

                //啟動該Intent,實現Activity的跳轉
                startActivity(intent);
            }
        });
    }
}

??新建一個SecondActivity,用于顯示傳遞的數據。對應的布局文件也很簡單,就是一個TextView。

package com.example.bundletest;

import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;

public class SecondActivity extends AppCompatActivity {
    //聲明控件對象
    private TextView textView;

    @SuppressLint("SetTextI18n")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        //獲取控件的對象
        textView = findViewById(R.id.text_view);

        /**
         *讀取數據
         */
        Intent intent = getIntent();
        //從Intent中取出Bundle
        Bundle bundle = intent.getBundleExtra("Message");

        //獲取數據
        assert bundle != null;
        String name = bundle.getString("Name");
        int num = bundle.getInt("Num");

        //顯示數據
        textView.setText(name + "\n" + num);
    }
}

??運行程序后,結果如下圖所示:



??點擊Button,結果如下圖所示:


② 當傳遞的參數很多,或者傳遞一個類的對象時

??新建一個JavaBean,將這個類命名為FunPerson,并實現Serializable接口。

package com.example.bundletest;

import java.io.Serializable;

public class FunPerson implements Serializable {
    //創建實例變量
    private String name;
    private int num;

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public int getNum() {
        return num;
    }
}

??修改MainActivity中的代碼:

public class MainActivity extends AppCompatActivity {
    //聲明控件對象
    private Button mButton;

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

        //獲取控件的對象
        mButton = findViewById(R.id.button);

        //為Button綁定監聽器
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                /**
                 * 存入數據
                 */
                FunPerson person = new FunPerson();
                //設置數據
                String name = "Trump is fun";
                int num = 12345;
                person.setName("name");
                person.setNum(num);

                //實例化一個Bundle
                Bundle bundle = new Bundle();
                //把FunPerson數據放入到Bundle容器中
                bundle.putSerializable("Person", person);

                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                //把Bundle容器中的數據放到Intent中
                intent.putExtras(bundle);

                //啟動該Intent,實現Activity的跳轉
                startActivity(intent);
            }
        });
    }
}

??修改SecondActivity中的代碼:

public class SecondActivity extends AppCompatActivity {
    //聲明控件對象
    private TextView textView;

    @SuppressLint("SetTextI18n")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_second);

        //獲取控件的對象
        textView = findViewById(R.id.text_view);

        /**
         *讀取數據
         */
        Intent intent = getIntent();
        //從Intent中取出Bundle
        Bundle bundle = intent.getExtras();

        //獲取FunPerson里的數據數據
        assert bundle != null;
        FunPerson person = (FunPerson)bundle.getSerializable("Person");

        assert person != null;
        String name = person.getName();
        int num = person.getNum();

        //顯示數據
        textView.setText(name + "\n" + num);
    }
}

??看下運行后的結果:


2. 在Activity to Fragment傳遞數據時使用Bundle

??Activity重新創建時,會重新構建它所管理的Fragment,原先的Fragment的字段值將會全部丟失,但是通過Fragment.setArguments(Bundle bundle)方法設置的bundle會保留下來。所以盡量使用Fragment.setArguments(Bundle bundle)方式來傳遞參數。
??有兩種實現方案:

① 方法一:使用Fragment的靜態方法newInstance()來傳遞數據

??新建MainActivity:

public class MainActivity extends AppCompatActivity {

    private Button mButton;

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

        mButton = findViewById(R.id.button);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //發送數據
                BlankFragment blankFragment =  BlankFragment.newInstance("Message_1 To Fragment", "Message_2 To Fragment");
                FragmentManager fragmentManager = getSupportFragmentManager();
                FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
                //FrameLayout用于動態更新fragment
                fragmentTransaction.replace(R.id.frame_layout, blankFragment);
                fragmentTransaction.commit();
            }
        });
    }
}

??MainActivity的布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:layout_marginTop="8dp"
        android:text="SendMsg"
        android:textAllCaps="false"
        app:layout_constraintBottom_toTopOf="@+id/guide_line"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.constraintlayout.widget.Guideline
        android:id="@+id/guide_line"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_begin="255dp" />
    
    <FrameLayout
        android:id="@+id/frame_layout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/guide_line"
        app:layout_constraintVertical_bias="0.0" >
    </FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

??新建一個Fragment:

public class BlankFragment extends Fragment {
    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;


    public static BlankFragment newInstance(String param1, String param2) {
        BlankFragment fragment = new BlankFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View view = inflater.inflate(R.layout.fragment_blank, container, false);

        TextView textView = view.findViewById(R.id.text_view);
        //Fragment獲取數據
        Bundle bundle = getArguments();
        String message = null;
        if (bundle != null) {
            message = bundle.getString(ARG_PARAM1);
        }
        textView.setText(message);
        return view;
    }
}

??BlankFragment的布局文件比較簡單,就是一個顯示用的TextView。
??運行程序,點擊Button,結果如下圖bundle4所示:


② 方法二:

??修改MainActivity的代碼:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button mButton = findViewById(R.id.button);
        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //發送數據
                ToFragment fragment = new ToFragment();
                //新建一個Bundle實例
                Bundle bundle = new Bundle();
                bundle.putString("data", "From Activity To Fragment");
                //將數據傳遞到Fragment
                fragment.setArguments(bundle);
                FragmentManager fragmentManager = getSupportFragmentManager();
                FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
                //FrameLayout用于動態更新fragment
                fragmentTransaction.replace(R.id.frame_layout, fragment);
                fragmentTransaction.commit();
            }
        });
    }
}

??新建一個碎片ToFragment,簡單起見,就不給新建的碎片弄一個布局文件,直接使用BlankFragment的布局文件,節省時間:

public class ToFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        //簡單起見,此處直接使用BlankFragment的布局文件
        View view = inflater.inflate(R.layout.fragment_blank, container, false);

        TextView textView = view.findViewById(R.id.text_view);
        //得到從Activity傳來的數據
        Bundle bundle = this.getArguments();
        String message = null;
        if (bundle != null) {
            message = bundle.getString("data");
        }
        textView.setText(message);
        return view;
    }
}

??運行程序后結果如下所示:


3. 在消息機制的Message中使用setData()傳遞數據時用到Bundle

??這個栗子的思路也很簡單,點擊屏幕,給Activity發送一個Message,傳遞兩個參數,并通過Toast顯示出來,最后finish()掉這個Activity。
??新建一個Activity:

public class MainActivity extends AppCompatActivity {

    final static int FLAG = 1;

    @SuppressLint("HandlerLeak")
    public Handler mHandler = new Handler() {
        @Override
        public void handleMessage(@NonNull Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case FLAG:
                    //獲取Message傳遞過來的數據
                    String data1 = msg.getData().getString("text1");
                    String data2 = msg.getData().getString("text2");
                    init(data1, data2);
                    break;
                default:
                    break;
            }
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this, this));
    }

    public void init(String str1, String str2) {
        //將獲取的數據Toast出來
        Toast.makeText(MainActivity.this, str1 + '\n' + str2, Toast.LENGTH_SHORT).show();
        finish();
    }
}

??在建一個Java類:

@SuppressLint("ViewConstructor")
public class MyView extends View {
    private MainActivity activity;

    public MyView(Context context, MainActivity activity) {
        super(context);
        this.activity = activity;
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int)event.getX();
        int y = (int)event.getY();
        Rect rect = new Rect(0, 0, 320, 480);
        if (rect.contains(x, y)) {
            Message message = new Message();
            message.what = MainActivity.FLAG;
            //新建Bundle的實例
            Bundle bundle = new Bundle();
            //往Bundle中傳入數據
            bundle.putString("text1", "Trump want to ban TimTok");
            bundle.putString("text2", "Make America great again");
            //message利用bundle傳遞數據
            message.setData(bundle);
            //用activity中的handler發送消息
            activity.mHandler.sendMessage(message);
        }
        return super.onTouchEvent(event);
    }
}

??運行程序,得到如下結果:



??點擊屏幕指定區域,得到如下結果:


六、小結

??到此,Bundle的分析基本就結束了,其實Bundle比較簡單,只是一個數據容器,不像Activity等有復雜的生命周期。對于開發者來說,只需要了解Bundle的功能、使用場景并掌握常用的數據存取方法即可。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373