Activity之間的數據傳遞方法彙總

在Activity間傳遞的數據通常比較簡單,可是有時候實際開發中也會傳一些比較複雜的數據,本節一塊兒來學習更多Activity間數據的傳遞方法。java

一、經過 Intent 傳遞

咱們在進行 Activity 跳轉時,是要有 Intent,此時 Intent 是能夠攜帶數據的,咱們能夠利用它將數據傳遞給其它Activity。Intent 應該是系統提供的支持類型最廣,功能最全面的傳遞方式了。基本數據類型、複雜數據類型(如數組、集合)、自定義數據類型等等都能支持,並且使用起來也不復雜。下面將經過幾個小栗子分別介紹一下這幾種方法。android

1.一、基本數據類型傳遞

String 不是基本數據類型,Java 的基本數據類型有且僅有8種,Intent 都作了很好的支持。這8種基本類型都有本身的包裝類型(Wrap Class,複雜類型),並且包裝類型也實現了 Serializable 接口(後面再說),使得 Intent 也能很好的支持包裝類型。數據庫

8種基本類型及其包裝類對應關係以下:小程序

容我煮個栗子:微信小程序

假設有 Activity1,Activity2 兩個 Activity;若是要在 Activity1 中啓動 Activity2,並傳過去幾個基本類型的數據,就能夠這麼寫:數組

Intent intent = new Intent(this, Activity2.class);
intent.putExtra(String name, boolean value);
intent.putExtra(String name, byte value);
intent.putExtra(String name, char value);
intent.putExtra(String name, short value);
intent.putExtra(String name, int value);
intent.putExtra(String name, float value);
intent.putExtra(String name, long value);
intent.putExtra(String name, double value);
startActivity(intent);
複製代碼

在 Activity2 的 onCreate 中就能夠經過以下方式接收:性能優化

Intent intent = getIntent();
boolean bool = intent.getBooleanExtra(String name, boolean defaultValue);
byte bt = intent.getByteExtra(String name, byte defaultValue);
char ch = intent.getCharExtra(String name, char defaultValue);
short sh = intent.getShortExtra(String name, short defaultValue);
int i = intent.getIntExtra(String name, int defaultValue);
float fl = intent.getFloatExtra(String name, float defaultValue);
long lg = intent.getLongExtra(String name, long defaultValue);
double db = intent.getDoubleExtra(String name, double defaultValue);
複製代碼

PS:上面發送和接收的時候,同一個字段必須使用相同的 name,好比:intent.putExtra("BOOLEAN", true);intent.getBooleanExtra("BOOLEAN", false);bash

1.二、複雜數據類型傳遞

Java 中也定義了一些經常使用的複雜類型,好比 String、基本數據類型的數組、ArrayList、HashMap 等等,Intent 也對它們作了支持,使得咱們能很容易的經過 Intent 傳遞這些複雜類型。方法與上面基本類型相似,好比:微信

intent.putExtra(String name, String value);
intent.putExtra(String name, int[] value);
intent.putExtra(String name, Parcelable value);
intent.putExtra(String name, Serializable value);
intent.putExtra(String name, CharSequence value);
intent.putStringArrayListExtra(String name, ArrayList<String> value);
複製代碼

接收方式也相似,這裏就再也不一一列舉了。數據結構

不過,像 ArrayList、HashMap 這種,自己還能存放複雜類型的數據結構,要想經過 Intent 傳遞,得確保它們內部存放的類型也是能支持序列化和反序列化的。

1.三、自定義數據類型傳遞

上面已經列舉了不少 Intent 支持的類型,可是默認提供的這些類型,總歸是不夠用的,不少時候咱們會定義本身的數據類型,好比定義一個 Student:

public class Student{
 public String name;
 public int age;
}
複製代碼

那麼這個時候咱們應該如何經過Intent來傳遞呢?

1.3.一、實現 Serializable 接口

咱們先看一下默認提供並被 Intent 支持的複雜數據類型的實現方式:

public final class String
 implements java.io.Serializable, Comparable<String>, CharSequence
public class ArrayList<E> extends AbstractList<E>
 implements List<E>, RandomAccess, Cloneable, java.io.Serializable
public class HashMap<K,V>
 extends AbstractMap<K,V>
 implements Map<K,V>, Cloneable, Serializable
複製代碼

咱們能夠看到它們有一個共同的特色,都 implement 了 Serializable 接口。

Serializable 是一個空接口,它沒有定義任何方法,知識用來標記其實現類是支持序列化和反序列化的。

所以當咱們想讓自定義的類型也能經過 Intent 傳遞時,只須要讓該類實現 Serializable 接口便可。

依舊用 Student 來煮個栗子:

public class Student implements Serializable{
 private static final long serialVersionUID = 1L;
 public String name;
 public int age;
}
複製代碼

傳遞方法就是:

intent.putExtra(String name, Serializable value);
intent.getSerializableExtra(String name);
複製代碼

PS:關於 Serializable 還有一些知識點,好比:serialVersionUID、靜態變量序列化、transient 關鍵字、繼承問題等等,這裏就不介紹了,有興趣的能夠自行去查閱。

1.3.二、實現 Parcelable 接口

上面介紹了 Serializable 接口,但 Serializable 是 Java 的實現,Android 下能正常使用,沒毛病,但 Google 以爲 Serializable 在 Android 內存不大性能不強的狀況下的效率不太夠,因而爲 Android 量身定製了一個專用的接口——Parcelable。

仍是用 Student 來煮栗子:

要想實現 Parcelable 接口,只須要先寫好 Student 類和屬性,而後讓 Student 實現Parcelable,再而後根據 AS 的兩步提示:第一步重寫 describeContents 和 writeToParcel,第二步建立 CREATOR 就大功告成了。寫好的類以下:

public class Student implements Parcelable{
 public String name;
 public int age;
 protected Student(Parcel in) {
 name = in.readString();
 age = in.readInt();
 }
 public static final Creator<Student> CREATOR = new Creator<Student>() {
 @Override
 public Student createFromParcel(Parcel in) {
 return new Student(in);
 }
 @Override
 public Student[] newArray(int size) {
 return new Student[size];
 }
 };
 @Override
 public int describeContents() {
 return 0;
 }
 @Override
 public void writeToParcel(Parcel dest, int flags) {
 dest.writeString(name);
 dest.writeInt(age);
 }
}
複製代碼

此時經過 Intent 去傳遞就可使用以下方法:

intent.putExtra(String name, Parcelable value);
intent.getParcelableExtra(String name);
複製代碼

這兩種實現序列化的方法的使用原則:

1)在使用內存的時候,Parcelable 比 Serializable 性能高,因此推薦使用 Parcelable。

2)Serializable 在序列化的時候會產生大量的臨時變量,從而引發頻繁的 GC。

3)Parcelable 不能使用在要將數據存儲在磁盤上的狀況,由於 Parcelable 不能很好的保證數據的持續性在外界有變化的狀況下。儘管 Serializable 效率低點,但此時仍是建議使用 Serializable 。

PS:Intent 還支持經過 Bundle 封裝數據,而後傳遞 Bundle,可是查看 intent.putExtra 的實現,咱們會發現,其實 intent.putExtra 的內部也是維護的一個 Bundle,所以,經過 putExtra 放入的數據,取出時也能夠經過 Bundle 去取。

二、經過全局變量傳遞

顧名思義,就是藉助一個全局變量作中轉,去傳遞數據。仍是之前面的兩個 Activity 爲例,傳遞不支持序列化的 Student 對象。咱們能夠先建立一個工具類,好比:

public class Transmitter {
 public static Student student;
}
複製代碼

那麼傳遞和接收時,就能夠這麼操做:

//傳遞
Student stu = new Student();
Transmitter.student = stu;
Intent intent = new Intent(this, Activity2);
startActivity(intent);
//接收
onCreate(...){
 Student stu = Transmitter.student;
}
複製代碼

能夠看到使用起來很是的方便快捷。

可是,全局變量在 APP 運行期間一直存在,若是經過全局變量存放的數據量比較大,變量個數多;而且在不須要使用後,沒有及時的將全局變量置爲 null,好讓 GC 去回收,那麼是有可能會引起 OOM 問題的。

所以,若是要使用全局變量來做爲數據傳遞方法,那麼就必定要注意維護好這些全局變量的狀態。

三、經過 SharedPreferences 傳遞

SharedPreferences 是 Android 提供的一種實現數據存儲的方式,它能夠將數據以 xml 格式存儲在機器中,一般用來存儲 APP 的設置信息,咱們也能夠用它來實現 Activity 間的數據傳遞。

可是,SharedPreferences 因其特殊的工做方式,只提供了對部分基本類型和 String 的操做,對其它既有複雜類型和自定義類型是不支持的。它所支持的類型只有:

boolean
float
int
long
String
Set<String>
複製代碼

仍舊拿前面的兩個 Activity 煮栗子,要實現它們之間的數據傳遞,只須要如今 Activity1 中,將數據放入 SharedPreferences,以下:

SharedPreferences sp = getSharedPreferences("FILENAME", MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putBoolean(String key, boolean value);
editor.putFloat(String key, float value);
editor.putInt(String key, int value);
editor.putLong(String key, long value);
editor.putString(String key, String value);
editor.putStringSet(String key, Set<String> values);
//editor.commit();
editor.apply();
startActivity(...);
複製代碼

而後在 Activity2 中經過 SharedPreferences 將數據取出來,以下:

SharedPreferences sp = getSharedPreferences("FILENAME", MODE_PRIVATE);
sp.getBoolean(String key, boolean defValue);
sp.getFloat(String key, float defValue);
sp.getInt(String key, int defValue);
sp.getLong(String key, long defValue);
sp.getString(String key, String defValue);
sp.getStringSet(String key, Set<String> defValue);
複製代碼

關於 SharedPreferences 有幾點須要注意:

**一、**getSharedPreferences("FILENAME", MODE_PRIVATE) 是經過 Context 調用的,發送和接收的 FILENAME、MODE_PRIVATE 都要一致。

二、發送時,往 SharedPreferences 存入數據後,須要提交,提交的方式有兩種:commit、apply,這兩個的區別以下:

**commit:**同步操做,當即將修改寫到 Storage,有 boolean 類型返回值。

**apply:**當即刷新 In-memory 中的數據,而後啓動異步任務將修改寫到 Storage,無返回值。

當兩個 apply 同時操做時,後調用 apply 的將會被保存到 Storage 中;當有 apply正在執行時,調用 commit,commit 將被阻塞,直到 apply 執行完。

因 Android framework 已經作好全部的事情,因此當咱們不須要關注提交操做的返回值時,能夠將 commit 無條件替換 apply 使用,並且 AS 也會建議將 commit 替換成 apply。

**三、**SharedPreferences 支持的數據類型都必須是支持序列化操做的,上面提到的 Set是一個 interface,咱們並不能直接實例化,但咱們可使用它的直接或間接實現類,好比:HashSet、TreeSet、LinkedHashSet等等。

咱們查看這幾個的實現,不難發現,它們也都是實現了 Serializable 接口,支持序列化操做的:

public class HashSet<E>
 extends AbstractSet<E>
 implements Set<E>, Cloneable, java.io.Serializable
public class TreeSet<E> extends AbstractSet<E>
 implements NavigableSet<E>, Cloneable, java.io.Serializable
public class LinkedHashSet<E>
 extends HashSet<E>
 implements Set<E>, Cloneable, java.io.Serializable {
複製代碼

四、經過 SystemProperties 傳遞

這個類能夠看作一個維護全局變量的類,只不過這裏的全局變量是系統的,它們的值是 build.prop 文件裏面的內容。咱們先看一下它的定義:

/**
 * Gives access to the system properties store. The system properties
 * store contains a list of string key-value pairs.
 *
 * {@hide}
 */
public class SystemProperties
複製代碼

沒錯,這玩意是個 hide 的類,那就意味着正常狀況下 SDK 裏面是沒有的,AS 裏面也是訪問不到的。不過咱們仍是能夠經過一些手段去訪問到它,好比反射、將源碼的庫導出到 AS 使用、將 APP 放在源碼中編譯等等。

這裏咱們就不關注用什麼手段去訪問它了,咱們重點仍是在利用它進行 Activity 之間的數據傳遞。

假設咱們是在源碼中編譯,仍是用一開始的兩個 Activity 來煮栗子,發送數據時能夠這麼操做:

SystemProperties.set("NAME", "Shawn.XiaFei");
startActivity(...);
複製代碼

接收時就能夠這麼寫:

SystemProperties.get("NAME");
//或者
SystemProperties.get("NAME", "defValue");
複製代碼

是否是很方便呢,不過別激動,咱們看下 set 的實現:

/**
 * Set the value for the given key.
 * @throws IllegalArgumentException if the key exceeds 32 characters
 * @throws IllegalArgumentException if the value exceeds 92 characters
 */
public static void set(String key, String val) {
 if (key.length() > PROP_NAME_MAX) {
 throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
 }
 if (val != null && val.length() > PROP_VALUE_MAX) {
 throw new IllegalArgumentException("val.length > " +
 PROP_VALUE_MAX);
 }
 native_set(key, val);
}
複製代碼

看註釋,沒錯,key 和 val 都限制了長度的!!!固然,32和92字符,在通常狀況下也仍是夠用的。可是下面就要說通常 APP 開發可能沒法完成的事了。

前面說了,這玩意是 SDK 不可見的,並且它維護的是系統的屬性值,系統屬性值 APP 能夠讀,但不能輕易修改。所以上面 set 的時候,若是權限不夠就會報以下錯誤:

Unable to set property "NAME" to "Shawn.XiaFei": connection failed; errno=13 (Permission denied)
type=1400 audit(0.0:167): avc: denied { write } for name="property_service" dev="tmpfs" ino=10696 scontext=u:r:untrusted_app_25:s0:c512,c768 tcontext=u:object_r:property_socket:s0 tclass=sock_file permissive=0
複製代碼

這個錯誤在 Rom 開發中比較常見,解決辦法就是配置相應的 avc 權限,這一操做是通常 APP 開發者沒法進行的。有興趣的能夠本身去查資料,這裏不作介紹。

五、經過 SettingsProvider 傳遞

愛折騰的人可能注意到了 Android 設備上通常都會有這麼一個應用,它的做用是經過數據庫去維護一些系統配置信息。在 Rom 開發中,一般藉助它設置首次開機的默認行爲。

經過它傳遞數據的關鍵在 android.provider.Settings 類,這個類裏面有 3 個經常使用的靜態內部類,分別是:Global、System、Secure,它們分別對應不一樣的權限等級。

煮栗子了:

發送時,這麼寫就能夠了:

/*Settings.System.putInt(ContentResolver cr, String name, int value);
Settings.System.putString(ContentResolver cr, String name, String value);
Settings.System.putFloat(ContentResolver cr, String name, float value);
Settings.System.putLong(ContentResolver cr, String name, long value);*/
Settings.Global.putString(getContentResolver(), "NAME", "Shawn.XiaFei");
startActivity(...);
複製代碼

接收時,就這麼寫:

String name = Settings.Global.getString(getContentResolver(), "NAME");
複製代碼

使用起來也是很簡單滴!不過,使用起來雖然簡單,但也並非那麼容易的。它也是要權限的!!!

若是權限不夠,運行的時候就會報以下錯誤:

java.lang.RuntimeException: Unable to start activity ComponentInfo{xxx.xxx/xxx.xxx.Activity1}: java.lang.SecurityException: Permission denial: writing to settings requires:android.permission.WRITE_SECURE_SETTINGS
 at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2805)
 at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2883)
 at android.app.ActivityThread.-wrap11(Unknown Source:0)
 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1613)
 at android.os.Handler.dispatchMessage(Handler.java:106)
 at android.os.Looper.loop(Looper.java:164)
 at android.app.ActivityThread.main(ActivityThread.java:6523)
 at java.lang.reflect.Method.invoke(Native Method)
 at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:857)
複製代碼

意思很明瞭,得給它 WRITE_SECURE_SETTINGS 的權限,咱們試着在 Manifest 裏面添加一下,結果 AS 標紅了,提示以下:

Permissions with the protection level signature or signatureOrSystem are only granted to system apps. If an app is a regular non-system app, it will never be able to use these permissions.

意思就是說,這個權限只有系統 APP 才能得到,三方 APP 沒戲。

六、經過數據庫傳遞

其實上面介紹的 SettingsProvider 方法,也是經過數據庫實現的,只不過它對數據庫的操做作了封裝,咱們感受不到而已。既然如此,咱們也能夠在本身 APP 中建立數據庫,而後經過數據庫來實現 Activity 之間的數據傳遞。

栗子煮太多,吃不動,不煮了,有興趣的能夠本身去查一下數據庫的知識。

PS:實話實說吧,我的用的很少,忘了怎麼玩了……

七、經過文件傳遞

前面提到的 SharedPreferences 也是基於文件實現的,只不過 SharedPreferences 是固定成 xml 格式的文件。咱們也能夠經過自定義文件操做方式去實現數據的存取,進而實現 Activity 之間的數據傳遞。

說了栗子不煮了,有興趣本身去查一下吧。

PS:緣由同上一條……

總結

其實 Activity 之間數據傳遞的方法仍是不少的,也各有優缺點,但最最最最最經常使用的仍是第一種—— Intent,其餘方法都是理論可行,實際使用起來都會有點雞肋,或者得不償失。

所以要想掌握好 Activity 之間數據傳遞的技巧,我的以爲只須要掌握 Intent 的用法,能熟練使用,靈活處理就 OK 了。至於其它方法,能說得出來原理就能夠了。

本人Java開發4年Android開發5年,按期分享Android高級技術及經驗分享,歡迎你們關注~(分享內容包括不限於高級UI、性能優化、移動架構師、NDK、混合式開發(ReactNative+Weex)微信小程序、Flutter等全方面的Android進階實踐技術;但願能幫助到你們,也節省你們在網上搜索資料的時間來學習,也能夠分享動態給身邊好友一塊兒學習!)

相關文章
相關標籤/搜索