3Activity
1. 本章概述
本章讲解Android的组件式开发思想、Android的四大组件、Android中的Activity的基本概念、生命周期函数以及Activity之间的传值。
2. 本章重点与难点及学习方法
1) 重点
(1) Android的四大组件。
(2) Activity的生命周期。
(3) Activity的基本用法。
(4) Activity页面之间的传值。
2) 难点
(1) Activity生命周期。
(2) Activity页面之间的传值。
3. 重难点学习建议
本章将重点学习Activity的创建方法、Activity生命周期函数。理解Android程序结构及组件式开发思想,通过反复练习实践掌握Activity的用法,同时通过开发环境的Logcat观察Android生命周期函数的执行时机。
3.1Android四大组件
Android应用开发是组件式开发,所谓组件,可以理解为装修房屋的一个组成元素,一个Android应用就像一间房子,房子里的一张桌子、一把椅子、一张床就相当于Android的组件,除了这种可以直观上看得到的组件外,还有一些组件负责服务功能,例如,房子中的水管道、煤气管道、电力管道等,这些不是直接呈现在视觉中,但起着很重要的作用,就相当于Android的Service组件。要实现一个Android应用,就可以把一堆接口标准、封装完整的组件拿来用,组件搭配使用,就形成了一间装修好的房子,也就是一个Android应用了。Android的四大组件如下。
Activity: 表示一个可视化的用户界面,在应用程序中是一个单独的屏幕。每个屏幕都是通过继承和扩展基类Activity实现的。
Service: 表示服务,没有可见的用户界面,只提供服务,能够长时间运行于后台,通过继承和扩展基类Service来实现。在后台运行于应用程序进程的主线程中,因此Service不会阻塞其他组件或者用户界面。
ContentProvider: 表示内容提供者,可以将应用程序特定的数据提供给另一个应用程序使用,其数据存储方式可以是Android文件系统、SQLite数据库或者其他方式。
BroadcastReceiver: 表示广播接收器,自身并不实现图形用户界面,但是当收到某个广播后,BroadcasetReceiver可以启动Activity作为响应,或者通过NotificationManager提醒用户,或者调用Service处理长时间事务。
除了以上四大组件外,Intent 也是一个非常重要的组件,它在不同组件之间传递信息,将一个组件的请求意图传给另一个组件,可以实现组件之间调用,还可以通过Intent进行组件间传递数据。Android会根据意图的内容选择适当的组件来调用。
3.2Activity的创建
Activity的中文意思是活动,可以显示由几个View组件组成的用户接口,并且可以对事件进行相应的处理。在Android中,Activity代表手机屏幕的一屏,或是平板电脑中的一个窗口。它是Android应用程序与用户交互的窗口,几乎每一个Android应用程序都离不开Activity,它就像一个网站的页面一样,每个页面都可以通过一个独立的类来表示,这个独立的类继承于Activity这个基类。
创建Activity,大致可以分为以下两个步骤。
(1) 创建一个Activity一般是继承android.app包中的AppCompatActivity类,不过在不同的应用场景下,也可以继承Activity的子类。创建一个继承AppCompatActivity的类,名称为MainActivity的具体代码如下。import android.app.Activity;
public class MainActivity extends AppCompatctivity {
}
(2) 重写需要的回调方法。通常情况下,都需要重写onCreate()方法,并且在该方法中调用setContentView()方法设置要显示的视图。例如,在步骤(1)中创建的Activity中,重写onCreate()方法,并且设置要显示的视图的具体代码如下。@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
创建Activity后,还需要在AndroidManifest.xml文件中配置该Activity,具体的配置方法是在标记中添加标记实现。标记的基本格式如下。
…
(1) 启动Activity步骤如下。
① 生成一个意图对象Intent。
② 调用setClass方法设置所要启动的Activity。
③ 调用startActivity方法启动Activity,其语句如下。public void startActivity (Intent intent)
(2) 关闭Activity,其语句如下。public void finish ()
3.3Activity的生命周期
在Activity的生命周期中,有表3.1所示的4个重要状态。表3.1Activity生命周期
状态描述活动状态当前Activity位于Activity栈顶,用户可见,并且可以获得焦点暂停状态失去了焦点的Activity,仍然可见,但是在内存低的情况下,它不能被系统killed(杀死)停止状态该Activity被其他Activity所覆盖,不可见,但是它仍然保存所有的状态和信息,不过,在内存低的情况下,它可能会被系统killed(杀死)销毁状态该Activity结束,或Activity所在的Dalvik进程被结束图3.1描述了Activity从创建到销毁整个生命周期方法的调用过程。
图3.1Activity的生命周期
从最初调用onCreate()到最终调用onDestroy()称为完整生命周期。Activity会在onCreate()中进行所有“全局”状态的设置,在onDestroy()中释放所有持有的资源。例如,如果它有一个从网络上下载数据的后台线程,那就可以在onCreate()中创建这个线程并在onDestroy()中停止这个线程。
从Activity调用onStart()开始,到调用对应的onStop()为止,称为可见生命周期。在这段时间内尽管此Activity并不一定是在屏幕的最前方,也不一定可以和用户交互,但是用户可以在屏幕上看到这个Activity。在这两个方法运行周期之间可以根据需求维护Activity。例如,可以在onStart()中注册一个IntentReceiver(意图接收器)来监控那些可以对UI 产生影响的环境改变,当UI 不继续在用户面前显示时,可以在onStop() 中注销这个IntentReceiver。
每当Activity 在用户面前显示或者隐藏时都会调用相应的方法,所以onStart()和onStop()方法在整个生命周期中可以多次被调用。
从Activity调用onResume()开始,到调用对应的onPause()为止称为前景生命周期,这段时间Activity处于其他所有Activity的前面,且与用户交互。一个Activity 可以经常在resumed 和paused 状态之间转换,例如,手机进入休眠时、Activity的结果返回时或者新的Intent 到来时,所以这两个方法中的代码应该非常简短。
总之,所有Activity都应该实现自己的onCreate(Bundle)方法来进行初始化设置,大部分还应该实现onPause()方法提交数据的修改,并且准备终止与用户的交互。
【例3.1】Activity生命周期回调函数执行时机练习。定义两个Activity,第一个Activity中有个标签,标签的内容为“第一个Activity”,还有一个Button,Button显示的内容为“跳到第二个Activity”,第二个Activity有一个标签,标签内容为“第二个Activity”,还有一个Button,Button显示的内容为“返回第一个Activity”。在每个Activity中添加Activity的回调函数,运行观察Logcat的输出,理解Activity各个回调函数的执行时机。
本程序需要创建两个Activity,分别为MainActivity和Main2Activity,创建Activity的步骤如下。
(1) 在使用Android项目结构时,选择Java目录下的包名,右击,在弹出的快捷菜单中选择New→Activity→Empty Activity,出现图3.2所示的对话框。
图3.2新建Activity对话框
(2) 在MainActivity类中重写Activity的几个生命周期方法,在每个方法的方法体里使用Logcat输出如下内容: 当前所在Activity名: 当前所在方法名。例如,在onRestart方法中加入如下输出语句:Log.d(TAG,"onCreate: ");
在MainActivity对应的布局文件上放置一个按钮,设置按钮的id为“go”,在后台代码中通过findViewById方法找到该按钮,设置按钮的单击事件,Android事件处理模型和Java类似,所有实现OnClickListener 接口的类,都可作为按钮事件的监听器,设置监听器的方法为setOnClickListener,每当单击按钮时,都会自动回到OnClickListener 接口的onClick方法。MainActivity对应代码如下。import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.example.jason.demo20181126.R;
public class MainActivity extends AppCompatActivity {
//定义静态变量TAG,用于Logcat输出的标签
public static final String TAG="MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main1);
Log.d(TAG, "onCreate: ");
System.out.println();
Button startActivity=(Button) findViewById(R.id.go);
//设置启动按钮的监听器,匿名类作监听器
startActivity.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//使用Intent进行跳转
Intent intent=new Intent(MainActivity.this, Main2Activity.class);
startActivity(intent);
}
});
}
//重写onStart方法
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart");
}
}
MainActivity对应的布局文件如下。
同样地,在Main2Activity中,重写Main2Activity类中的几个生命周期方法,在每个方法的方法体里输出如下内容: 当前所在Activity名: 当前所在方法名。同时设置按钮监听器方法,单击按钮时返回,结束当前Activity即是返回,返回语句为:finish();
Main2Activity对应的代码如下。import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import com.example.jason.demo20181126.R;
public class Main2Activity extends AppCompatActivity {
public static final String TAG="Main2Activity";
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
btn=findViewById(R.id.back);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart: ");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume: ");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause: ");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop: ");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: ");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart: ");
}
}
MainActivity对应的布局文件activity_main代码如下。
第一个Activity运行效果,如图3.3所示。
第二个Activity运行效果,如图3.4所示。
使用Logcat观察输出结果,如图3.5所示。图3.3第一个Activity运行效果
图3.4第二个Activity运行效果
图3.5Logcat查看MainActivity生命周期函数执行顺序
3.4Activity间的信使Intent
Intent的中文意思是“意图,意向”,Intent机制在Android中用来协助应用间的交互与通信,Intent负责对应用中一次操作的动作、动作涉及的数据和附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将Intent传递给调用的组件,并完成组件的调用。Intent不仅可用于应用程序之间,也可用于应用程序内部的Activity/Service之间的交互。因此,可以将Intent理解为不同组件之间通信的“媒介”专门提供组件互相调用的相关信息。所以将Intent翻译为“信使”。
3.4.1显式调用和隐式调用〖*2〗1. 显式调用直接调用Activity的Class类,例如MainActivity调用NewActivity。Intent intent=new Intent(this, NewActivity.class);
startActivity(intent);
2. 隐式调用
顾名思义,隐式调用就是在不明确设置激活对象的前提下寻找最匹配的组件,隐式不明确指定启动哪个Activity,而是设置Action、Data、Category,让系统来筛选出合适的Activity,筛选是根据所有的来筛选。例如有甲乙丙三人: 甲160cm,乙170cm,丙180cm。如果是显式意图的话,要指明选择乙的话会说: “我选择乙”,但是如果是隐式调用,则会说: “我要选择170cm的人”,虽然没有指明要选乙,但会寻找条件最匹配的人。
在intent过滤器中类似于上面例子中的“身高”条件的匹配条件如下。
(1) action: 行为。
(2) category: 提供将要执行的action的额外信息。
(3) data: scheme、host、path、type,data属性匹配信息。
当在程序中设置了这些激活组件的条件,程序就会去寻找最匹配的组件,但是注意: 只要有一点不匹配,就是不匹配。
隐式Intent的核心代码模式,首先是在AndroidManifest.xml中为某个Activity设置意图过滤器。
以上设置是设置Activity本身的属性,接下来在程序中要设置的是要寻找时匹配的条件。Intent intent=new Intent();
intent.setAction("....");
intent.addCategory("....");
intent.setData(Uri.parse("...."));
//设置data的scheme、host、path条件
intent.setDataAndType(Uri.parse(""),String type);
//同时设置data的scheme、host、path、type条件
startActivity(intent);
//调用intent.addCategory("android.intent.category.DEFAULT");
例如,在MainActivity启动时直接进行Activity隐式跳转,将进行隐式Intent匹配,最后寻找到并激活NewActivity。
在NewActivity中设置了action为com.neuedu.action,category为com.neuedu.category,data的属性host、scheme分别为www.baidu.com和baidu。
在MainActivity中进行隐式跳转,隐式调用如下代码。protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Activity跳转,隐式调用
Intent intent=new Intent();
intent.setAction("com.neuedu.action");
intent.addCategory("com.neuedu.category");
intent.setData(Uri.parse("neuedu://www.my12306.com/"));
startActivity(intent);
//此方法中调用intent.addCategory("android.intent.category.DEFAULT");
}
3.4.2向下一个Activity传递数据
当在一个Activity中启动另一个Activity时,经常需要传递一些数据过去。这时就可以通过Intent来实现,因为Intent通常被称为是两个Activity之间的信使,通过将要传递的数据保存在Intent中,就可以将其传递到另一个Activity中了。利用Intent的putExtra()来存储需要传递的数据,可以传送int、long、char等一些基础类型,对复杂的对象建议采用Bundle进行传递。putExtra("A",B)中,AB为键值对,第一个参数为键名,第二个参数为键对应的值。如果取出Intent对象中的这些值,则需要在另一个Activity中调用getStringExtra()等方法,注意需要使用对应类型的方法,参数为键名。假设Intent就像一封邮件,里面有送信人地址(原始Activity),也有收信人地址(目标Activity),参数也可看作是信件内容。
Intent在传递复杂数据时,可以借助Bundle来实现。Bundle只是一个信息的载体,将内部的数据以键值对形式存储。在传复杂参数前,需要将对象封装起来,在Android系统中,通过Parcelable和Serializable接口实现在进程间和网络中封装传递对象。
Parcelable的性能优于Serializable,在内存开销方面较小,所以在内存间数据传输时推荐使用Parcelable,如Activity间传输数据。而Serializable可将数据持久化方便保存,所以在需要保存或网络传输数据时选择Serializable。
下面通过一个具体的实例介绍如何使用Intent在Activity之间交换数据,传递复杂数据对象时,需要对象是可序列化的,这里的Data就是一个实现Serializable接口的可序列化类。
【例3.2】使用Intent从TransmitDataActivity传递一个字符串、一个整数和一个Data对象到MyActivity中,在MyActivity中把传递过来的数据显示在TextView上。
将数据保存到Intent对象,并跳到另一个用来显示这些数据的Activity的代码如下。import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import cn.edu.neusoft.software.mobile.activitydemo.R;
public class TransmitDataActivity extends AppCompatActivity {
private Button btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_transmit_data);
btn=(Button) findViewById(R.id.btnValue);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(TransmitDataActivity.this,
MyActivity.class);
intent.putExtra("intent_string","使用intent传值");
intent.putExtra("intent_integer",100);
Data data=new Data();
data.id=1000;
data.name="Android";
intent.putExtra("intent_object",data);
startActivity(intent);
}
});
}
}
上面的代码涉及一个Data类,这个类是可序列化的,也就是实现了java.io.Serializable接口的类。Data类的代码如下。public class Data implements Serializable{
String name;
int id;
}
在MyActivity类中获取通过Intent对象传递来的3个值(String、Integer和Data类型的值)的代码如下。import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;
import cn.edu.neusoft.software.mobile.activitydemo.R;
public class MyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
TextView textView=(TextView)findViewById(R.id.textResult);
String intentString=getIntent().getStringExtra("intent_string");
/String intentString=getIntent().getExtras().getString("intent_string");/
int intentInteger=getIntent().getExtras().getInt("intent_integer");
Data data=(Data)getIntent().getExtras().get("intent_object");
StringBuffer sb=new StringBuffer();
sb.append("intent_string");
sb.append(intentString);
sb.append("\\n");
sb.append("intent_integer");
sb.append(intentInteger);
sb.append("\\n");
sb.append("data.id");
sb.append(data.id);
sb.append("\\n");
sb.append("data.name");
sb.append(data.name);
sb.append("\\n");
textView.setText(sb.toString());
}
}
程序分析: 使用Intent传递数据,是最常用的一种数据传递的方法。通过Intent.putExtra方法可以将简单类型的数据或可序列化的对象保存在Intent对象中,然后在目标Activity中使用getXxx(getInt、getString等)方法获得这些数据。
运行程序后,单击图3.6所示界面的按钮,会显示图3.7所示的输出信息。
图3.6TransmitData程序的主界面
图3.7显示从Intent对象中获取的数据
3.4.3返回数据给上一个Activity
在开发应用时,不仅需要传递数据给其他Activity,也要从其他Activity中返回数据。返回数据,一般采用Intent对象的方式来返回数据,采用这种方式,需要使用startActivityForResult(Intent intent,int requestCode),requestCode的值是自定义的,用于识别跳转的目标Activity。跳转的目标Activity所要做的就是返回数据/结果,setResult(int resultCode)只返回结果不带数据,或者setResult(int resultCode,Intent data)两者都返回。而接收返回的数据/结果的处理函数是onActivityResult(int requestCode,int resultCode,Intent data),这里的requestCode就是startActivityForResult的requestCode,resultCode就是setResult里面的resultCode,返回的数据在data里面。
【例3.3】使用startActivityForResult从目标Activity返回值,创建3个Activity,名字分别为FirstActivity、SecondActivity和ThirdActivity,在FirstActivity布局上放置两个按钮,单击分别跳到SecondActivity和ThirdActivity,在SecondActivity和ThirdActivity上放置按钮,单击返回到FirstActivity,同时分别返回一个字符串给FirstActivity,在FirstActivity中通过对话框显示返回的字符串。
在FirstActivity类中获取通过startActivityForResult启动SecondActivity和ThirdActivity,在onActivityResult中处理返回结果。FirstActivity的代码如下。import android.content.Intent;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
public class FirstActivity extends AppCompatActivity {
private Button btnSecond,btnThird;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first);
btnSecond=findViewById(R.id.btnSecond);
btnSecond.setOnClickListener(new ButtonListener());
btnThird=findViewById(R.id.btnThird);
btnThird.setOnClickListener(new ButtonListener());
}
class ButtonListener implements View.OnClickListener{
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btnSecond:
Intent intent=new Intent(FirstActivity.this,SecondActivity.
class);
startActivityForResult(intent,1);
break;
case R.id.btnThird:
Intent intent1=new Intent(FirstActivity.this,ThirdActivity.
class);
startActivityForResult(intent1,2);
break;
}
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 1:
if(resultCode==RESULT_OK) {
String returnedData=data.getStringExtra("data_return");
Toast.makeText(this,returnedData+" ",Toast.LENGTH_
SHORT).show();
Log.d("FirstActivity", returnedData);
}
break;
case 2:
if(resultCode==RESULT_OK) {
String returnedData=data.getStringExtra("data_return");
Toast.makeText(this,returnedData,Toast.LENGTH_LONG).
show();
Log.d("FirstActivity", returnedData);
}
break;
default:
}
}
}
FirstActivity对应的布局文件如下。
SecondActivity的代码如下。import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
public class SecondActivity extends AppCompatActivity {
private Button btn;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second);
btn=findViewById(R.id.back);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent();
intent.putExtra("data_return", "Hello FirstActivity,I am
secondActivity");
setResult(RESULT_OK, intent);
finish();
}
});
}
}
SecondActivity对应的布局文件如下。
ThirdActivity的后台代码和对应的布局请参考SecondActivity自行完成。程序运行效果如图3.8所示,在MainActivity中单击“跳到第二个”按钮跳到第二个Activity中,在SecondActivity单击“返回”按钮返回到MainActivity中,同时在MainActivity中显示返回的结果,如图3.9和图3.10所示。
图3.8SecondActivity运行效果
图3.9带返回值的运行效果1
图3.10带返回值的运行效果2
3.5知识拓展: Activity/ActionBarActivity/AppCompatActivity
在AS中创建的MainActivity类默认是继承自AppCompatActivity,它是Activity的子类。ActionBarActivity与AppCompatActivity都是Activity的子类,ActionBarActivity在AS中已经是过期类。在AS中如果创建的类继承自Activity,则不带ActionBar,如果要在AS中也使用ActionBar,并且不使用已经过时的ActionBarActivity,有什么办法呢?就是使用AppCompatActivity。在AS中把MainActivity继承自AppCompatActivity,并导入对应的包。在以后的项目中,可以通过手动修改Activity的继承父类,来决定是否显示ActionBar,并且对程序没有其他影响。
小结
本章给出了一个简单的例子来作为Android入门学习的第一个程序。通过这个程序读者可以掌握建立Android程序的方法,理解Android程序的基本结构,理解Android程序的组件式开发方法,掌握Android的四大组件及Activity的基本概念与用法。
习题
1. 两个Activity之间跳转时必然会执行的是哪几个方法?
2. 简述Android系统的4种基本组件Activity、Service、BroadcastReceiver和ContentProvider的用途。
3. Intent的作用是什么?Intent传递数据时,可以传递哪些类型数据?
4. 简要说明gradle文件的主要作用。
5. Activity对象的7个生命周期方法是什么?说明各方法的执行时机。
6. Activity生命周期中,第一个需要执行的方法是什么?