在同一個地方跌倒兩次,才能體會到“好記性不如爛筆頭”!
一、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的功能、使用場景并掌握常用的數據存取方法即可。