Activity之间的数据传递方法汇总

在Activity间传递的数据通常比较简单,可是有时候实际开发中也会传一些比较复杂的数据,本节一块儿来学习更多Activity间数据的传递方法。java

一、经过 Intent 传递

咱们在进行 Activity 跳转时,是要有 Intent,此时 Intent 是能够携带数据的,咱们能够利用它将数据传递给其它Activity。Intent 应该是系统提供的支持类型最广,功能最全面的传递方式了。基本数据类型、复杂数据类型(如数组、集合)、自定义数据类型等等都能支持,并且使用起来也不复杂。下面将经过几个小栗子分别介绍一下这几种方法。android

1.一、基本数据类型传递

String 不是基本数据类型,Java 的基本数据类型有且仅有8种,Intent 都作了很好的支持。这8种基本类型都有本身的包装类型(Wrap Class,复杂类型),并且包装类型也实现了 Serializable 接口(后面再说),使得 Intent 也能很好的支持包装类型。数据库

8种基本类型及其包装类对应关系以下:数组

容我煮个栗子:bash

假设有 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 中就能够经过以下方式接收:app

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);dom

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);
复制代码

接收方式也相似,这里就再也不一一列举了。socket

不过,像 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 有几点须要注意:

1、getSharedPreferences("FILENAME", MODE_PRIVATE) 是经过 Context 调用的,发送和接收的 FILENAME、MODE_PRIVATE 都要一致。

2、发送时,往 SharedPreferences 存入数据后,须要提交,提交的方式有两种:commit、apply,这两个的区别以下:

commit:同步操做,当即将修改写到 Storage,有 boolean 类型返回值。

apply:当即刷新 In-memory 中的数据,而后启动异步任务将修改写到 Storage,无返回值。

当两个 apply 同时操做时,后调用 apply 的将会被保存到 Storage 中;当有 apply正在执行时,调用 commit,commit 将被阻塞,直到 apply 执行完。

因 Android framework 已经作好全部的事情,因此当咱们不须要关注提交操做的返回值时,能够将 commit 无条件替换 apply 使用,并且 AS 也会建议将 commit 替换成 apply。

3、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 了。至于其它方法,能说得出来原理就能够了。

最后

若是你看到了这里,以为文章写得不错就给个赞呗?若是你以为那里值得改进的,请给我留言。必定会认真查询,修正不足。谢谢。

相关文章
相关标签/搜索