【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阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351