关于Activity你必须要知道的一切
前言:Activity是Android四大组件之一,为用户提供与系统交互的界面,每一个应用都有一个或者多个Acticity,这样会有各种各样的细节问题需要查找,我将本人接触到的知识点汇总到此篇文章。
1.Activity简单介绍
一个Activity是一个应用程序组件,提供一个屏幕,用户可以用来交互为了完成某项任务,例如拨号、拍照、发送email、看地图。每一个activity被给予一个窗口,在上面可以绘制用户交互的画面。窗口通常充满屏幕,但也可以小于屏幕而浮于其它窗口之上。一个用户交互界面对应一个activity
,相当于是界面的容器setContentView()
,activity 是Context的子类,同时实现了window.callback
接口(里面方法如dispatchtouchevent
可以分发事件)和keyevent.callback
等, 可以处理与窗体用户交互的事件。
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {}
2.Activity的创建
1.创建步骤:
- 创建class类继承Activity
- 创建布局文件,作为Activity的显示内容
- 在清单文件中注册Activity
2.注意点:
- 需要在清单文件中为其配置一个activity标签,声明你的activity在manifest文件为了它可以被系统访问。否则如果系统找不到,在显示时会直接产生
ActivityNotFoundException
。要声明你的activity,打开manifest
文件,添加一个activity元素作为application
元素的子元素。 - Activity 在调用的时候才会实例化,如果manifest没有声明这个activity,不使用则不会报错。
- 如果Activity所在的包跟应用包名同名,那么可以省略不写。而用.xxxActivity来代替。
- 标签中如果带有这个子节点,则会在系统中创建一个快捷图标
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
- 声明入口activity,就会生成快捷图标,可以声明多个入口activity,会产生多个Activity对应的快捷图标。
- activity的名称label、图标icon可以和应用程序application节点的名称、图标不相同,但默认使用application节点下的属性。
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
3.Activity的的跳转
Activity的跳转需要创建Intent对象,通过设置intent对象的参数指定要跳转的activity。通过设置Activity的包名和类名实现跳转,称为显式意图。
通过指定动作实现跳转,称为隐式意图。
1. 显式意图
- 跳转至同一项目下的另一个Activity,直接指定该Activity的字节码即可
Intent intent = new Intent();
intent.setClass(this, SecondActivity.class);
startActivity(intent);
- 跳转至其他应用中的Activity,需要指定该应用的包名和该Activity的类名
Intent intent = new Intent();
//启动系统自带的拨号器应用
intent.setClassName("com.android.dialer","com.android.dialer.DialtactsActivity");
startActivity(intent);
2. 隐式意图
- 隐式意图跳转至指定Activity
Intent intent = new Intent();
//启动系统自带的拨号器应用
intent.setAction(Intent.ACTION_DIAL);
startActivity(intent);
- 要让一个Activity可以被隐式启动,需要在清单文件的activity节点中设置intent-filter子节点
- action 指定动作(可以自定义,可以使用系统自带的,可以使任意字符串,但一般写全类名)
- data 指定数据(操作什么内容,包括URI和数据类型)
- category 类别 (默认类别)
<intent-filter >
<action android:name="com.chenqiao.second"/>
<data android:scheme="chenqiao" android:mimeType="aa/bb"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
- 隐式意图的属性设置
//[1]创建意图对象 意图就是我要完成一件事
Intent intent = new Intent();
//[2] 设置跳转的动作
intent.setAction("com.chenqiao.testactivity");
//[3] 设置category
intent.addCategory("android.intent.category.DEFAULT");
//[4]设置数据
// intent.setData(Uri.parse("chenqiao:"+110));
//[5]设置数据类型
//intent.setType("aa/bb");
//[6]注意 如果setdata 方法和 settype 方法一起使用的时候 应该使用下
//面这个方法
intent.setDataAndType(Uri.parse("chenqiao:"+110), "aa/bb");
//[4]开启Activity
startActivity(intent);
- 你要启动的那个Activity的意图过滤器
<intent-filter>
<action android:name="com.itheima.testactivity" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="aa/bb" android:scheme="chenqiao" />
</intent-filter>
- 隐式意图启动Activity,需要为intent设置以上三个属性,且值必须与该Activity在清单文件中对三个属性的定义匹配。intent-filter节点及其子节点都可以同时定义多个,隐式启动时只需与任意一个匹配即可。在启动效率上,隐式远低于显示。
- 如果系统中存在多个Activity的intent-filter同时与你的intent匹配,那么系统会显示一个对话框,列出所有匹配的Activity,由用户选择启动哪一个。显式意图一般用于启动同一应用中的Activity。隐式意图一般用于启动不同应用中的Activity,因为不能拿到另一个应用的activity类名,不能显示启动。
3.意图匹配原则
-
action
行为检测:manifest文件中activity的一个intentfilter元素以子元素的形式列出了行为。例如:
<intent-filter . . . >
<action android:name="com.example.project.SHOW_CURRENT" />
<action android:name="com.example.project.SHOW_RECENT" />
<action android:name="com.example.project.SHOW_PENDING" />
. . .
</intent-filter>
一个Intent对象只命名一个单独的行为,而一个过滤器可以列出多个行为。列表不能为空;一个过滤器至少要包含一个元素,否则它将屏蔽所有的意图。要通过这个检测,在Intent对象中指定的行为必须与过滤器所列出的行为之一相匹配。如果该对象或是过滤器没有指定一个行为,就会有下面的结果:如果过滤器没有列出任何行为,就没有行为可以与意图相匹配,所有的意图都无法通过检测。没有意图可以通过过滤器。另一方面,如果Intent对象没有指定任何的行为,它将自动通过检测——只要过滤器含有一个或以上的行为。
-
category
类别检测:intent-filter也会将类别作为子元素列出。例如:
<intent-filter . . . >
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
. . .
</intent-filter>
注意之前描述过的表示行为和类型的常量不在mainfest文件中被使用。而是使用完整的字符串值。例如,在范例中的“android.intent.category.BROWSABLE”字符串和文档之前提到的CATEGORY_BROWSABLE常量相对应。类似地,字符串“android.intent.action.EDIT”和ACTION_EDIT常量相对应。
一个意图要通过类别检测,Intent对象中的每一个类别都必须与过滤器中的某个类别相匹配。过滤器可以列出其他更多的类别,但不能省略任何一个意图中含有的类别。原则上,所以说,一个不含有类别的Intent对象,无论过滤器中有哪些类别,总是能够通过这项检测。这通常是正确的。不过,有一个例外,Android把所有传递给startActivity()的隐式意图视为它们包含了至少这样一个类别:“android.intent.category.DEFAULT”(CATEGORY_DEFAULT常量)。因此,要接受隐式意图的活动必须在其意图过滤器中包含有“android.intent.category.DEFAULT”。(设有“android.intent.action.MAIN”和“android.intent.category.LAUNCHER”的过滤器不在此范围之中。它们将活动标记为了新任务的开始,并显示在应用启动器(launcher)屏幕上。它们可以在类别列表中包含“android.intent.category.DEFAULT”,不过这并不是必须的)
- data数据检测:如同行为和类别,意图过滤器的数据类型也是作为子元素保存的。而且和它们一样,这些子元素可以出现多次或完全不出现。
<intent-filter . . . >
<data android:mimeType="video/mpeg" android:scheme="http" . . . />
<data android:mimeType="audio/mpeg" android:scheme="http" . . . />
. . .
</intent-filter>
每一个元素可以指定一个URI和数据类型(MINE媒体类型)。URI的每一个部分由不同的属性——模式(scheme)、主机(host)、接口(port)和路径(path)。当一个Intent对象中的URI和过滤器中的URI相比较时,仅比较过滤器中所包含的URI部分。例如,如果一个过滤器仅指定了一个模式,那所有具有这个模式的URI就与过滤器相匹配。如果过滤器指定了一个模式及一个授权但是没有指定路径,那么无论是什么路径,具有相同模式和授权的URI将得到匹配。如果过滤器指定了模式、授权和路径,那就只有具有相同模式、授权和路径的URI得到匹配。不过,过滤器中指定的路径可以包含通配符以仅仅限定部分路径。一个data元素的类型(type)属性指定了数据的MINE类型。在过滤器中这比URI更为常见。Intent对象和过滤器都可以用””通配符作为子类别域来标识子类别匹配,例如“text/”或“audio/*”。
数据检测同时将Intent对象中的URI和数据类型与过滤器中的URI和数据类型相比较。该过程遵循以下规则:
1.不包含URI和数据类型的Intent对象只有在过滤器也不指定任何URI及数据类型时才能通过检测。
2.包含URI但不包含数据类型(且数据类型不能通过URI推断出来)的Intent对象只有在其URI与过滤器中的URI相匹配且过滤器同样没有指定类型时才能通过检测。这是仅仅在类似mailto:和tel:这样没有指向实际数据的URI时才会出现的情况。
3.包含数据类型但不包含URI的Intent对象只有在过滤器列出了相同的数据类型 而没有指定URI时才能通过检测。
4.同时包含有URI和数据类型(或URI可以推导出数据类型)的Intent对象在其数据类型与过滤器中列出的类型相匹配时通过检测的数据类型部分。当其URI与过滤器中的URI相匹配,或,其具有一个content:或file:URI并且过滤器没有指定URI时,通过测试的URI部分。换言之,如果过滤器仅列出了数据类型,一个组件将被假定支持content:和file:数据。
4.Activity跳转时的数据传递
Activity之间的数据传递,常见的有4中,Intent传递简单数据,Bundle传递数据包,传递值对象,获取Activity的返回参数。
1. Intent传递简单数据
- Activity通过Intent启动时,可以通过Intent对象携带数据到目标Activity
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("maleName", maleName);
intent.putExtra("femaleName", femaleName);
startActivity(intent);
- 在目标Activity中取出数据数据
Intent intent = getIntent();
String maleName = intent.getStringExtra("maleName");
String femaleName = intent.getStringExtra("femaleName");
2. Bundle传递数据包
MainActivity:
Intent intent = new Intent(MainActivity.this,OtherActivity.class);
Bundle bundle = new Bundle();
bundle.putString("name","MirGao");
bundle.putString("age","24");
intent.putExtras(bundle);
startActivity(intent);
OtherActivity:
Intent intent = getIntent();
Bundle b = intent.getExtras();
tv.setText(b.getString("name") + " " + b.getString("age"));
3.传递值对象
所谓的值对象,就是我们通常在项目中自定义的,有数据类型的javabean。
那我们就先自定义一个数据类型。
public class UserBean implements Serializable{
private String name;
private String age;
public UserBean(String name, String age) {
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public void setAge(String age) {
this.age = age;
}
public String getName() {
return name;
}
public String getAge() {
return age;
}
}
在这里我们定义了两个都是String类型的参数,并且实现了Serializable接口。使我们的自定义数据类型进行数据序列化。
在MainActivity中直接进行自定义对象的传递,并赋予两个参数:
Intent intent = new Intent(MainActivity.this,OtherActivity.class);
intent.putExtra("UserBean",new UserBean("MirGao","24"));
startActivity(intent);
OtherActivity中获取值对象数据:
UserBean userBean ;
userBean = (UserBean) getIntent().getSerializableExtra("UserBean");
tv.setText(userBean.getName() + " " + userBean.getAge());
使用UserBean对象获取序列化后的对象并进行强制转换,并通过获取的对象,进行操作。使用序列化很简单,方便的可以进行复杂,大量数据的传递。但是,Serializable与Parcelable相比而言 效率比较低 ,所以Android平台又给我们提供了Parcelable,他是一个专门针对移动工具上数据序列化的接口,那么,下面我们就来进行学习。
public class UserBean implements Parcelable{
private String name;
private String age;
public UserBean(String name, String age) {
this.name = name;
this.age = age;
}
protected UserBean(Parcel in) {
name = in.readString();
age = in.readString();
}
public void setName(String name) {
this.name = name;
}
public void setAge(String age) {
this.age = age;
}
public String getName() {
return name;
}
public String getAge() {
return age;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(getName());
dest.writeString(getAge());
}
public static final Creator<UserBean> CREATOR = new Creator<UserBean>() {
@Override
public UserBean createFromParcel(Parcel in) {
return new UserBean(in.readString(),in.readString());
}
@Override
public UserBean[] newArray(int size) {
return new UserBean[size];
}
};
}
我们实现了Parcelable接口,并且实现了3个方法,writeToParcel这个方法中是需要我们去手动读取的,我们把我们要传递的变量保存,以供其他的成员变量或者组件使用。
Other中获取数据:
UserBean userBean = getIntent().getParcelableExtra("UserBean");
tv.setText(userBean.getName() + " " + userBean.getAge());
总结:Serializable和Parcelable都是序列化接口,因为Serializable简单,方便,Android系统对他有自动完成序列化的操作,所以速度是比较慢的。而Parcelable中都是手动去添加数据类型,读取数据,Android系统并没有给我们提供序列化机制,所以Parcelable的数据是相对复杂的,但是速度是比较快的。所以在使用时,注意优缺点选择。
5.启动Activity并获取返回值
从A界面打开B界面, B界面关闭的时候,返回一个数据给A界面:
- 1.开启activity并且获取返回值
//第一个参数为请求码,即调用startActivityForResult()传递过去的值
//第二个参数为结果码,可以根据业务需求自己编号,结果码用于标识返回数据来自哪个新Activity
startActivityForResult(intent, 0);
- 2.在新开启的界面里面实现设置数据的逻辑
Intent data = new Intent();
data.putExtra("phone", phone);
//设置一个结果数据,数据会返回给调用者
setResult(0, data);
finish();//关闭掉当前的activity,才会返回数据
- 3.在开启者activity里面实现方法
//通过data获取返回的数据
onActivityResult(int requestCode, int resultCode, Intent data)
- 4.根据请求码和结果码确定业务逻辑
请求码:用来区分数据来自于哪一个Activity
结果码:用来区分,返回的数据时属于什么类型
5. Activity的生命周期
一张图看清Activity的生命周期图:
Activity的生命周期-
void onCreate():Activity已经被创建完毕,当Activity第一次启动的时候,触发该方法,可以在此时完成Activity的初始化工作。onCreate 方法有一个参数,该参数可以为空( null ),也可以是之前调用onSaveInstanceState()方法保存的状态信息。
-
void onStart():该方法的触发表示所属Activity将被展现给用户。Activity已经显示在屏幕,但没有得到焦点。
-
void onResume():Activity得到焦点,可以与用户交互。
-
void onPause():Activity失去焦点,无法再与用户交互,但依然可见。当一个正在前台运行的Activity因为其他的Activity需要前台运行而转入后台运行的时候,触发该方法。这时候需要将Activity的状态持久化,比如正在编辑的数据库记录等。
-
void onStop():Activity不可见,进入后台。当一个Activity不再需要展示给用户的时候,触发该方法。如果内存紧张,系统会直接结束这个Activity,而不会触发onStop 方法。所以保存状态信息是应该在onPause 时做,而不是onStop 时做。Activity如果没有在前台运行,都将被停止或者Linux 管理进程为了给新的Activity预留足够的存储空间而随时结束这些Activity。因此对于开发者来说,在设计应用程序的时候,必须时刻牢记这一原则。在一些情况下,onPause 方法或许是Activity触发的最后的方法,因此开发者需要在这个时候保存需要保存的信息。
-
void onDestroy():Activity被销毁。当Activity销毁的时候,触发该方法。和onStop 方法一样,如果内存紧张,系统会直接结束这个Activity而不会触发该方法。
·onSaveInstanceState :系统调用该方法,允许Activity保存之前的状态,比如说在一串字符串中的光标所处的位置等。通常情况下,开发者不需要重写覆盖该方法,在默认的实现中,已经提供了自动保存Activity所涉及到的用户界面组件的所有状态信息。 -
void onRestart():当处于停止状态的Activity需要再次展现给用户的时候,触发该方法,即从不可见变成可见时会执行此方法
-
完整生命周期(entire lifetime)
onCreate-->onStart-->onResume-->onPause-->onStop-->onDestory可视生命周期(visible lifetime)
onStart-->onResume-->onPause-->onStop前台生命周期(foreground lifetime)
onResume-->onPause
6.Activity数据的保存
场景:当Activity由于系统配置等发生改变,会导致Activity被杀死而重新创建。即会调用onDestroy销毁Activity,再重新onCreate开启新Activity,系统通过调用onSaveInstanceState和onRestoreInstanceState分别保存和恢复视图(View)状态。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//这里,当Acivity第一次被创建的时候为空
//所以我们需要判断一下
if( savedInstanceState != null ){
savedInstanceState.getString("anAnt");
}
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("anAnt","Android");
}
onSaveInstanceState
onSaveInstanceState在onStop之前调用,但不一定在onPause之前或者之后执行。onRestoreInstanceState在onStart之后调用。需要注意的是,onSaveInstanceState方法只会在Activity被异常终止,在Activity即将被销毁且有机会重新显示的情况下才会调用。具体有以下几种情形:
1、当用户按下HOME键时。
这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,故系统会调用onSaveInstanceState,让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则。
2、长按HOME键,选择运行其他的程序时。
3、按下电源按键(关闭屏幕显示)时。
4、从activity A中启动一个新的activity时。
5、屏幕方向切换时,例如从竖屏切换到横屏时。(如果不指定configchange属性) 在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState一定会被执行。
总而言之,onSaveInstanceState的调用遵循一个重要原则,即当系统“未经你许可”时销毁了你的activity,则onSaveInstanceState会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。此外,由于默认的onSaveInstanceState()方法的实现帮助UI存储它的状态,所以如果你需要覆盖这个方法去存储额外的状态信息时,你应该在执行任何代码之前都调用父类的onSaveInstanceState()方法(super.onSaveInstanceState())。 既然有现成的可用,那么我们到底还要不要自己实现onSaveInstanceState()?这得看情况了,如果你自己的派生类中有变量影响到UI,或你程序的行为,当然就要把这个变量也保存了,那么就需要自己实现,否则就不需要。
onRestoreInstanceState
onRestoreInstanceState方法在activity确定被销毁
以后,重建activity时调用,注意是确定activity被销毁,例如,当正在显示activity A的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行。
另外,onRestoreInstanceState的bundle参数也会传递到onCreate方法中,你也可以选择在onCreate方法中做数据还原。 还有onRestoreInstanceState在onstart之后执行。
至于这两个函数的使用,给出示范代码(留意自定义代码在调用super的前或后):
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putBoolean("MyBoolean", true);
savedInstanceState.putDouble("myDouble", 1.9);
savedInstanceState.putInt("MyInt", 1);
savedInstanceState.putString("MyString", "Welcome back to Android");
// etc.
super.onSaveInstanceState(savedInstanceState);
}
@Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
boolean myBoolean = savedInstanceState.getBoolean("MyBoolean");
double myDouble = savedInstanceState.getDouble("myDouble");
int myInt = savedInstanceState.getInt("MyInt");
String myString = savedInstanceState.getString("MyString");
}
7. Activity的启动模式
一个应用一般包含很多Activity,它们按照各自打开的顺序排列在返回栈(Back Stack)中,这些Activity统称为Task。返回栈中的Activity永远不会重新排列,遵循先进后出的原则。
通过在AndroidManifest.xml配置Activity的启动模式。
<activity
android:name=".aidldemo.BindingActivity"
android:launchMode="standard"
... />
在代码中向Intent添加相应标志。
Intent intent = new Intent(this, MyActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
-
standard
标准启动模式
默认的启动模式,新启动的Activity放入返回栈栈顶,遵循先进后出原则,同一个Activity可以被实例化多次。 -
singleTop
单一顶部模式
如果任务栈的栈顶存在这个要开启的activity,不会重新的创建activity,而是复用已经存在的activity。保证栈顶如果存在,不会重复创建。
应用场景:浏览器的书签 -
singeTask
单一实例模式
当开启activity的时候,就去检查在任务栈里面是否有实例已经存在,如果有实例存在就复用这个已经存在的activity,并且把这个activity上面的所有的别的activity都清空,复用这个已经存在的activity。保证整个任务栈里面只有一个实例存在.
应用场景:浏览器的activity。从不同地方打开浏览器使用的都是同一个实例。
如果一个activity的创建需要占用大量的系统资源(cpu,内存)一般配置这个activity为singletask的启动模式。 -
singleInstance
单一任务栈模式
singleInstance启动模式非常特殊, activity会运行在自己的任务栈里面,并且这个任务栈里面只有一个实例存在。如果你要保证一个activity在整个手机操作系统里面只有一个实例存在,使用singleInstance
应用场景: 电话拨打界面。
8.结束语
以上就是我在学习的过程当中遇到的关于Activity的有关问题总结, 个人眼界有限, 可能还不是很全面, 我会在以后的学习过程当中不断补充。纯粹为了自己温故而知新,不断的学习。┭┮﹏┭┮