欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android Activity之间的数据传递方法总结

程序员文章站 2023-08-15 23:05:37
前言 在activity间传递的数据一般比较简单,但是有时候实际开发中也会传一些比较复杂的数据,本节一起来学习更多activity间数据的传递方法。 1、通过 in...

前言

在activity间传递的数据一般比较简单,但是有时候实际开发中也会传一些比较复杂的数据,本节一起来学习更多activity间数据的传递方法。

1、通过 intent 传递

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

1.1、基本数据类型传递

string 不是基本数据类型,java 的基本数据类型有且仅有8种,intent 都做了很好的支持。这8种基本类型都有自己的包装类型(wrap class,复杂类型),而且包装类型也实现了 serializable 接口(后面再说),使得 intent 也能很好的支持包装类型。
8种基本类型及其包装类对应关系如下:

Android Activity之间的数据传递方法总结

容我煮个栗子:

假设有 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);

1.2、复杂数据类型传递

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.3、自定义数据类型传递

上面已经列举了很多 intent 支持的类型,但是默认提供的这些类型,总归是不够用的,很多时候我们会定义自己的数据类型,比如定义一个 student:

public class student{
 public string name;
 public int age;
}

那么这个时候我们应该如何通过intent来传递呢?

1.3.1、实现 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.2、实现 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 去取。

2、通过全局变量传递

顾名思义,就是借助一个全局变量做中转,去传递数据。还是以前面的两个 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 问题的。

因此,如果要使用全局变量来作为数据传递方法,那么就一定要注意维护好这些全局变量的状态。

3、通过 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<string>是一个 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 {

4、通过 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 开发者无法进行的。有兴趣的可以自己去查资料,这里不做介绍。

5、通过 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 没戏。

6、通过数据库传递

其实上面介绍的 settingsprovider 方法,也是通过数据库实现的,只不过它对数据库的操作做了封装,我们感觉不到而已。既然如此,我们也可以在自己 app 中创建数据库,然后通过数据库来实现 activity 之间的数据传递。
栗子煮太多,吃不动,不煮了,有兴趣的可以自己去查一下数据库的知识。

ps:实话实说吧,个人用的不多,忘了怎么玩了……

7、通过文件传递

前面提到的 sharedpreferences 也是基于文件实现的,只不过 sharedpreferences 是固定成 xml 格式的文件。我们也可以通过自定义文件操作方式去实现数据的存取,进而实现 activity 之间的数据传递。
说了栗子不煮了,有兴趣自己去查一下吧。

ps:原因同上一条……

总结

其实 activity 之间数据传递的方法还是很多的,也各有优缺点,但最最最最最常用的还是第一种—— intent,其他方法都是理论可行,实际使用起来都会有点鸡肋,或者得不偿失。

因此要想掌握好 activity 之间数据传递的技巧,个人觉得只需要掌握 intent 的用法,能熟练使用,灵活处理就 ok 了。至于其它方法,能说得出来原理就可以了。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对的支持。