Activity的创建
Activity 是Android 四大组件之一,用于展示界面。Activity 中所有操作都与用户密切相关,是一个负责与用户交互的组件,它上面可以显示一些控件也可以监听并处理用户的事件。一个Activity 通常就是一个单独的屏幕,Activity 之间通过Intent 进行通信。
1. 自定义类继承Activity
新建一个Android 工程,在src 目录下新建一个类,不妨起名MyActivity,让该类继承Android SDK提供的Activity。
2. 在MyActivity 中覆写onCreate()方法
onCreate() 方法是在当前Activity 被系统创建的时候调用的, 在该方法中一般要通过setContentView(R.layout.myactivity)方法给当前Activity 绑定布局文件或视图。因此为了让我们的MyActivity 能够展示界面,需要在工程目录的res/layout/目录下创建一个布局文件。比如我创建的布局文件名(注意:Android 的资源文件名中是不允许有大写字母的,必须全小写,如果有多个单词可以用下划线分开)为myactivity.xml:
必须要注意的就是在该方法中必须先调用父类的onCreate() 方法, 也就是super.onCreate(savedInstanceState);该句代码必须被调用一次。这是为什么呢?我们看看父类的onCreate()方法里面都干了些啥就知道了。如下图所示,在父类的方法中执行了一堆业务逻辑,而且在最后一行用了一个成员变量mCalled 记录了当前方法是否被调用了。虽然我们现在还看不懂这些代码,但是我们已经知道既然父类帮我们实现了这些功能,那么我们子类就一定必须调用一次父类的该方法。
3. 在AndroidManifest.xml 中进行Activity 的注册
Android 中共有四大组件,除了BroadCastReceiver 之外,其他三个组件如果要使用那么必须在AndroidManifest.xml 中进行注册(也叫声明)。
我们在上面创建的MyActivity 如何注册呢?其实如果你不会的话完全可以将eclipse 自动生成的MainActivity 的注册代码给拷贝过来,然后将android:name 的属性值修改成我们的类。
上面用到的标签以及属性含义如下:
l activity/android:name:要注册的Activity 的全限定类名,在eclipse 中按住Ctrl+鼠标左键可以点击到该类的代码视图,必须指定。
l activity/android:label:Activity 左上角显示的名称
l intent-filter:叫意图过滤器,一般用于隐式启动该Activity
l action/android:name:意图过滤器的Action 名称,可以自定义也可以使用系统提供的。
l category/android:name:意图过滤器的category 名称,只能使用系统提供的常量值。
我将MainActivity 的所有东西都拷贝过来了,只不过是替换了类名。
对于MainActivity来说正是配置了特定(
action 为android:name="android.intent.action.MAIN",
category 为android:name="android.intent.category.LAUNCHER")的intent-filter,那么当应用安装好以后,才会在桌面创建一个图标,点击该图标打开MainActivity。现在我自定义的MyActivity 也配置了一模一样的intent-filter,那么系统也会为我的MyActivity 创建一个图标,如果用户点击了该图标,那么也可以直接打开我的MyActivity 界面。也就是说一个Android 工程可以创建多个图标,不过通常情况下一个Android 应用只要给一个入口的Activity 配置为入口Activity 即可。
Activity的跳转
一个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="自己定义,习惯用包名后加功能名"/>
<category android:name="android.intent.category.DEFAULT"/> //默认
</intent-filter>
2. 谁要跳转到这个activity,谁的方法里面调用
Intent intent = new Intent();
intent.setAction("要跳转的activity在清单文件里配置的action");
intent.addCategory("android.intent.category.DEFAULT");-->默认
startActivity(intent);
Ø 隐示意图需要注意的地方
在清单文件的 intent-filter 里面还可以配置 data标签,data标签可以配置多个不同种类型的
例如:
<data android:scheme="自己定义"/> -->设置前缀,与电话播放器调用很像
<data android:mimeType="text/plain"/> -->定义类型,这里不能随意定义
在java代码里,如果同时配置了scheme和mineType:
intent.setDataAndType(scheme,mimeType);
如果只配置scheme:
intent.setData();
如果只配置了mimeType:
intent.setType();
案例-人品计算器
综合了activity的创建,显示意图打开界面,数据的传递的一个案例。
需求分析:
主要有三个界面,分别是首页MainActivity,计算页面CalcActivity,结果界面ResultActivity。实现的效果是首页显示一个logo界面停留两秒后进入到计算页面,在计算界面输入需要测试的姓名,并勾选对应的性别点击计算按钮即可进入结果界面查看计算的结果。
具体的效果及流程如下图:
2S后自动进入
输入姓名后计算的结果界面:
代码实现
1. MainActivity布局:
2. MainActivity代码自动等待两秒进入CalcActivity:
3. 计算界面布局XML:
4. 计算界面代码实现:获取输入的姓名和选中的性别,传递到ResultActivity界面
5. ResultActivity界面布局xml:
案例-短信助手
通过该案例可以让我们学会多界面的开发,数据的传递,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. 主界面代码实现
public class MainActivity extends Activity {
/**收件人号码*/
private EditText et_number;
/**短信内容*/
private EditText et_message;
/**选择短信模板的请求码*/
public static final int SELECT_SMS = 1;
/**选择联系人的请求码*/
public static final int SELECT_CONTACT = 2;
@Override
protected void 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);
}
public void selectSmsContent(View view) {
Intent intent = new Intent(this, SmsListActivity.class);
// startActivity(intent);
// 开启新的Activity并且获取返回值
startActivityForResult(intent, SELECT_SMS);
}
// 开启新的界面 选择联系人
public void selectContact(View view) {
Intent intent = new Intent(this, ContactListActivity.class);
// startActivity(intent);
// 开启新的Activity并且获取返回值
startActivityForResult(intent, SELECT_CONTACT);
}
@Override
protected void 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);
}
}else if(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. 选中联系人界面代码实现
/**
* 联系人选择界面
*/
public class ContactListActivity extends Activity {
private ListView lv_contact;
private String[] numbers = {
"13512340000",
"13512340001",
"13512340002",
"13512340003",
"13512340004",
"13512340005",
"13512340006",
};
@Override
protected void 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
public void 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. 选择短信模板界面代码实现
/**
* 选择短信模板界面
*/
public class SmsListActivity extends Activity {
private ListView lv;
private String[] smsMessages={
"春节的欢聚已分开,鞭炮声声已渐远。各自奔往工作地,踌躇满志加紧干。同事见面问新年,祝福的话语说不完。亲人的嘱托在耳畔,勤奋工作莫迟延。愿你年后心情好,事业更上一层楼!",
"欢快的歌声尽情飘,温暖的春风暖心潮。万千的喜气多热闹,吉祥的日子要来到。发条短信问个好,财源广进吉星照。万事顺利开怀笑,羊年幸福乐逍遥。",
"心情预报:今夜到明早想你,预计下午转为很想你,受此情绪影响,傍晚将转为暴想,此类天气将持续到见你为止。",
"世界如此忙碌,用心的人就会幸福;想你的脸,心里就温暖;想你的嘴,笑容跟着灿烂!随着新年的到来,关心你的人要跟你说声:新年快乐!",
"羊年到,身体很重要,应酬拒不掉,少喝酒多吃菜,表示心意就算了;祝愿老板身体好,越过越开心,财富滚滚来,生活越来越美好。",
"新年到了,衷心祝福你。祝你年年圆满如意,月月事事顺心,日日喜悦无忧,时时高兴欢喜,刻刻充满朝气!",
"勇攀高峰不怕险,开辟新径铸辉煌。勤奋创业走新道,学做银羊敢闯关。羊年要走阳关道,排除万难创业路。艰苦奋斗能胜天,幸福生活如意添。愿你羊年心想事业成!"
};
@Override
protected void 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
public void 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(int requestCode,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 生命周期图
下图是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实例,并放入任务栈.
l singleTop:单一顶部模式
特点:如果activity已经被开启,而且是在栈顶,就不会在创建当前这个activity的实例,而是复用这个已经开启的activity,但是如果不是在栈顶,就会初始化一个新的实例,在整个栈里允许有多个实例
l singleTask:单一任务栈
特点:当前栈里只允许有一个当前activity的实例,如果要开启的activity在栈里存在,并且在底部,就会移除这个activity上面所有的activity
应用场景:如果这个activity非常消耗cpu和内存,建议把这个activity的启动模式设置为singleTask,浏览器的browserActivity 设置的就是
l singleinstance:单一实例
特点:整个手机操作系统只有一个实例,并且是单独运行在自己的任务栈里
应用场景:通话界面的activity
Note:
Activity的启动模式是面试几乎都会问到的一个知识点,也是很重要的一个知识点,在项目开发中经常会用到启动模式来帮助我们解决一些比较难解决的bug,也可以帮助我们去优化一个项目的任务栈,防止界面太多导致卡顿和OOM。