1.前言
初学者看到这里应该感觉到很奇怪,Android的四大组件只有Activity、Service、BroadcastReceiver和ContentProvider并没有Intent,为什么要把Intent放到这里来讨论呢?其实原因很简单Intent是四大组件的枢纽,是Android通信的桥梁,没有Intent我们连最基本的功能都不能完成。
2.Intent是什么?
Intent的中文意思是“意图,意向”,在Android中提供了Intent机制来协助应用间的交互与通讯,Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给调用的组件,并完成组件的调用。Intent不仅可用于应用程序之间,也可用于应用程序内部的Activity/Service之间的交互。因此,可以将Intent理解为不同组件之间通信的“媒介”专门提供组件互相调用的相关信息。
3.Intent的属性
Intent主要由以下七个属性:Action(动作)、Data(数据)、Type(类型)、Category(类别)、Component(组件)、Extra(扩展)和Flags(标记)。
1)Action(动作)
Action是指Intent将要完成的动作,是一个字符串常量。要注意的时在Java中的Action与在Intent-Filter中的格式是不一样的,比如:Intent.ACTION_CALL(Java中)android.intent.action_CALL(Intent-Filter中)。此外在Android系统中定义了一些标准的Action如下所示:
ACTION_CALL(android.intent.action.CALL) — 执行调用一个指定的数据
ACTION_DIAL(android.intent.action.DIAL) — 直接拨打电话
ACTION_SEND(android.intent.action.SEND) — 直接发送短信
ACTION_SENDTO(android.intent.action.SENDTO) — 选择发送短信
ACTION_EDIT(android.intent.action.EDIT) — 显示可编辑的数据
ACTION_MAIN(android.intent.action.MAIN) — 应用程序入口
ACTION_VIEW(android.intent.action.VIEW) — 显示数据给用户
ACTION_SYNC(android.intent.action.SYNC) — 同步数据
ACTION_BATTERY_LOW(android.intent.action.BATTERY_LOW) — 在设备上显示电池电量过低条件
ACTION_HEADSET_PLUG(android.intent.action.HEADSET_PLUG) — 有线耳机插入或拔出
ACTION_SCREEN_OFF(android.intent.action.SCREEN_OFF) — 屏幕关闭后发送
ACTION_SCREEN_ON(android.intent.action.SCREEN_ON) — 屏幕打开后发送
ACTION_TIMEZONE_CHANGED(android.intent.action.TIMEZONE_CHANGED) — 时区改变
2)Data(数据)
Data通常用于向Action属性提供操作的数据
3)Type(类型)
Type通常用于指定Data所指定的Uri对应的MIME类型
4)Category(类别)
Category用于为Action提供额外的附加类别信息是一个字符串常亮,通常与Action结合使用,一个Intent对象只能有一个Action,但是可以有多个Category
5)Component(组件)
Component就是目标组件的名称,由组件所在应用程序配置文件中设置的包名+组件的全限定类名组成,这是显示Intent并且激发的组件只有一个
6)Extra(扩展)
Extra通常用于多个Action之间的数据交换,Extra的属性是一个Bundle的对象,其中是通过键值对进行数据的存储
7)Flags(标记)
Flags用于对不同来源进行标记,多数用于如何启动Activity以及启动后如何对待,所有的标记都是整数类型
4.Intent寻找目标组件
在Android系统中Intent寻找目标组件通常有两种:显式Intent和隐式Intent
1)显式Intent
一般情况下,一个应用中会包含多个界面也就是多个Activity,并且这些Activity之间可以来回自由的切换,这种Activity之间的切换就是通过Intent机制来实现的,显示Intent则是通过指定固定的Intent组件名称来实现的,一般用在知道目标组件名称的前提下,在同一应用程序的内部实现。显式Intent通常通过两种方法实现startActivityForResult()和startActivity()
2)隐式Intent
隐式Intent通过Intent Filter来实现的,它一般用在没有明确指出目标组件名称的前提下,在不同应用程序之间和相同应用程序内部都可以使用。隐式Intent通过比较Intent对象内容与Intent-filter过滤器来实现的,主要匹配Action(动作)、Data(数据)和Category(类别)这三个属性,而Extra(扩展)和Flags(标记)对于启动哪个组件时并不起任何作用。隐式Intent主要包括预定义动作的隐式Intent(系统提供,在Intent类中已经定义好的)和自定义动作的隐式Intent(开发者根据需求自定义的Action)
3)常用的系统Intent
//1.拨打电话
// 给移动客服10086拨打电话
Uri uri = Uri.parse("tel:10086");
Intent intent = new Intent(Intent.ACTION_DIAL, uri);
startActivity(intent);
//2.发送短信
// 给10086发送内容为“Hello”的短信
Uri uri = Uri.parse("smsto:10086");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
intent.putExtra("sms_body", "Hello");
startActivity(intent);
//3.打开浏览器:
// 打开百度主页
Uri uri = Uri.parse("http://www.baidu.com");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
//4.显示地图:
// 打开Google地图中国北京位置(北纬39.9,东经116.3)
Uri uri = Uri.parse("geo:39.9,116.3");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
//5.路径规划
// 路径规划:从北京某地(北纬39.9,东经116.3)到上海某地(北纬31.2,东经121.4)
Uri uri = Uri.parse("http://maps.google.com/maps?f=d&saddr=39.9 116.3&daddr=31.2 121.4");
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
startActivity(intent);
//6.打开录音机
Intent mi = new Intent(Media.RECORD_SOUND_ACTION);
startActivity(mi);
//7.打开摄像头拍照:
// 打开拍照程序
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, 0);
// 取出照片数据
Bundle extras = intent.getExtras();
Bitmap bitmap = (Bitmap) extras.get("data");
//8.进入联系人页面:
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(People.CONTENT_URI);
startActivity(intent);
//9.查看指定联系人:
Uri personUri = ContentUris.withAppendedId(People.CONTENT_URI, info.id);//info.id联系人ID
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(personUri);
startActivity(intent);
5.Intent传递数据
1)传递简单数据
- 一次传递一个
//存一个数据
Intent intent = new Intent(A.this, B.class);
intent.putExtra("key", "value");
startActivity(intent);
//取一个数据
Intent intent = getIntent();
String Value = intent.getStringExtra("key");
- 一次传递多个
//存多个简单数据
Intent intent = new Intent(A.this, B.class);
Bundle bundle = new Bundle();
bundle.putInt("key1", "value1");
bundle.putString("key2", "value2");
intent.putExtras(bundle);
startActivity(intent);
//取多个简单数据
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
int Value1 = bundle.getInt("key1");
String Value2 = bundle.getString("key2");
从上面的代码中可以看出在存数据的时候可以直接通过调用Intent的putExtra()方法存储数据,取数据一端在获得Intent后调用getXxxExtra()方法获取对应类型的数据。在传递多个数据的情况下可以使用Bundle对象作为容器,使用Bundle对象的putXxx将数据储存在Bundle中,然后调用Intent的PutExtras()方法将Bundle存入Intent中进行传递,取数据的一端在在获得Intent后调用getExtras()方法取得Bundle容器,然后调用Bundle的getXxx获取对应类型的数据。
2)传递数组
//存数组数据
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putStringArray("StringArray", new String[]{"value1", "value2"});
intent.putExtras(bundle);
startActivity(intent);
//取数组数据
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
String[] StringArray = bundle.getStringArray("StringArray");
在存数据和取数据时对应不同的数据类型使用相对应的putXxxArray()方法和getXxxArray()方法
3)传递集合
List<基本数据类型或String>
//存List<String/Integer>数据
Intent intent = new Intent();
intent.putStringArrayListExtra("Stringlist", Stringlist);
intent.putIntegerArrayListExtra("Integerlist", Integerlist);
startActivity(intent);
//取List<String/Integer>数据
Intent intent = getIntent();
Stringlist = intent.getStringArrayListExtra(Stringlist);
Integerlist = intent.getIntegerArrayListExtra(Integerlist);
List<Object>
//存List<Object>数据
Intent intent = new Intent(A.this, B.class);
intent.putExtra("Objlist", (Serializable) Objlist);
startActivity(intent);
//取List<Object>数据
Intent intent = getIntent();
List<Info> listObj = (ArrayList<Info>) getIntent().getSerializableExtra("Objlist");
这里需要注意将List强转成Serializable类型,然后存入(可用Bundle做媒介)4)传递Map<String, Object>
//初始化Map
Map<String, Object> map1 = new HashMap<String, Object>();
map1.put("key1", "value1");
map1.put("key2", "value2");
//初始化List
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
list.add(map1);
Intent intent = new Intent(A.this, B.class);
Bundle bundle = new Bundle();
//须定义一个list用于在budnle中传递需要传递的ArrayList<Object>,这个是必须要的
ArrayList bundlelist = new ArrayList();
bundlelist.add(list);
bundle.putParcelableArrayList("list",bundlelist);
intent.putExtras(bundle);
startActivity(intent);
从上述代码中可以看出,对Map进行传递的解决办法是给Map外层套一个List再进行传递
5)传递Bitmap
Bitmap bitmap = null;
Intent intent = new Intent();
Bundle bundle = new Bundle();
bundle.putParcelable("bitmap", bitmap);
intent.putExtra("bundle", bundle);
由于Bitmap默认实现Parcelable接口,所有可以直接进行传递
6.Intent传递对象
通过Intent传递对象的方法有两种,分别是通过实现Serialization接口或者Parcelable接口将对象进行序列化以后传递和将对象转换为Json字符串进行传递。
1)实现Serialization接口
Serializable 是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。至于序列化的方法也很简单,只需要让一个业务Bean去实现 Serializable 这个接口并且写上get和set方法
public class Person implements Serializable
{
private String name;
private int age;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
}
在实现
Serializable 接口后,需要通过调用Intent的putExtra(String name, Serializable value)存入数据,如果存在有多个数据的话,就要先将数据存入到Bundle容器中(使用Bundle.putSerializable(x,x))再存入Intent中
//存person对象
Person person = new Person();
person.setName("Name");
person.setAge(20);
Intent intent = new Intent(A.this, B.class);
intent.putExtra("person", person);
startActivity(intent);
//取person对象
Intent intent = getIntent();
Person person = (Person)intent.getSerializableExtra("person");
这里调用了 getSerializableExtra()方法来获取通过参数传递过来的序列化对象, 接着再将它向下转型成 Person对象,这样我们就成功实现了使用 Intent来传递对象的功能了
2)实现Parcelable接口
除了使用Serializable之外,使用Parcelable也可以实现相同的效果,不过不同于将对象进行序列化,Parcelable方式的实现原理是将一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现传递对象的功能了
public class Person implements Parcelable
{
private String name;
private int age;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
public int describeContents()
{
return 0;
}
public void writeToParcel(Parcel dest, int flags)
{
dest.writeString(name); // 写出name
dest.writeInt(age); // 写出age
}
public static final Parcelable.Creator<Person> CREATOR = new Parcelable.Creator<Person>()
{
public Person createFromParcel(Parcel source)
{
Person person = new Person();
person.name = source.readString(); // 读取name
person.age = source.readInt(); // 读取age
return person;
}
public Person[] newArray(int size)
{
return new Person[size];
}
};
}
在实现Parcelable接口后就必须重写describeContents()和writeToParcel()这两个方法,其中describeContents()方法直接返回0就可以了,而writeToParcel()方法中我们需要调用Parcel的writeXxx()方法将Person类中的字段一一写出。通过writeToParcel将你的对象映射成Parcel对象,再通过createFromParcel将Parcel对象映射 成你的对象。也可以将Parcel看成是一个流,通过writeToParcel把对象写到流里面, 在通过createFromParcel从流里读取对象,只不过这个过程需要你来实现,因此写的 顺序和读的顺序必须一致。
//存person对象
Intent intent = new Intent(A.this, B.class);
intent.putExtra("person", person);
startActivity(intent);
//取person对象
Intent intent = getIntent();
Person person = (Person)intent.getParcelableExtra("person");
接着使用putExtra()和getParcelableExtra()方法分别存储对象和读取对象
Serialization和Parcelable两种序列化方式相比较:
- 在使用内存的时候,Parcelable比Serializable性能高,所以推荐使用Parcelable。
- Serializable在序列化的时候会产生大量的临时变量,从而引起频繁的GC。
- Parcelable不能使用在要将数据存储在磁盘上的情况,因为Parcelable不能很好的保证数据的 持续性在外界有变化的情况下。尽管Serializable效率低点,但此时还是建议使用Serializable。
3)转换为Json字符串
使用与上面两个例子相同的业务Bean
public class Person
{
private String name;
private int age;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
}
使用
转换为Json字符串的方法去传递对象的本质就是将对象解析成Json字符串,对Json字符串进行传递,接收时也是接收的Json字符串,再将Json字符串解析成对象。在对Json进行解析的过程中不建议使用Android内置的解析器,可以使用GSON或者fastjson等第三方解析框架。
//存person对象
Person person = new Person();
person.setName("Name");
person.setAge(20);
Intent intent = new Intent(A.this, B.class);
intent.putExtra("person", new Gson().toJson(person));
startActivity(intent);
//取person对象
Intent intent = getIntent();
String personJson = intent.getStringExtra("person");
Person person = new Gson().fromJson(personJson, Person.class);
7.使用Application类定义全局数据
如果需要传递数据并且从Activity1获取数据,Activity4种要用到这些数据,在进行数据传递的过程中通常的做法是Activity1 —> Activity2 —> Activity3 —> Activity4一个个页面进行传递但是这样的做法显然比较繁琐,在这中情况下就可以考虑使用Application全局对象了。
Android系统在每个应用运行时都会先创建一个Application的对象并且只会创建一个,所以Application又是一个单例模式(singleton)的类,Application对象的生命周期又是整个应用中最长的一个组件,可以说Application对象的生命周期就相当于整个应用的生命周期。因为它是全局唯一的,所以在不同的Activity,Service中获得的对象都是同一个对象,所以可以通过 Application 来进行一些:数据传递、数据共享、数据缓存等操作。
Application也是系统的一个组件,他自己也有一个生命周期
public class App extends Application
{
public void onCreate()
{
// 程序创建的时候执行
super.onCreate();
}
public void onTerminate()
{
// 程序终止的时候执行
super.onTerminate();
}
public void onLowMemory()
{
// 低内存的时候执行
super.onLowMemory();
}
public void onTrimMemory(int level)
{
// 程序在内存清理的时候执行
super.onTrimMemory(level);
}
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
}
}
—public void onCreate()
在应用程序创建的时候被调用,可以实现这个这个方法来创建和实例化任何应用程序状态变量或共享资源。还可以在这个方法里面得到Application的单例。
—public void onTerminate()
当终止应用程序对象时调用,不保证一定被调用,当程序是被内核终止以便为其他应用程序释放资源,那么将不会提醒,并且不调用应用程序的对象的onTerminate方法而直接终止进程。
—public void onLowMemory()
当系统资源匮乏的时候,我们可以在这里可以释放额外的内存, 这个方法一般只会在后台进程已经结束,但前台应用程序还是缺少内存时调用。可以重写这个方法来清空缓存或者释放不必要的资源。
—public void onTrimMemory(int level)
当运行时决定当前应用程序应该减少其内存开销时(通常在进入后台运行的时候)调用,包含一个level参数,用于提供请求的上下文。
—public void onConfigurationChanged (Configuration newConfig)
与Activity不同,配置改变时,应用程序对象不会被终止和重启。如果应用程序使用的值依赖于特定的配置,则重写这个方法来加载这些值,或者在应用程序级处理配置值的改变。
使用方法:
1)自定义类并且继承自Application类
class MyApplication extends Application
{
private String name;
private int age;
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
}
2)在AndroidManifest.xml中进行注册
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.administrator.intentdemo">
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
3)调取MyApplication使用
class MainActivity extends Activity
{
public void onCreate(Bundle savedInstanceState)
{
MyApplication myApplication = ((MyApplication)getApplicationContext());
String name = myApplication.getName();
int age = myApplication.getAge();
}
}
8.总结
使用Application类虽然解决了用Intent在每个Activity之间传递数据的繁琐性,但是在使用Application的过程中不一定是安全的,因为Application的对象是存在与内存中的,既然存在于内存中当系统内存紧张的时候就有可能被杀死,当Application被系统杀掉了以后,我们再进入Activity中是可以正常进入的,此时因为程序的启动会创建一个新的Application,这里的Application和被杀掉的Application不是同一个对象,如果这个时候再去使用get方法获取Application中的变量就会报出NullPointerException的异常,如果没有对这个异常进行抓取就会crash掉。所以如果要存入比较重要的数据还是建议使用数据库进行本地化处理,另外在使用变量的过程中最好对变量进行判空。此外不仅仅是Application变量会这样,单例对象以及公共静态变量也会这样。