Android基础第八天
Activity的跳转
显示意图 | 隐式意图 | |
跳转实现 | 通过字节码,或者包名+类名的方法 | 需要知道另外一个Activity 在AndroidManifest.xml 中配置的intent-filter 中的action 和category |
是否可以通过其他应用开启界面 | 不可 | 可以 |
效率 | 用于激活自己内部组件,效率高 | 用于激活别的应用程序的界面,或者将自己的应用程序暴露给别的应用程序调用,效率低(需要遍历清单文件) |
一个Android 应用通常有多个界面,也就说有多个Activity,那么如何从一个Activity 跳转到另外一个Activity 呢?以及跳转的时候如何携带数据呢?
Activity 之间的跳转分为2 种:
l 显式跳转:在可以引用到另外一个Activity的字节码,或者包名和类名的时候,通过字节码,或者包名+类名的方法实现的跳转叫做显示跳转。显示跳转多用于自己工程内部多个Activity 之间的跳转,因为在自己工程内部可以很方便地获取到另外一个Activity 的字节码。
l 隐式跳转:隐式跳转不需要引用到另外一个Activity 的字节码,或者包名+类名,只需要知道另外一个Activity 在AndroidManifest.xml 中配置的intent-filter 中的action 和category 即可。言外之意,如果你想让你的Activity 可以被隐式意图的形式启动起来,那么就必须为该Activity 配置intent-filter。
Activity 之间的跳转都是通过Intent进行的。Intent 即意图,不仅用于描述一个Activity的信息,同时也是一个数据的载体。
开启界面的两种方式
Ø 显示意图的用法:
1. 通过Intent 的带参构造函数
Intent intent = new Intent(this, SecondActivity.class);
2. 通过Intent 的setClass 方法
intent.setClass(this,SecondActivity.class);
Intent 可以携带的数据类型
1. 八种基本数据类型boolean、byte、char、short、int、float、double、long 和String 以及这9 种数据类型的数组形式
2. 实现了Serializable 接口的对象
3. 实现了Android 的Parcelable 接口的对象以及其数组对象
Ø 隐式意图的用法
1. 要跳转的activity在清单文件里增在intent-filter
<intent-filter>
<action android:name="自己定义,习惯用包名后加功能名"/>
<categoryandroid:name="android.intent.category.DEFAULT"/> //默认
</intent-filter>
2. 谁要跳转到这个activity,谁的方法里面调用
Intentintent = new Intent();
intent.setAction("要跳转的activity在清单文件里配置的action");
intent.addCategory("android.intent.category.DEFAULT");-->默认
startActivity(intent);
Ø 隐示意图需要注意的地方
在清单文件的 intent-filter 里面还可以配置 data标签,data标签可以配置多个不同种类型的
例如:
<dataandroid:scheme="自己定义"/> -->设置前缀,与电话播放器调用很像
<dataandroid:mimeType="text/plain"/> -->定义类型,这里不能随意定义
在java代码里,如果同时配置了scheme和mineType:
intent.setDataAndType(scheme,mimeType);
如果只配置scheme:
intent.setData();
如果只配置了mimeType:
intent.setType();
案例-短信助手
通过该案例可以让我们学会多界面的开发,数据的传递,startActivityForResult的用法。
需求分析:
主界面是一个可以让用户输入联系人号码和短信内容的界面,然后点击按钮发送出去:
右上角点击+号按钮进入选择联系人界面:
选中某一个号码后关闭该界面并将选中的数据返回给主界面显示。
选择模板短信按钮点击进入短信模板列表界面:
选中某一条短信后关闭该界面并将选中的短信数据返回给主界面展示。
代码实现
1. 主界面布局XML:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:layout_marginTop="13dip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入收件人号码"
android:inputType="phone"/>
<Button
android:onClick="selectContact"
android:layout_width="wrap_content"
android:layout_alignParentRight="true"
android:layout_height="wrap_content"
android:text="+"
/>
</RelativeLayout>
<EditText
android:id="@+id/et_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="请输入短信的内容"
android:inputType="textMultiLine"
android:lines="5"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="selectSmsContent"
android:text="选择模板短信" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发送短信" />
</LinearLayout>
2. 主界面代码实现
publicclass MainActivity extends Activity {
/**收件人号码*/
private EditText et_number;
/**短信内容*/
private EditText et_message;
/**选择短信模板的请求码*/
publicstaticfinalintSELECT_SMS = 1;
/**选择联系人的请求码*/
publicstaticfinalintSELECT_CONTACT = 2;
@Override
protectedvoid onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
et_message = (EditText) findViewById(R.id.et_message);
et_number = (EditText) findViewById(R.id.et_number);
}
publicvoid selectSmsContent(View view) {
Intent intent = new Intent(this, SmsListActivity.class);
// startActivity(intent);
// 开启新的Activity并且获取返回值
startActivityForResult(intent,SELECT_SMS);
}
// 开启新的界面选择联系人
publicvoid selectContact(View view) {
Intent intent = new Intent(this, ContactListActivity.class);
// startActivity(intent);
// 开启新的Activity并且获取返回值
startActivityForResult(intent,SELECT_CONTACT);
}
@Override
protectedvoid onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == SELECT_SMS) {
System.out.println("我们开启的新的选择短信的界面被关闭了,结果数据返回到这里");
if (data != null) {
String message = data.getStringExtra("message");
et_message.setText(message);
}
}elseif(requestCode == SELECT_CONTACT) {
System.out.println("我们开启的新的选择联系人界面被关闭了,结果数据返回到这里");
if(data!=null){
String number =data.getStringExtra("number");
et_number.setText(number);
}
}
}
}
选中联系人界面XML
<?xml version="1.0"encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/lv_contact"
android:layout_width="match_parent"
android:layout_height="match_parent">
</ListView>
</LinearLayout>
3. 选中联系人界面代码实现
/**
* 联系人选择界面
*/
publicclassContactListActivityextends Activity {
private ListView lv_contact;
private String[] numbers = {
"13512340000",
"13512340001",
"13512340002",
"13512340003",
"13512340004",
"13512340005",
"13512340006",
};
@Override
protectedvoid onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_contactlist);
lv_contact = (ListView) findViewById(R.id.lv_contact);
lv_contact.setAdapter(new ArrayAdapter<String>(this, R.layout.item, numbers));
lv_contact.setOnItemClickListener(new OnItemClickListener() {
@Override
publicvoid onItemClick(AdapterView<?>parent, View view,
int position, long id) {
String number = numbers[position];
//把当前界面的数据,返回给开启我的界面.
Intent intent = new Intent();
intent.putExtra("number", number);
setResult(0,intent);
//关闭当前界面
finish();
}
});
}
}
4. 选择短信模板界面XML
<?xml version="1.0"encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/lv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>
</LinearLayout>
5. 选择短信模板界面代码实现
/**
* 选择短信模板界面
*/
publicclass SmsListActivity extends Activity {
private ListView lv;
private String[] smsMessages={
"春节的欢聚已分开,鞭炮声声已渐远。各自奔往工作地,踌躇满志加紧干。同事见面问新年,祝福的话语说不完。亲人的嘱托在耳畔,勤奋工作莫迟延。愿你年后心情好,事业更上一层楼!",
"欢快的歌声尽情飘,温暖的春风暖心潮。万千的喜气多热闹,吉祥的日子要来到。发条短信问个好,财源广进吉星照。万事顺利开怀笑,羊年幸福乐逍遥。",
"心情预报:今夜到明早想你,预计下午转为很想你,受此情绪影响,傍晚将转为暴想,此类天气将持续到见你为止。",
"世界如此忙碌,用心的人就会幸福;想你的脸,心里就温暖;想你的嘴,笑容跟着灿烂!随着新年的到来,关心你的人要跟你说声:新年快乐!",
"羊年到,身体很重要,应酬拒不掉,少喝酒多吃菜,表示心意就算了;祝愿老板身体好,越过越开心,财富滚滚来,生活越来越美好。",
"新年到了,衷心祝福你。祝你年年圆满如意,月月事事顺心,日日喜悦无忧,时时高兴欢喜,刻刻充满朝气!",
"勇攀高峰不怕险,开辟新径铸辉煌。勤奋创业走新道,学做银羊敢闯关。羊年要走阳关道,排除万难创业路。艰苦奋斗能胜天,幸福生活如意添。愿你羊年心想事业成!"
};
@Override
protectedvoid onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_smslist);
lv = (ListView) findViewById(R.id.lv);
lv.setAdapter(new ArrayAdapter<String>(this,R.layout.item, smsMessages));
lv.setOnItemClickListener(new OnItemClickListener() {
//当listview的条目被点击的时候调用的方法
@Override
publicvoid onItemClick(AdapterView<?>parent, View view,
int position, long id) {
System.out.println("position:"+position);
String message = smsMessages[position];
//把当前界面的数据,返回给开启我的界面.
Intent data = new Intent();
data.putExtra("message", message);
setResult(0, data);
//把当前界面关闭
finish();
}
});
}
}
总结:
在上面的案例中我们用到了两个新知识点。
一、关闭当前Activity
在Activity 中调用finish()方法即可关闭当前Activity。
二、启动一个Activity 时获取该Activity 销毁时的返回值
如果想让我们的MainActivity 打开ContactListActivity,同时能够获取到ContactListActivity销毁时返回的数据, 那么就不能通过startActivity(Intent)方法去启动ContactListActivity了。必须使用startActivityForResult(Intent intent, int requestCode)方法,该方法必须跟onActivityResult(intrequestCode,int resultCode, Intent data)方法结合着使用才行,当MainActivity启动的ContactListActivity销毁前,如果ContactListActivity调用了setResult(int resultCode, Intent data)方法,那么系统就会调用MainActivity的onActivityResult()方法,同时将数据Intent 的形式传递过来。
上面用到了3 个新的API:
1、startActivityForResult(Intent intent, int requestCode)
启动Activity,同时等待该Activity 返回数据。只有该Activity 销毁时数据才会被返回。
参数1:意图,封装要启动的Activity,当然也可以携带数据
参数2:请求码,如果是大于0 的整数,那么该请求码会在onActivityResult 中的requestCode中出现,如果小于等于0,则不会被返回。
2、onActivityResult(int requestCode, int resultCode, Intent data)
当打开的Activity 销毁的时候该方法会被调用,从该方法中获取销毁的Activity 设置的数据。
参数1:startActivityForResult 方法中的requestCode
参数2:setResult 方法中的resultCode
参数3:Intent 数据
3、setResult(int resultCode, Intent data)
被打开的Activity 如果想返回数据,则在销毁前一定要调用该方法,通过该方法设置数据。
参数1:结果码,该结果码可以用于区分是哪个Activity 给我们返回的数据
参数2:设置的数据
Activity 的生命周期
学到这里我们有必要对Activity 有一个全新的认识。Activity已经不能再简单的认为就是一个普通的类,其生命周期就是类的加载,对象的的创建和对象的卸载那么简单。Activity 从创建到销毁,整个生命周期是一个非常复杂的过程,该过程由Android 系统负责维护。
Activity 的3 种状态
我们人类有婴幼儿时期、青少年时期、中老年时期,Activity 一样也有3 种状态:Resumed、Paused、Stopped,这三种状态是Android 官方给出的,我们翻译过来可以理解为:激活或运行状态、暂停状态、停止状态。这3 种状态的特点如下:
1. Resumed 状态:Activity 位于前端位置,并且获取到了用户的焦点。也就是当前Activity 完全可见也可用。
注意:在Android 中目前只允许同时只能有一个Activity位于前端位置。
2. Paused 状态:如果另外一个Activity位于前端位置并且获取了焦点,但是该Activity 还依然可见,那么该Activity 就处于了Paused 状态。比如如果另外一个Activity 虽然位于前端,但是是透明的或者没有占满整个屏幕,那么就会出现上面的这种情况。位于Paused 状态的Activity 依然是“存活”着的,但是如果系统内存极端的不足,那么就有可能被系统“杀死”以便释放内存。
3. Stopped 状态:当另外一个Activity完全将该Activity 遮盖住的情况下,那么该Activity 就处于停止状态了。位于停止状态的Activity 依然“活着”,但是它已经对用户完全不可见了,因此只要系统需要释放内存就会将该Activity“杀死”。
我们编写的代码要根据Activity 的不同状态让其做不同的工作,比如如果我们的Activity 是用于播放视频的,那么当其位于Resumed 状态时我们可以让视频正常的播放,当Activity 位于Paused 状态的时候,我们也应该让我们的视频暂停播放,当Activity 位于Stopped 状态时我们应该停止播放视频并释放资源。
那么如何让我们的程序员能够感知到Activity的状态变化呢?Android 系统为了将Activity 状态的变化通知给我们在Activity 中提供了7 个回调方法。我们只需要在不同的回调方法中完成不同的工作即可。
Activity 生命周期的7 个回调方法
方法名 | 特点 |
OnCreate | 当Activity 第一次创建时被调用,只被调用一次,在该方法中我们应该执行创建视图、初始化数据等工作。该方法被调用之后紧接着就是调用onStart 方法。
|
onStart | 当Activity 对用户可见前被系统调用, 可以多次调用。 |
onResume | 获取焦点,当Activity 可以跟用户交互前被调用,该方法被调用后Activity 就处于Resumed 状态。 |
onPause | 失去焦点,只能看不能摸,当另外一个Activity 即将成为Resumed 状态前调用,在该方法中应该保存临时数据、停止动画、暂停视频播放器等。必须要注意的就是该方法执行必须要快,因为只有当该方法执行过之后,另外一个Activity 才会成为Resumed 状态,不然会造成Activity 直接切换的“卡顿”现象。 |
onStop | 当Activity 对用户已经完全不可见的时候就会调用该方法, 可以多次调用。当另外一个Activity 已经成为Resumed 状态或者当前Activity 被销毁的情况下会导致当前Activity 不可见。 |
onDestory | 当Activity 被销毁前调用, 只被调用一次。当前Activity 调用finish()方法或者系统为了释放内存而将其销毁都会调用该方法。 |
onRestart | 当Activity 处于onStop 状态之后,如果重新启动则会调用该方法。比如如果当前Activity位于Resumed 状态,此时我们按了Home 键,那么Activity 就完全不可见了,onStop 方法就会被执行,然后再长按Home 键再将该Activity 重新启动起来就会调用onRestart 方法。 |
Activity 生命周期图
完整生命周期:oncreate-->onstart-->onresum-->onpause-->onstop-->ondestroy
下图是Android官方给出的Activity 生命周期图:
横竖屏切换时Activity 的生命周期
Android 手机在横竖屏切换时,默认情况下会把Activity 先销毁再创建,模拟器横竖屏切换的快捷键是Ctrl+F11。
在类似手机游戏、手机影音这一类的应用中,这个体验是非常差的。不让Activity 在横竖屏切换时销毁,只需要在清单文件声明Activity 时配置<activity>节点的几个属性即可,其方式如下:
一. 4.0 以下版本:
android:configChanges="orientation|keyboardHidden"
二. 4.0 以上版本:
android:configChanges="orientation|screenSize"
三. 4.0 以上版本:
android:configChanges="orientation|keyboardHidden|screenSize"
将该参数配置到Activity 中后再切换屏幕方向,那么Activity就不会再销毁和重新创建了。但是配置了该参数仅仅是不让Activity 销毁和重建,Activity 界面依然会跟着屏幕方向重新调整,那么如何固定Activity 的方向呢?
固定Activity 的方向
想固定Activity 的方向其实比较简单,有两种方法:
1、通过配置文件
在AndroidManifest.xml 中的activity 节点中添加如下属性。
android:screenOrientation="portrait"
该属性通常有两个常量值,portrait:垂直方向,landscape:水平方向。
2、通过代码
在Activity 的onCreate 方法中执行如下方法。
//垂直方向
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
//水平方向
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
Activity 的启动模式
Activity 的任务栈
Task Stack(任务栈)是一个具有栈结构的容器,可以放置多个Activity 实例。启动一个应用,系统就会为之创建一个task,来放置根Activity;默认情况下,一个Activity 启动另一个Activity 时,两个Activity是放置在同一个task 中的,后者被压入前者所在的task 栈,当用户按下back 键,后者从task 中被弹出,
前者又显示在屏幕前,特别是启动其他应用中的Activity 时,两个Activity 对用户来说就好像是属于同一个应用;系统task 和应用task 之间是互相独立的,当我们运行一个应用时,按下Home 键回到主屏,启动另一个应用,这个过程中,之前的task 被转移到后台,新的task 被转移到前台,其根Activity 也会显示到
幕前,过了一会之后,再次按下Home 键回到主屏,再选择之前的应用,之前的task 会被转移到前台,系统仍然保留着task 内的所有Activity 实例,而那个新的task 会被转移到后台,如果这时用户再做后退等动作,就是针对该task 内部进行操作了。
任务栈的设计是为了提高用户体验,但是也有其不足的地方。任务栈的缺点如下:
1、每开启一次页面都会在任务栈中添加一个Activity,而只有任务栈中的Activity 全部清除出栈时,任务栈被销毁,程序才会退出,这样的设计在某种程度上可能造成了用户体验差,需要点击多次返回才可以把程序退出了。
2、每开启一次页面都会在任务栈中添加一个Activity 还会造成数据冗余, 重复数据太多, 会导致内存溢出的问题(OOM)。
Andriod给出的官方图:
为了解决任务栈产生的问题,Android 为Activity 设计了启动模式,那么下面的内容将介绍Android 中Activity 的启动模式,这也是最重要的内容之一。
Activity 的启动模式
启动模式(launchMode)在多个Activity 跳转的过程中扮演着重要的角色,它可以决定是否生成新的Activity 实例,是否重用已存在的Activity 实例,是否和其他Activity 实例共用一个task。
Activity 一共有以下四种launchMode:standard、singleTop、singleTask、singleInstance。我们可以在AndroidManifest.xml配置<activity>的android:launchMode 属性为以上四种之一即可。
l standard:标准启动模式
特点:默认启动模式, 每次激活Activity时(startActivity),都创建Activity实例,并放入任务栈. (默认activity都是标准启动模式)
l singleTop:单一顶部模式
配置: activity: launchMode="singleTop"
特点:如果activity已经被开启,而且是在栈顶,就不会在创建当前这个activity的实例,而是复用这个已经开启的activity,但是如果不是在栈顶,就会初始化一个新的实例,在整个栈里允许有多个实例(系统短信界面)
l singleTask:单一任务栈
特点:当前栈里只允许有一个当前activity的实例,如果要开启的activity在栈里存在,并且在底部,就会移除这个activity上面所有的activity
应用场景:如果这个activity非常消耗cpu和内存,建议把这个activity的启动模式设置为singleTask,浏览器的browserActivity 设置的就是(系统浏览器界面)
l singleinstance:单一实例
特点:整个手机操作系统只有一个实例,并且是单独运行在自己的任务栈里
应用场景:通话界面的activity(系统电话界面)