本节内容:Activity基本用法(创建Activity、隐藏标题栏、Toast提醒方式、在活动中使用menu、销毁一个活动)、使用intent、活动的生命周期、活动的启动模式、几种关于活动的最佳实践技巧、小栗子乘法计算器;
用户接口、控件的容器、包含用户界面的组件,主要用于和用户进行交互
创建Activity要点:
1.手动创建活动:
一个Activity就是一个类(右击com.example.activitytest 包→New→Class),并且继承Activity,重写onCreate方法,设置布局
2.创建和加载布局:
右击res/layout 目录→New→Android XML File,根元素就默认选择为LinearLayout;
加载布局:setContentView(R.layout.first_layout);
(一个Activity对应一个xml文件)
3.在AndroidManifest 文件中注册:
指定主活动:
1
2
3
4
|
<
intent-filter
>
<
action
android:name
=
"android.intent.action.MAIN"
/>
<
category
android:name
=
"android.intent.category.LAUNCHER"
/>
</
intent-filter
>
|
隐藏标题栏:
在onCreate()方法中添加如下代码:
requestWindowFeature(Window.FEATURE_NO_TITLE);
注意:这句代码一定要在setContentView()之前执行,不然会报错。
或者,在AcdroidMainfest.xml中
<application
android:icon="@drawable/icon"
android:label="@string/app_name"
android:theme="@android:style/Theme.NoTitleBar"
>
添加Theme属性,设置为NoTitleBar即可
在活动中使用Toast:
Toast 是Android 系统提供的一种非常好的提醒方式,在程序中可以使用它将一些短小的信息通知给用户,这些信息会在一段时间后自动消失,并且不会占用任何屏幕空间。
首先需要定义一个弹出Toast 的触发点,以点击按钮为例,在onCreate()方法中添加代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
//一定要在setContentView()之前执行!!!
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.first_layout);
Button button1 = (Button) findViewById(R.id.button_1);
button1.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
Toast.makeText(FirstActivity.
this
,
"You clicked Button 1"
,Toast.LENGTH_SHORT).show();
}
});
}
|
Toast 的用法非常简单,通过静态方法makeText()创建出一个Toast 对象,然后调用show()将Toast 显示出来就可以了。这里需要注意的是,makeText()方法需要传入三个参数。第一个参数是Context,也就是Toast 要求的上下文,由于活动本身就是一个Context 对象,因此这里直接传入FirstActivity.this即可。第二个参数是Toast显示的文本内容,第三个参数是Toast显示的时长,有两个内置常量可以选择Toast.LENGTH_SHORT 和Toast.LENGTH_LONG。
此处也可以将监听器类实现为内部类,再为按钮注册该监听器:
btn.setOnClickListener(new MyClickListener());//写在onCreate方法中
1
2
3
4
5
6
7
|
class
MyClickListener
implements
OnClickListener{
@Override
public
void
onClick(View v) {
// TODO Auto-generated method stub
Toast.makeText(MainActivity.
this
,
"You clicked Button 1"
,Toast.LENGTH_SHORT).show();
}
}
|
在活动中使用menu:
首先在res 目录下新建一个menu 文件夹,右击res 目录→New→Folder,输入文件夹名menu,点击Finish。接着在这个文件夹下再新建一个名叫main 的菜单文件,右击menu 文件夹→New→Android XML File,文件名输入main,点击Finish 完成创建。然后在main.xml 中添加如下代码:
1
2
3
4
5
6
7
8
|
<
menu
xmlns:android
=
"http://schemas.android.com/apk/res/android"
>
<
item
android:id
=
"@+id/add_item"
android:title
=
"Add"
/>
<
item
android:id
=
"@+id/remove_item"
android:title
=
"Remove"
/>
</
menu
>
|
然后打开Activity,重写onCreateOptionsMenu()方法,代码如下所示:
1
2
3
4
|
public
boolean
onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return
true
;
}
|
再定义菜单响应事件。在FirstActivity 中重写onOptionsItemSelected()方法:
1
2
3
4
5
6
7
8
9
10
11
12
|
public
boolean
onOptionsItemSelected(MenuItem item) {
switch
(item.getItemId()) {
case
R.id.add_item:
Toast.makeText(
this
,
"You clicked Add"
, Toast.LENGTH_SHORT).show();
break
;
case
R.id.remove_item:
Toast.makeText(
this
,
"You clicked Remove"
, Toast.LENGTH_SHORT).show();
break
;
default
:
}
return
true
;
}
|
销毁一个活动:
按back键,或在触发点调用finish方法;
也可以调用finishActivity()
来终止你之前启动了的一个独立activity。
注意: 多数情况下,你不应该明确地通过这些方式来关闭acitivity。 就像下面要讨论的activity的生命周期。系统会为你管理。所以你不必关闭他们。 调用这些方法将有悖于用户体验。它们仅用于你绝对不想让用户再返回这个activity的实例。
使用intent:
Intent 是Android 程序中各组件之间进行交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,还可以在不同组件之间传递数据。Intent 一般可被用于启动活动、启动服务、以及发送广播等场景;
Intent对象包含:Component name,Action,Data,Extras,Category,Flags;(去官网查看详细介绍)
Intent 的用法大致可以分为两种,显式Intent 和隐式Intent;
显式Intent:
Intent 有多个构造函数的重载,其中一个是Intent(Context packageContext, Class<?> cls)。这个构造函数接收两个参数,第一个参数Context 要求提供一个启动活动的上下文,第二个参数Class 则是指定想要启动的目标活动;
Activity 类中提供了一个startActivity()方法,这个方法是专门用于启动活动的,它接收一个Intent参数;
在触发点使用intent:
1
2
|
Intent intent =
new
Intent(FirstActivity.
this
, SecondActivity.
class
);
startActivity(intent);
|
隐式Intent:
它并不明确指出我们想要启动哪一个活动,而是指定了一系列更为抽象的action 和category 等信息,然后交由系统去分析这个Intent,并帮我们找出合适的活动去启动。
通过在<activity>标签下配置<intent-filter>的内容,可以指定当前活动能够响应的action和category(每个Intent 中只能指定一个action,但却能指定多个category。),打开AndroidManifest.xml,添加如下代码:
1
2
3
4
5
6
7
|
<
activity
android:name
=
".SecondActivity"
>
<
intent-filter
>
<
action
android:name
=
"com.example.activitytest.ACTION_START"
/>
<
category
android:name
=
"android.intent.category.DEFAULT"
/>
<
category
android:name
=
"com.example.activitytest.MY_CATEGORY"
/>
</
intent-filter
>
</
activity
>
|
在触发点使用intent:
1
2
3
|
Intent intent =
new
Intent(
"com.example.activitytest.ACTION_START"
);
intent.addCategory(
"com.example.activitytest.MY_CATEGORY"
);
startActivity(intent);
|
更多隐式Intent 的用法:
我们不仅可以启动自己程序内的活动,还可以启动其他程序的活动,这使得Android 多个应用程序之间的功能共享成为了可能。比如说你的应用程序中需要展示
一个网页,这时你没有必要自己去实现一个浏览器(事实上也不太可能),而是只需要调用系统的浏览器来打开这个网页就行了。
在触发点添加如下代码:
1
2
3
|
Intent intent =
new
Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse(
"http://www.baidu.com"
));
startActivity(intent);
|
这里我们首先指定了Intent 的action 是Intent.ACTION_VIEW,这是一个Android 系统内置的动作,其常量值为android.intent.action.VIEW。然后通过Uri.parse()方法,将一个网址字符串解析成一个Uri 对象,再调用Intent 的setData()方法将这个Uri 对象传递进去。
( Intent 的setData()方法它接收一个Uri 对象,主要用于指定当前Intent 正在操作的数据,而这些数据通常都是以字符串的形式传入到Uri.parse()方法中解析产生的。)
与此对应,我们还可以在<intent-filter>标签中再配置一个<data>标签,用于更精确地指定当前活动能够响应什么类型的数据。
<data>标签中主要可以配置以下内容:
1. android:scheme
用于指定数据的协议部分,如上例中的http 部分。
2. android:host
用于指定数据的主机名部分,如上例中的www.baidu.com 部分。
3. android:port
用于指定数据的端口部分,一般紧随在主机名之后。
4. android:path
用于指定主机名和端口之后的部分,如一段网址中跟在域名之后的内容。
5. android:mimeType
用于指定可以处理的数据类型,允许使用通配符的方式进行指定。
只有<data>标签中指定的内容和Intent 中携带的Data 完全一致时,当前活动才能够响应该Intent。不过一般在<data>标签中都不会指定过多的内容,如上面浏览器示例中,其实只需要指定android:scheme 为http,就可以响应所有的http 协议的Intent 了。
接下来自定义一个可以响应http协议intent的活动,在其activity标签里添加如下代码:
1
2
3
4
5
|
<
intent-filter
>
<
action
android:name
=
"android.intent.action.VIEW"
/>
<
category
android:name
=
"android.intent.category.DEFAULT"
/>
<
data
android:scheme
=
"http"
/>
</
intent-filter
>
|
运行可以发现浏览器和该活动都可以响应该intent。
除了http 协议外,我们还可以指定很多其他协议,比如geo 表示显示地理位置、tel 表示拨打电话。
下面的代码展示了如何在我们的程序中调用系统拨号界面,在触发点添加如下代码:
1
2
3
|
Intent intent =
new
Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse(
"tel:10086"
));
startActivity(intent);
|
发邮件:
1
2
3
|
Intent intent =
new
Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);
|
向下一个活动传递数据:
Intent 中提供了一系列putExtra()方法的重载,可以把我们想要传递的数据暂存在Intent 中,启动了另一个活动后,只需要把这些数据再从Intent 中取出就可以了。putExtra()方法接收两个参数,第一个参数是键,用于后面从Intent中取值,第二个参数才是真正要传递的数据。
在触发点添加如下代码:
1
2
3
4
|
String data =
"Hello SecondActivity"
;
Intent intent =
new
Intent(FirstActivity.
this
, SecondActivity.
class
);
intent.putExtra(
"extra_data"
, data);
startActivity(intent);
|
然后我们在SecondActivity 中的onCreate()方法将传递的数据取出,这里由于我们传递的是字符串,所以使用getStringExtra()方法来获取传递的数据,如果传递的是整型数据,则使用getIntExtra()方法,传递的是布尔型数据,则使用getBooleanExtra()方法,以此类推:
1
2
3
|
Intent intent = getIntent();
String data = intent.getStringExtra(
"extra_data"
);
Log.d(
"SecondActivity"
, data);
|
返回数据给上一个活动:
返回上一个活动只需要按一下Back 键就可以了,并没有一个用于启动活动Intent 来传递数据。通过查阅文档你会发现,Activity 中还有一个startActivityForResult()方法也是用于启动活动的,但这个方法期望在活动销毁的时候能够返回一个结果给上一个活动。该方法接收两个参数,第一个参数还是Intent,第二个参数是请求码,用于在之后的回调中判断数据的来源。
在触发点添加如下代码:
1
2
|
Intent intent =
new
Intent(FirstActivity.
this
, SecondActivity.
class
);
startActivityForResult(intent,
1
);
|
这里我们使用了startActivityForResult()方法来启动SecondActivity,请求码只要是一个唯一值就可以了,这里传入了1。接下来我们在SecondActivity 中给按钮注册点击事件,并在点击事件中添加返回数据的逻辑,代码如下所示:
1
2
3
4
|
Intent intent =
new
Intent();
intent.putExtra(
"data_return"
,
"Hello FirstActivity"
);
setResult(RESULT_OK, intent);
finish();
|
可以看到,我们还是构建了一个Intent,只不过这个Intent 仅仅是用于传递数据而已,它没有指定任何的“意图”。紧接着把要传递的数据存放在Intent 中,然后调用了setResult()方法。这个方法非常重要,是专门用于向上一个活动返回数据的。setResult()方法接收两个参数, 第一个参数用于向上一个活动返回处理结果, 一般只使用RESULT_OK 或RESULT_CANCELED 这两个值,第二个参数则是把带有数据的Intent 传递回去,然后调用了finish()方法来销毁当前活动。
由于我们是使用startActivityForResult()方法来启动SecondActivity 的,在SecondActivity被销毁之后会回调上一个活动的onActivityResult()方法,因此我们需要在FirstActivity 中重写这个方法来得到返回的数据,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
|
@Override
protected
void
onActivityResult(
int
requestCode,
int
resultCode, Intent data) {
switch
(requestCode) {
case
1
:
if
(resultCode == RESULT_OK) {
String
returnedData = data.getStringExtra(
"data_return"
);
Log.d(
"FirstActivity"
, returnedData);
}
break
;
default
:
}
}
|
如果用户通过按下Back 键回到FirstActivity,我们可以通过重写onBackPressed()方法来解决这个问题,代码如下所示:
1
2
3
4
5
6
7
|
@Override
public
void
onBackPressed() {
Intent intent =
new
Intent();
intent.putExtra(
"data_return"
,
"Hello FirstActivity"
);
setResult(RESULT_OK, intent);
finish();
}
|
活动的生命周期:
Android 是使用任务(Task)来管理活动的,一个任务就是一组存放在栈里的活动的集合,这个栈也被称作返回栈(Back Stack)。每当我们按下Back 键或调用finish()方法去销毁一个活动时,处于栈顶的活动会出栈,这时前一个入栈的活动就会重新处于栈顶的位置。系统总是会显示处于栈顶的活动给用户。
四种状态:运行、暂停、停止、销毁;
七个回调方法:onCreate()、onStart()、onResume()、onPause()、 onStop()、onDestroy()、onRestart();
以上七个方法中除了onRestart()方法,其他都是两两相对的,从而又可以将活动分为三种生存期:完整生存期、可见生存期、前台生存期;
活动被回收了怎么办:
onSaveInstanceState()回调方法,这个方法会保证一定在活动被回收之前调用,因此我们可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。
onSaveInstanceState()方法会携带一个Bundle 类型的参数,Bundle 提供了一系列的方法用于保存数据,比如可以使用putString()方法保存字符串,使用putInt()方法保存整型数据,以此类推。每个保存方法需要传入两个参数,第一个参数是键,用于后面从Bundle 中取值,第二个参数是真正要保存的内容。
使用方法:
1
2
3
4
5
6
|
@Override
protected
void
onSaveInstanceState(Bundle outState) {
super
.onSaveInstanceState(outState);
String tempData =
"Something you just typed"
;
outState.putString(
"data_key"
, tempData);
}
|
当活动被系统回收之前有通过onSaveInstanceState()方法来保存数据的话,onCreate()方法的Bundle 类型的参数就会带有之前所保存的全部数据,我们只需要再通过相应的取值方法将数据取出即可:
1
2
3
4
|
if
(savedInstanceState !=
null
) {
String tempData = savedInstanceState.getString(
"data_key"
);
Log.d(TAG, tempData);
}
|
提醒一点,Intent 还可以结合Bundle 一起用于传递数据的,首先可以把需要传递的数据都保存在Bundle 对象中,然后再将Bundle 对象存放在Intent 里。到了目标活动之后先从Intent 中取出Bundle,再从Bundle中一一取出数据。
活动的启动模式:
启动模式一共有四种,分别是standard(默认)、singleTop、singleTask 和singleInstance , 可以在AndroidManifest.xml 中通过给<activity> 标签指定android:launchMode 属性来选择启动模式。如:android:launchMode="singleTask"
在standard 模式(即默认情况)下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。对于使用standard 模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,每次启动都会创建该活动的一个新的实例。
测试:在活动中启动自身,并打印类信息;发现会产生多个实例;
在singleTop模式下,在启动活动时如果发现返回栈的栈顶已经是该活动,则认为可以直接使用它,不会再创建新的活动实例。
测试:在活动中启动自身,并打印类信息;发现只有一个实例;
在singleTask模式下,每次启动该活动时系统首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在则直接使用该实例,并把在这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。
测试:在活动1中启动活动2,在活动2中启动活动1,并打印类信息和活动1的onRestart()方法;发现活动1只有一个实例;
在singleInstance 模式下,活动会启用一个新的返回栈来管理这个活动(其实如果singleTask 模式指定了不同的taskAffinity,也会启动一个新的返回栈)。假设我们的程序中有一个活动是允许其他程序调用的,如果我们想实现其他程序和我们的程序可以共享这个活动的实例,应该如何实现呢?使用前面三种启动模式肯定是做不到的,因为每个应用程序都会有自己的返回栈,同一个活动在不同的返回栈中入栈时必然是创建了新的实例。而使用singleInstance 模式就可以解决这个问题,在这种模式下会有一个单独的返回栈来管理这个活动,不管是哪个应用程序来访问这个活动,都共用的同一个返回栈,也就解决了共享活动实例的问题。
测试:将活动2设为singleInstance模式,在活动1中启动活动2,在活动2中启动活动3,并打印各自返回栈id;发现活动1和3处于同一个返回栈,活动2启用新的返回栈;同时在活动3中按back键会返回到活动1,再按一次返回到活动2,再按一次退出程序;
几种关于活动的最佳实践技巧:
知晓当前是在哪一个活动:
新建一个BaseActivity 继承自Activity(抽象Activity不需要注册,会提示不是具体的类),然后在BaseActivity 中重写onCreate()方法,如下所示:
1
2
3
4
5
6
7
|
public
class
BaseActivity
extends
Activity {
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
Log.d(
"BaseActivity"
, getClass().getSimpleName());
}
}
|
接下来我们需要让BaseActivity 成为ActivityTest 项目中所有活动的父类。现在每当我们进入到一个活动的界面,该活动的类名就会被打印出来。
随时随地退出程序:
需要用一个专门的集合类对所有的活动进行管理,新建一个ActivityCollector 类作为活动管理器:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
class
ActivityCollector {
public
static
List<Activity> activities =
new
ArrayList<Activity>();
public
static
void
addActivity(Activity activity) {
activities.add(activity);
}
public
static
void
removeActivity(Activity activity) {
activities.remove(activity);
}
public
static
void
finishAll() {
for
(Activity activity : activities) {
if
(!activity.isFinishing()) {
activity.finish();
}
}
}
}
|
接下来修改BaseActivity 中的代码,在BaseActivity 的onCreate()方法中调用ActivityCollector 的addActivity()方法,表明将当前正在创建的活动添加到活动管理器里。然后在BaseActivity 中重写onDestroy()方法,并调用了ActivityCollector 的removeActivity()方法,表明将一个马上要销毁的活动从活动管理器里移除。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public
class
BaseActivity
extends
Activity {
@Override
protected
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
Log.d(
"BaseActivity"
, getClass().getSimpleName());
//
ActivityCollector.addActivity(
this
);
}
@Override
protected
void
onDestroy() {
super
.onDestroy();
///
ActivityCollector.removeActivity(
this
);
}
}
|
从此以后,不管你想在什么地方退出程序,只需要在触发点调用ActivityCollector.finishAll()方法就可以了。当然你还可以在销毁所有活动的代码后面再加上杀掉当前进程的代码,以保证程序完全退出。
启动活动的最佳写法:
在要启动的Activity中添加一个actionStart()方法,在这个方法中完成了Intent 的构建,另外所有需要的数据都是通过actionStart()方法的参数传递过来的,然后把它们存储到Intent 中,最后调用startActivity()方法启动Activity。
1
2
3
4
5
6
|
public
static
void
actionStart(Context context, String data1, String data2) {
Intent intent =
new
Intent(context, SecondActivity.
class
);
intent.putExtra(
"param1"
, data1);
intent.putExtra(
"param2"
, data2);
context.startActivity(intent);
}
|
这样写的好处就是一目了然,Activity 所需要的数据全部都在方法参数中体现出来了,这样即使不用阅读Activity 中的代码,或者询问负责编写Activity 的同事,你也可以非常清晰地知道启动SecondActivity 需要传递哪些数据。另外,这样写还简化了启动活动的代码,现在只需要一行代码就可以启动Activity,如下所示:
1
|
SecondActivity.actionStart(FirstActivity.
this
,
"data1"
,
"data2"
);
|
【练习】乘法计算器:
1.需要两个Activity
2.声明所有所需控件
3.为其中的文本和按钮设置值
法一,setText方法直接写死;
法二,先在string.xml声明再用setText方法
4.创建监听器类并注册
关键代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Button btn1 = (Button)findViewById(R.id.btn1);
btn1.setOnClickListener(
new
OnClickListener(){
@Override
public
void
onClick(View v) {
EditText ev1 = (EditText)findViewById(R.id.ev1);
EditText ev2 = (EditText)findViewById(R.id.ev2);
Intent intent =
new
Intent();
intent.putExtra(
"param1"
,ev1.getText().toString());
intent.putExtra(
"param2"
,ev2.getText().toString());
intent.setClass(MainActivity.
this
, SecondActivity.
class
);
startActivity(intent);
MainActivity.
this
.startActivity(intent);
}
});
|
1
2
3
4
5
6
|
TextView tv = (TextView)findViewById(R.id.tv);
Intent intent = getIntent();
String data1 = intent.getStringExtra(
"param1"
);
String data2 = intent.getStringExtra(
"param2"
);
int
r = Integer.parseInt(data1)*Integer.parseInt(data2);
tv.setText(r +
""
);
|