0%

Activity

Activity概述

活动代表了一个具有用户界面的单一屏幕,如 Java 的窗口或者帧。Android 的活动是 ContextThemeWrapper 类的子类。
它是一种可以包含用户界面组件,主要用于和用户进行交互。

AndroidManifest文件

AndroidManifest.xml 是每个android程序中必须的文件。它位于整个项目的根目录,描述了package中暴露的组件(activities, services, 等等),他们各自的实现类,各种能被处理的数据和启动位置。 除了能声明程序中的Activities, ContentProviders, Services, 和Intent Receivers,还能指定permissions和instrumentation(安全控制和测试)

活动与任务

Android将这些活动保持在同一个任务(task)中以维持用户的体验。简单地讲,任务是用户体验上的一个“应用程序”,是排成堆栈的一组相关活动。栈底的活动(根活动)是起始活动——一般来讲,它是用户在应用程序启动器(也称应用程序列表,下同)中选择的一个活动。栈顶的活动是正在运行的活动——它关注用户的行为(操作)。当一个活动启动另一个,新的活动被压入栈顶,变为正在运行的活动。前面那个活动保存在栈中。当用户点击返回按钮时,当前活动从栈顶中弹出,且前面那个活动恢复成为正在运行的活动。

一个任务的所有活动作为一个整体运行。整个任务(整个活动栈)可置于前台或发送到后台。例如,假设当前任务有四个活动在栈中——三个活动在当前活动下面。用户按下HOME键,切换到程序启动器,并选择一个新的应用程序(实际上是一个新的任务)。当前任务进入后台,新任务的根活动将显示。接着,过了一会,用户回到主屏幕并再次选择之前的应用程序(之前的任务)。那个任务栈中的所有四个活动都变为前台运行。当用户按下返回键时,不是离开当前任务回到之前任务的根活动。相反,栈顶的活动被移除且栈中的下一个活动将显示。

上面所描述的是活动和任务的默认行为,但是有方法来改变所有这些行为。活动与任务之间的联系及任务中活动的行为,是由启动活动的Intent对象的标志(flags)和清单文件中活动元素的属性共同决定的。

菜单menu

https://www.cnblogs.com/HDK2016/p/8038908.html

https://blog.csdn.net/aiynmimi/article/details/54964945

菜单的分类

菜单是Android应用中非常重要且常见的组成部分,主要可以分为三类:选项菜单、上下文菜单/上下文操作模式以及弹出菜单。它们的主要区别如下:

  • 选项菜单是一个应用的主菜单项,用于放置对应用产生全局影响的操作,如搜索/设置。

  • 上下文菜单是用户长按某一元素时出现的浮动菜单。它提供的操作将影响所选内容,主要应用于列表中的每一项元素(如长按列表项弹出删除对话框)。上下文操作模式将在屏幕顶部栏(菜单栏)显示影响所选内容的操作选项,并允许用户选择多项,一般用于对列表类型的数据进行批量操作。

  • 弹出菜单以垂直列表形式显示一系列操作选项,一般由某一控件触发,弹出菜单将显示在对应控件的上方或下方。它适用于提供与特定内容相关的大量操作。

选项菜单

当用户单击设备上的菜单按钮(Menu),触发事件弹出的菜单就是选项菜单。

效果图:

实现过程讲解:

在Activity中重写onCreateOptionsMenu()来创建选项菜单,在中包含了getMenuInflater().inflate(R.menu.main,menu),R.menu.main是res的menu文件夹下的xml文件是放菜单的文件夹;

实现代码:

设置菜单项可以通过两种方法

1.通过在XML文件中添加控件来实现

在R.menu.main的xml文件中,添加item控件来添加设置菜单项;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.jiapeng.munedemo.MainActivity" >

<item
android:id="@+id/mune_enter"
android:orderInCategory="100"
android:title="登录"
app:showAsAction="never"/>
<item
android:id="@+id/mune_setting"
android:orderInCategory="100"
android:title="设置"
app:showAsAction="never"/>
<item
android:id="@+id/mune_out"
android:orderInCategory="100"
android:title="退出"
app:showAsAction="never"/>
</menu>

其中:showAsAction主要是针对这个菜单的显示起作用的,它有三个可选项
always:总是显示在界面上
never:不显示在界面上,只让出现在右边的三个点中
ifRoom:如果有位置才显示,不然就出现在右边的三个点中

java代码和设置监听:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public boolean onCreateOptionsMenu(Menu menu) {
//导入菜单布局
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

public boolean onOptionsItemSelected(MenuItem item) {
//创建菜单项的点击事件
switch (item.getItemId()) {
case R.id.mune_enter:
Toast.makeText(this, "点击了登陆", Toast.LENGTH_SHORT).show();
break;
case R.id.mune_setting:
Toast.makeText(this, "点击了设置", Toast.LENGTH_SHORT).show();

break;
case R.id.mune_out:
Toast.makeText(this, "点击了退出", Toast.LENGTH_SHORT).show();
break;

default:
break;
}

return super.onOptionsItemSelected(item);
}

2、通过动态代码实现

1
2
3
4
5
6
menu.add(groupId,itemId,order,title),

groupId--1:分组的id;
itemId--100:菜单项的id;
order--1:菜单项排序用的;
title--"菜单1":菜单名称;

menu的用法类似于ArrayList,可以调用add方法来加载如
//API大于等于11 时 Item图标不显示
menu.add(1,100,1,”菜单一”);
menu.add(1,101,1,”菜单二”);
menu.add(1,102,1,”菜单三”);
add方法返回的是item,可以赋值给item,再调用item的setTitle与setIcon(在API>=11时,是不显示图标的)来设置item。

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public boolean onCreateOptionsMenu(Menu menu) {
// groupId--1:分组的id;itemId--100:菜单项的id;order--1:菜单项排序用的;title--"菜单1":菜单名称;
MenuItem item = menu.add(1, 100, 1, "菜单项");
item.setTitle("我是一个菜单");
// 在API>=11时,是不显示图标的
item.setIcon(R.drawable.ic_launcher);
menu.add(1, 101, 1, "登录");
menu.add(1, 102, 1, "设置");
menu.add(1, 103, 1, "退出");

return true;
}

public boolean onOptionsItemSelected(MenuItem item) {
// 创建菜单项的点击事件
switch (item.getItemId()) {
case 101:
Toast.makeText(this, "你点击了登录", Toast.LENGTH_SHORT).show();
break;
case 102:
Toast.makeText(this, "你点击了设置", Toast.LENGTH_SHORT).show();
break;
case 103:
Toast.makeText(this, "你点击了退出", Toast.LENGTH_SHORT).show();
break;

default:
break;
}

return super.onOptionsItemSelected(item);
}

上下文菜单(context menu)

当用户长按Activity页面时,弹出的菜单我们称为上下文菜单。我们经常在Windows中用鼠标右键单击弹出的菜单就是上下文菜单。

ContextMenu与OptionMenu的区别:
1、OptionMenu对应的是activity,一个activity只能拥有一个选项菜单;
2、ContextMenu对应的是view,每个view都可以设置上下文菜单;
3、一般情况下ContextMenu常用语ListView或者GridView

实现步骤:

(1)首先给View注册上下文菜单registerForContextMenu()
this.registerForContextMenu(contextView);
(2)添加上下文菜单的内容onCreateContextMenu()

效果图:

img

img

img

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jiapeng.munedemo.MainActivity"
tools:ignore="MergeRootFrame" >

<ListView
android:id="@+id/mune_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
</ListView>

</FrameLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
showListView();
// 注册上下文菜单
this.registerForContextMenu(listview);
}

/**
* 加载数据
*/
private void showListView() {
listview = (ListView) findViewById(R.id.mune_list);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_list_item_1, getDate());
listview.setAdapter(adapter);

}

/**
* 创建数据源
*
* @return list
*/
private ArrayList<String> getDate() {
ArrayList<String> list = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
list.add("菜单" + i);
}
return list;
}

/**
* 添加上下文菜单的菜单项
*/
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
menu.setHeaderTitle("上下文菜单");
menu.setHeaderIcon(R.drawable.ic_launcher);
//加载上下文菜单内容
menu.add(1, 1, 1, "保存");
menu.add(1, 2, 1, "更改");
menu.add(1, 3, 1, "删除");
super.onCreateContextMenu(menu, v, menuInfo);
}

/**
* 创建单击事件
*/
public boolean onContextItemSelected(MenuItem item) {
switch (item.getItemId()) {
case 1:
Toast.makeText(this, "点击了保存", Toast.LENGTH_SHORT).show();
break;
case 2:
Toast.makeText(this, "点击了更改", Toast.LENGTH_SHORT).show();
break;
case 3:
Toast.makeText(this, "点击了删除", Toast.LENGTH_SHORT).show();
break;

default:
break;
}
return super.onContextItemSelected(item);
}

注:何为上下文菜单:Windows操作系统中任何地方右击鼠标会出现俗称的“右键菜单”,其实就是指上下文菜单。因为上下文菜单根据鼠标位置来判断弹出什么的菜单(如桌面右击显示个性化菜单,文件右击则显示针对文件操作删除等的菜单)也就是根据上下文来判断如何弹出和弹出哪种菜单,所以称为上下文菜单。手机上就是长按会弹出选项

子菜单(Sub Menu)

就是将功能相同的操作分组显示,他作用在OptionsMenu上,是OptionsMenu的二级菜单

实现步骤:

(1)重写onCreateOptionsMenu()方法
(2)点击事件,重写onOptionsItemSelected()方法

注意:

(1)SubMenu.add(groupId, itemId, order, title);
因为每个SubMenu有一个groupId,所以需要使用这个groupId区别是点击了那个子菜单
(2)APP的样式会影响子菜单的显示风格

效果图

img

img

img

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.jiapeng.munedemo.MainActivity"
tools:ignore="MergeRootFrame" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="SubMenu"
android:textSize="30sp" />

</FrameLayout>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Override
public boolean onCreateOptionsMenu(Menu menu) {
SubMenu fileMenu = menu.addSubMenu("查看文件");
SubMenu editMenu = menu.addSubMenu("输入文件");
//添加菜单项
fileMenu.add(1, 1, 1, "文件1");
fileMenu.add(1, 2, 1, "文件2");
fileMenu.add(1, 3, 1, "文件3");
editMenu.add(2, 1, 1, "输入1");
editMenu.add(2, 2, 1, "输入2");
editMenu.add(2, 3, 1, "输入3");
return super.onCreateOptionsMenu(menu);
}

//创建点击事件
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getGroupId() == 1) {
switch (item.getItemId()) {
case 1:
Toast.makeText(this, "点击了文件1", Toast.LENGTH_SHORT).show();
break;
case 2:
Toast.makeText(this, "点击了文件2", Toast.LENGTH_SHORT).show();
break;
case 3:
Toast.makeText(this, "点击了文件3", Toast.LENGTH_SHORT).show();
break;

default:
break;
}
} else if (item.getGroupId() == 2) {
switch (item.getItemId()) {
case 1:
Toast.makeText(this, "点击了输入1", Toast.LENGTH_SHORT).show();
break;
case 2:
Toast.makeText(this, "点击了输入2", Toast.LENGTH_SHORT).show();
break;
case 3:
Toast.makeText(this, "点击了输入3", Toast.LENGTH_SHORT).show();
break;

default:
break;
}
}
return super.onOptionsItemSelected(item);
}
  • android:id
    定义资源ID,它是个唯一值,使用“@+id/name”格式可以给这个菜单项创建一个新的资源ID,“+”号指示要创建一个新的ID。

  • android:title
    字符串资源,它用字符串资源或原始的字符串来定义菜单的标题。

  • android:titleCondensed
    字符串资源。它用字符串资源或原始的字符串来定义一个简要的标题,以便在普通的标题太长时来使用。

  • android:icon
    可绘制资源,它定义了一个菜单项所要使用的图标。

  • android:onClick
    方法名。在这个菜单项被点击时,会调用这个方法。在Activity中,这个方法必须用public关键字来声明,并且只接受一个MenuItem对象,这个对象指明了被点击的菜单项。这个方法会优先标准的回调方法:onOptionsItemSelected()。
    警告:如果要使用ProGuard(或类似的工具)来混淆代码,就要确保不要重名这个属性所指定的方法,因为这样能够破坏功能。
    这个属性在API级别11中被引入。

  • android:showAsAction
    关键词。它定义这个项目作为操作栏中的操作项的显示时机和方式。只用Activity包含了一个ActionBar对象时,菜单项才能够作为操作项来显示。这个属性在API级别11中被引入,有效值如下:
    值 说明
    ifRoom 如果有针对这个项目的空间,则只会把它放到操作栏中
    withText 操作项也要包含文本(通过android:title属性来定义的)。可以把这个值与其他的Flag设置放到一起,通过管道符“|”来分离它们。
    never 这个项目不会放到操作栏中
    always
    始终包这个项目放到操作栏中。要避免使用这个设置,除非在操作栏中始终显示这个项目是非常关键的。设置多个项目作为始终显示的操作项会导
    致操作栏中其他的UI溢出。
    icollapseActiionView 它定义了跟这个操作项关联的可折叠的操作View对象(用android:actionViewLayout来声明)。这个关键词在API级别14中被引入。

  • android:actionViewLayout
    它引用一个布局资源,这个布局要用于操作窗口。更多的信息请参照“操作栏”开发指南。这个属性在API级别11中被引入。

  • android:actionViewClass
    类名。它定义了操作窗口要使用的View对象的完整的类名。例如,“android.widget.SearchView”说明操作窗口要使用的SearchView类。
    警告:如果要使用ProGuard(或类似的工具)来混淆代码,就要确保不要重名这个属性所指定的方法,因为这样能够破坏功能。
    这个属性在API级别11中被引入。

  • android:actionProviderClass

    类名,它是操作项目所使用的ActionProvider类的完整的类名。例如,“android.widget.ShareActionProvider”说明要使用
    ShareActionProvider类。
    警告:如果要使用ProGuard(或类似的工具)来混淆代码,就要确保不要重名这个属性所指定的方法,因为这样能够破坏功能。
    这个属性在API级别14中被引入。

  • android:alphabeticShortcut
    字符,定义一个字符快捷键

  • android:numericShortcut
    数字值,定义一个数字快捷键

  • android:checkable
    布尔值,如果菜单项是可以复选的,那么就设置为true。

  • android:checked
    布尔值,如果复选菜单项默认是被选择的,那么就设置为true。

  • android:visible
    布尔值,如果菜单项默认是可见的,那么就设置为true。

  • android:enabled
    布尔值,如果菜单项目默认是可用的,那么就设置为true。

  • android:menuCategory
    关键词。它的值对应了定义菜单项优先级的CATEGORE_*常量,有效值如下:
    值 说明
    Container 菜单项是容器的一部分
    system 菜单项是由系统提供的。
    secondary 提供给用户的辅助选择的菜单项(很少使用)
    alternative 基于当前显示的数据来选择操作的菜单项。

  • android:orderInCategory
    整数值,它定义菜单项在菜单组中的重要性的顺序。

Group

<group>
它定义了一个菜单组(它是一个具有共同特征的菜单项的组合,如菜单项的可见性、可用性或可复选性)。它要包含多个<item>元素,而且必须是<menu>元素的子元素。

属性(ATTRIBUTES):

  • android:id
    资源ID。它是资源的唯一标识。使用“@+id/name”格式给菜单项创建一个新的资源ID。“+”号指示应该给这个元素创建一个新的资源ID。

  • android:checkableBeharior
    关键词。针对菜单组的可复选行为的类型。有效值如下:
    值 说明
    none 没有可复选性
    all 组内的所有的项目都被复选(使用复选框)
    single 仅有一个项目能够被复选(使用单选按钮)

  • android:visible

    布尔值,如果菜单组是可见的,就设置为true。

  • android:enabled
    布尔值,如果菜单组是可用的,就设置为true。

  • android:menuCategory
    关键词。它的值对应了Menu类的CATEGORY_*常量,定义了菜单组的优先级。有效值如下:
    值 说明
    container 菜单组是容器的一部分
    system 菜单组是由系统提供的。
    secondary 提供给用户的辅助选择的菜单组(很少使用)
    alternative 基于当前显示的数据来选择操作的菜单组。

  • android:orderInCategory
    整数值,它定义了分类中菜单项目的默认顺序。

使用XML定义menu

理论上而言,使用XML和Java代码都可以创建Menu。但是在实际开发中,往往通过XML文件定义Menu,这样做有以下几个好处:

  1. 使用XML可以获得更清晰的菜单结构
  2. 将菜单内容与应用的逻辑代码分离
  3. 可以使用应用资源框架,为不同的平台版本、屏幕尺寸创建最合适的菜单(如对drawable、string等系统资源的使用)

要定义Menu,我们首先需要在res文件夹下新建menu文件夹,它将用于存储与Menu相关的所有XML文件。
我们可以使用

三种XML元素定义Menu,下面简单介绍一下它们:

  1. ”是菜单项的容器。元素必须是该文件的根节点,并且能够包含一个或多个元素。
  2. 是菜单项,用于定义MenuItem,可以嵌套元素,以便创建子菜单。
  3. 元素的不可见容器(可选)。可以使用它对菜单项进行分组,使一组菜单项共享可用性和可见性等属性。

其中,是我们主要需要关注的元素,它的常见属性如下:

  • android:id:菜单项(MenuItem)的唯一标识
  • android:icon:菜单项的图标(可选)
  • android:title:菜单项的标题(必选)
  • android:showAsAction:指定菜单项的显示方式。常用的有ifRoom、never、always、withText,多个属性值之间可以使用|隔开。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<item android:id="@+id/option_normal_1"
android:icon="@mipmap/ic_vpn_key_white_24dp"
android:title="普通菜单1"
app:showAsAction="ifRoom"/>

<item android:id="@+id/option_normal_2"
android:icon="@mipmap/ic_email_white_24dp"
android:title="普通菜单2"
app:showAsAction="always"/>

<item android:id="@+id/option_normal_3"
android:icon="@mipmap/ic_vpn_key_white_24dp"
android:title="普通菜单3"
app:showAsAction="withText|always"/>

<item android:id="@+id/option_normal_4"
android:title="普通菜单4"
app:showAsAction="never"/>
</menu>

菜单栏中的菜单项会分为两个部分。一部分可以直接在菜单栏中看见,我们可以称之为常驻菜单;另一部分会被集中收纳到溢出菜单中(就是菜单栏右侧的小点状图标)。一般情况下,常驻菜单项以图标形式显示(需要定义icon属性),而溢出菜单项则以文字形式显示(通过title属性定义)。showAsAction的差异如下所示:

  1. always:菜单项永远不会被收纳到溢出菜单中,因此在菜单项过多的情况下可能超出菜单栏的显示范围。
  2. ifRoom:在空间足够时,菜单项会显示在菜单栏中,否则收纳入溢出菜单中。
  3. withText:无论菜单项是否定义了icon属性,都只会显示它的标题,而不会显示图标。使用这种方式的菜单项默认会被收纳入溢出菜单中。
  4. never:菜单项永远只会出现在溢出菜单中。

java加载代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater=getMenuInflater();
inflater.inflate(R.menu.option_menu_normal,menu);
return true;
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.option_normal_1:
return true;
case R.id.option_normal_2:
return true;
case R.id.option_normal_3:
return true;
case R.id.option_normal_4:
return true;
default:
return super.onOptionsItemSelected(item);
}
}

可以看见,我们在Activity中重写了onCreateOptionsMenu方法,在这个方法中完成加载Menu资源的操作,关键代码如下:

1
2
3
4
//获取MenuInflater
MenuInflater inflater=getMenuInflater();
//加载Menu资源
inflater.inflate(R.menu.option_menu_normal,menu);

需要注意的是,这个方法必须返回true,否则Menu将不会显示。

获取MenuInflater

LayoutInflater是用来实例化整个布局文件,而 MenuInflater是用来实例化Menu目录下的Menu布局文件的。
传统意义上的菜单定义需要Override Activity的onCreateOptionsMenu,然后在里面调用Menu.add把Menu的一个个item加进来,比较复杂。而通过使用MenuInflater可以把Menu的构造直接放在Menu布局文件中,真现模型(Model)与视图(View)的分离,程序也看着清爽多了。

几点说明:

与LayoutInflater相比,MenuInflater的用法简单多了。首先,MenuInflater获取方法只有一种:Activity.getMenuInflater();其次,MenuInflater.inflater(int menuRes,Menu menu)(这里不代表inflater就是static方法,可以这样调用,只是为了描述方便)的返回值是void型,这就决定了MenuInflater.inflater后就没有后续操作了。这说明通过这种方式把Menu布局文件写好后就不能在程序中动态修改了,而不像LayoutInflater.inflater那样,返回值是View型,可以进行后续的进一步操作。另外,MenuInflater只有一个void inflater(int menuRes,Menu menu)非构造方法。

包含多级子菜单的选项菜单

是可以嵌套

的,而又是的容器。因此,我们可以在应用中实现具有层级结构的子菜单。下面给出一个实际的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<item android:id="@+id/option_sub_file"
android:title="文件"
app:showAsAction="ifRoom">
<menu>
<item android:id="@+id/file_new"
android:title="新建"/>
<item android:id="@+id/file_save"
android:title="保存"/>

<item android:id="@+id/file_more"
android:title="更多">
<menu>
<item android:id="@+id/file_more_1"
android:title="更多1"/>
<item android:id="@+id/file_more_2"
android:title="更多2"/>

<item android:id="@+id/file_more_more"
android:title="更多更多">
<menu>
<item android:id="@+id/file_more_more_1"
android:title="更多更多1"/>
<item android:id="@+id/file_more_more_2"
android:title="更多更多2"/>
</menu>
</item>
</menu>
</item>
</menu>
</item>
</menu>

intent

Intent中文意思指”意图”,按照Android的设计理念,Android使用Intent来封装程序的”调用意图”,不管启动Activity、Service、BroadcastReceiver,Android都使用统一的Intent对象来封装这一”启动意图”。此外,Intent也是应用程序组件之间通信的重要媒介。在Android中指定的了具体是某个组件,那么就是显性意图;如果只是提出要求没有指定具体的某个人,在Android中即没有指定某个具体的组件,那么就是隐式意图;所有Intent页面跳转的方式又分为显示跳转和隐式跳转。

Intent和三大组件

Android应用程序包含三种重要组件:Activity、Service、BroadcastReceiver,应用程序采用一致的方式启动它们,都是依靠Intent来进行启动的,Intent中封装了程序要启动的意图。

下面是Intent启动不同组件的部分方法:
 Activity组件:
 startActivity(Intent intent);startActivityForResult(Intent intent,int requestCode);
 Service组件:
 startService(Intent intent);bindService(Intent intent,ServiceConnection conn,int flags);
 BroadcastReceiver组件:
 sendBroadcast(Intent intent);sendOrderedBroadcast(Intent intent,String receiverPermission);

显式(Explicit intent)和隐式(Implicit intent)。

一、显式(设置Component(组件))

显式,即直接指定需要打开的activity对应的类。
以下多种方式都是一样的,实际上都是设置Component直接指定Activity类的显式Intent,由MainActivity跳转到SecondActivity:
1、构造方法传入Component,最常用的方式

1
2
Intent intent = new Intent(this, SecondActivity.class);  
startActivity(intent);

2、setComponent方法

1
2
3
4
5
6
7
ComponentName componentName = new ComponentName(this, SecondActivity.class);  
// 或者ComponentName componentName = new ComponentName(this, "com.example.app016.SecondActivity");
// 或者ComponentName componentName = new ComponentName(this.getPackageName(), "com.example.app016.SecondActivity");

Intent intent = new Intent();
intent.setComponent(componentName);
startActivity(intent);

3、setClass/setClassName方法

1
2
3
4
5
6
Intent intent = new Intent();  

intent.setClass(this, SecondActivity.class);
// 或者intent.setClassName(this, "com.example.app016.SecondActivity");
// 或者intent.setClassName(this.getPackageName(), "com.example.app016.SecondActivity");
startActivity(intent);

显式Intent通过Component可以直接设置需要调用的Activity类,可以唯一确定一个Activity,意图特别明确,所以是显式的。设置这个类的方式可以是Class对象(如SecondActivity.class),
也可以是包名加类名的字符串(如”com.example.app016.SecondActivity”)。这个很好理解,在应用程序内部跳转界面常用这种方式。

二、隐式

隐式,即不是像显式的那样直接指定需要调用的Activity,隐式不明确指定启动哪个Activity,而是设置Action、Data、Category,让系统来筛选出合适的Activity。
筛选是根据所有的来筛选。
下面以Action为例:
AndroidManifest.xml文件中,首先被调用的Activity要有一个带有并且包含的Activity,设定它能处理的Intent,并且category设为”android.intent.category.DEFAULT”。
action的name是一个字符串,可以自定义,例如我在这里设成”abcdefg”:

1
2
3
4
5
6
7
<activity  
android:name="com.example.app016.SecondActivity">
<intent-filter>
<action android:name="abcdefg"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>

然后,在MainActivity,才可以通过这个action name找到上面的Activity。
下面两种方式分别通过setAction和构造方法方法设置Action,两种方式效果相同。
1、setAction方法

1
2
3
Intent intent = new Intent();  
intent.setAction("abcdefg");
startActivity(intent);

2、构造方法直接设置Action

1
2
Intent intent = new Intent("abcdefg");  
startActivity(intent);

通过设置Action字符串,表明自己的意图,即我想干嘛,需要由系统解析,找到能够处理这个Intent的Activity并启动。
比如我想打电话,则可以设置Action为”android.intent.action.DIAL”字符串,表示打电话的意图,系统会找到能处理这个意图的Activity,例如调出拨号面板。

有几点需要注意:
1、 这个Activity其他应用程序也可以调用,只要使用这个Action字符串。这样应用程序之间交互就很容易了,例如手机QQ可以调用QQ空间,可以调用腾讯微博等。
因为如此,为了防止应用程序之间互相影响,一般命名方式是包名+Action名,例如这里命名”abcdefg”就很不合理了,就应该改成”com.example.app016.MyTest”。
2、 当然,你可以在自己的程序中调用其他程序的Action。
例如可以在自己的应用程序中调用拨号面板:

1
2
3
4
Intent intent = new Intent(Intent.ACTION_DIAL);  
// 或者Intent intent = new Intent("android.intent.action.DIAL");
// Intent.ACTION_DIAL是内置常量,值为"android.intent.action.DIAL"
startActivity(intent);

3、一个Activity可以处理多种Action 只要你的应用程序够牛逼,一个Activity可以看网页,打电话,发短信,发邮件。。。当然可以。 Intent的Action只要是其中之一,就可以打开这个Activity。

1
2
3
4
5
6
7
8
9
10
activity  
android:name="com.example.app016.SecondActivity">
<intent-filter>
<!-- 可以处理下面三种Intent -->
<action android:name="com.example.app016.SEND_EMAIL"/>
<action android:name="com.example.app016.SEND_MESSAGE"/>
<action android:name="com.example.app016.DAIL"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

对于一个Action字符串,系统有可能会找到一个Activity能处理这个Action,也有可能找到多个Activity,也可能一个都找不到。
1、找到一个Activity
很简单,直接打开这个Activity。这个不需要解释。
2、找到多个Acyivity
系统会提示从多个activity中选择一个打开。
例如我们自己开发一个拨号面板应用程序,可以设置activity的中Action name为”android.intent.action.DIAL”,这样别的程序调用拨号器时,
用户可以从Android自带的拨号器和我们自己开发的拨号器中选择。

1
2
3
4
5
6
7
8
<activity  
android:name="com.example.app016.SecondActivity">
<intent-filter>
<action android:name="android.intent.action.DIAL"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

这也就是当Android手机装上UC浏览器后,打开网页时会弹出选择Android自带浏览器还是UC浏览器,可能都会遇到过。
3、一个Activity都没找到
一个都没找到的话,程序就会出错,会抛出ActivityNotFoundException。比如随便写一个action字符串:

1
2
Intent intent = new Intent("asasasas");  
startActivity(intent);

所以应该注意try catch异常。

1
2
3
4
5
6
7
8
9
Intent intent = new Intent("asasasas");  
try
{
startActivity(intent);
}
catch(ActivityNotFoundException e)
{
Toast.makeText(this, "找不到对应的Activity", Toast.LENGTH_SHORT).show();
}

或者也可以使用Intent的resolveActivity方法判断这个Intent是否能找到合适的Activity,如果没有,则不再startActivity,或者可以直接禁用用户操作的控件。

1
2
3
4
5
Intent intent = new Intent(Intent.ACTION_DIAL);  
if(intent.resolveActivity(getPackageManager()) == null)
{
// 设置控件不可用
}

注意resolveActivity方法返回值就是显式Intent上面讲到的ComponentName对象,一般情况下也就是系统找到的那个Activity。
但是如果有多个Activity可供选择的话,则返回的Component是com.android.internal.app.ResolverActivity,也就是用户选择Activity的那个界面对应的Activity,这里不再深究。

1
2
3
4
5
6
7
8
Intent intent = new Intent(Intent.ACTION_DIAL);  
ComponentName componentName = intent.resolveActivity(getPackageManager());
if(componentName != null)
{
String className = componentName.getClassName();
Toast.makeText(this, className, Toast.LENGTH_SHORT).show();
}

四大属性

Intent在Android中的核心作用就是“跳转”(Android中的跳转机制),同时可以携带必要的信息,将Intent作为一个信息桥梁。最熟悉的莫过于从一个活动跳转到另一个活动,然后返回到上一个活动。不过Intent的“跳转”作用不仅于此,Intent还可以在其他地方使用,比如在碎片中跳转,接受到一个广播后自动跳转。
 Intent主要有以下四个重要属性,它们分别为:

  • Action:Action属性的值为一个字符串,它代表了系统中已经定义了一系列常用的动作。通过setAction()方法或在清单文件AndroidManifest.xml中设置。默认为:DEFAULT。
  • Data:Data通常是URI格式定义的操作数据。例如:tel:// 。通过setData()方法设置。
  • Category:Category属性用于指定当前动作(Action)被执行的环境。通过addCategory()方法或在清单文件AndroidManifest.xml中设置。默认为:CATEGORY_DEFAULT。
  • Extras:Extras属性主要用于传递目标组件所需要的额外的数据。通过putExtras()方法设置。

Action:action表示该activity可以执行的动作。

  1. 自定义action,匹配Activity的功能:
    a.menifest中注册的Activity
1
2
3
4
5
6
<activity
android:name=".StudyActivity">
<intent-filter>
<action android:name="com.czh.study" />
</intent-filter>
</activity>

b.启动activity

1
2
Intent intent = new Intent("com.czh.study");
startActivity(intent)

当intent启动Activity的时候会拿着action到menifest中查找是否有activity的acitoin和我相同,如果匹配成功(即intent中的action和中的action相同)就跳转到该actvitiy.

(如果匹配的2个activity的话,系统会给你一个选择框让你选择启动那么activity)

  1. 利用系统action,调用UI

Action常用的值如下:

  • ACTION_MAIN:Android Application的入口,每个Android应用必须且只能包含一个此类型的Action声明。  
  • ACTION_VIEW:系统根据不同的Data类型,通过已注册的对应Application显示数据。
  • ACTION_EDIT:系统根据不同的Data类型,通过已注册的对应Application编辑示数据。 
  • ACTION_DIAL:打开系统默认的拨号程序,如果Data中设置了电话号码,则自动在拨号程序中输入此号码。 
  • ACTION_CALL:直接呼叫Data中所带的号码。 
  • ACTION_ANSWER:接听来电。 
  • ACTION_SEND:由用户指定发送方式进行数据发送操作。
  • ACTION_SENDTO:系统根据不同的Data类型,通过已注册的对应Application进行数据发送操作。 
  • ACTION_BOOT_COMPLETED:Android系统在启动完毕后发出带有此Action的广播(Broadcast)。 
  • ACTION_TIME_CHANGED:Android系统的时间发生改变后发出带有此Action的广播(Broadcast)。 
  • ACTION_PACKAGE_ADDED:Android系统安装了新的Application之后发出带有此Action的广播(Broadcast)。 
  • ACTION_PACKAGE_CHANGED:Android系统中已存在的Application发生改变之后(如应用更新操作)发出带有此Action的广播

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
1 从google搜索内容 
Intent intent = new Intent();
intent.setAction(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY,"searchString")
startActivity(intent);

2 浏览网页
Uri uri = Uri.parse("http://www.google.com");
Intent it = new Intent(Intent.ACTION_VIEW,uri);
startActivity(it);

3 显示地图
Uri uri = Uri.parse("geo:38.899533,-77.036476");
Intent it = new Intent(Intent.Action_VIEW,uri);
startActivity(it);

4 路径规划
Uri uri = Uri.parse("http://maps.google.com/maps?f=dsaddr=startLat%20startLng&daddr=endLat%20endLng&hl=en");
Intent it = new Intent(Intent.ACTION_VIEW,URI);
startActivity(it);

5 拨打电话
Uri uri = Uri.parse("tel:xxxxxx");
Intent it = new Intent(Intent.ACTION_DIAL, uri);
startActivity(it);

6 调用发短信的程序
Intent it = new Intent(Intent.ACTION_VIEW);
it.putExtra("sms_body", "The SMS text");
it.setType("vnd.android-dir/mms-sms");
startActivity(it);

7 发送短信
Uri uri = Uri.parse("smsto:0800000123");
Intent it = new Intent(Intent.ACTION_SENDTO, uri);
it.putExtra("sms_body", "The SMS text");
startActivity(it);
String body="this is sms demo";
Intent mmsintent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("smsto", number, null));
mmsintent.putExtra(Messaging.KEY_ACTION_SENDTO_MESSAGE_BODY, body);
mmsintent.putExtra(Messaging.KEY_ACTION_SENDTO_COMPOSE_MODE, true);
mmsintent.putExtra(Messaging.KEY_ACTION_SENDTO_EXIT_ON_SENT, true);
startActivity(mmsintent);

8 播放多媒体
Intent it = new Intent(Intent.ACTION_VIEW);
Uri uri = Uri.parse("file:///sdcard/song.mp3");
it.setDataAndType(uri, "audio/mp3");
startActivity(it);
Uri uri = Uri.withAppendedPath(MediaStore.Audio.Media.INTERNAL_CONTENT_URI, "1");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);

9 uninstall apk
Uri uri = Uri.fromParts("package", strPackageName, null);
Intent it = new Intent(Intent.ACTION_DELETE, uri);
startActivity(it);

10 install apk
Uri installUri = Uri.fromParts("package", "xxx", null);
returnIt = new Intent(Intent.ACTION_PACKAGE_ADDED, installUri);

11 打开照相机
<1>Intent i = new Intent(Intent.ACTION_CAMERA_BUTTON, null);
this.sendBroadcast(i);
<2>long dateTaken = System.currentTimeMillis();
String name = createName(dateTaken) + ".jpg";
fileName = folder + name;
ContentValues values = new ContentValues();
values.put(Images.Media.TITLE, fileName);
values.put("_data", fileName);
values.put(Images.Media.PICASA_ID, fileName);
values.put(Images.Media.DISPLAY_NAME, fileName);
values.put(Images.Media.DESCRIPTION, fileName);
values.put(Images.ImageColumns.BUCKET_DISPLAY_NAME, fileName);
Uri photoUri = getContentResolver().insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);

Intent inttPhoto = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
inttPhoto.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(inttPhoto, 10);

12 从gallery选取图片
Intent i = new Intent();
i.setType("image/*");
i.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(i, 11);

13 打开录音机
Intent mi = new Intent(Media.RECORD_SOUND_ACTION);
startActivity(mi);

14 显示应用详细列表
Uri uri = Uri.parse("market://details?id=app_id");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
//where app_id is the application ID, find the ID
//by clicking on your application on Market home
//page, and notice the ID from the address bar

刚才找app id未果,结果发现用package name也可以
Uri uri = Uri.parse("market://details?id=<packagename>");
这个简单多了

15 寻找应用
Uri uri = Uri.parse("market://search?q=pname:pkg_name");
Intent it = new Intent(Intent.ACTION_VIEW, uri);
startActivity(it);
//where pkg_name is the full package path for an application

16 打开联系人列表
<1>
Intent i = new Intent();
i.setAction(Intent.ACTION_GET_CONTENT);
i.setType("vnd.android.cursor.item/phone");
startActivityForResult(i, REQUEST_TEXT);

<2>
Uri uri = Uri.parse("content://contacts/people");
Intent it = new Intent(Intent.ACTION_PICK, uri);
startActivityForResult(it, REQUEST_TEXT);

17 打开另一程序
Intent i = new Intent();
ComponentName cn = new ComponentName("com.yellowbook.android2",
"com.yellowbook.android2.AndroidSearch");
i.setComponent(cn);
i.setAction("android.intent.action.MAIN");
startActivityForResult(i, RESULT_OK);

18 调用系统编辑添加联系人(高版本SDK有效):
Intent it = newIntent(Intent.ACTION_INSERT_OR_EDIT);
it.setType("vnd.android.cursor.item/contact");
//it.setType(Contacts.CONTENT_ITEM_TYPE);
it.putExtra("name","myName");
it.putExtra(android.provider.Contacts.Intents.Insert.COMPANY, "organization");
it.putExtra(android.provider.Contacts.Intents.Insert.EMAIL,"email");
it.putExtra(android.provider.Contacts.Intents.Insert.PHONE,"homePhone");
it.putExtra(android.provider.Contacts.Intents.Insert.SECONDARY_PHONE,
"mobilePhone");
it.putExtra( android.provider.Contacts.Intents.Insert.TERTIARY_PHONE,
"workPhone");
it.putExtra(android.provider.Contacts.Intents.Insert.JOB_TITLE,"title");
startActivity(it);

19 调用系统编辑添加联系人(全有效):
Intent intent = newIntent(Intent.ACTION_INSERT_OR_EDIT);
intent.setType(People.CONTENT_ITEM_TYPE);
intent.putExtra(Contacts.Intents.Insert.NAME, "My Name");
intent.putExtra(Contacts.Intents.Insert.PHONE, "+1234567890");
intent.putExtra(Contacts.Intents.Insert.PHONE_TYPE,Contacts.PhonesColumns.TYPE_MOBILE);
intent.putExtra(Contacts.Intents.Insert.EMAIL, "com@com.com");
intent.putExtra(Contacts.Intents.Insert.EMAIL_TYPE, Contacts.ContactMethodsColumns.TYPE_WORK);
startActivity(intent);

category

Category属性用于指定当前动作(Action)被执行的环境。

  1. 自定义category ,匹配Activity的功能:
    如果2个activity有相同的action,那么我们可以用不同的category来区分。
    a.menifest中注册的Activity
1
2
3
4
5
6
7
<activity  android:name=".StudyActivity">
<intent-filter>
<action android:name="com.czh.study" />
<category android:name="com.czh.category.study" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>

b.启动activity

1
2
3
Intent intent = new Intent("com.czh.study");
intent.addCategory("com.czh.category.study")
startActivity(intent);

解释:
1.这里的category起到了匹配的作用,系统先匹配action,如果action,再匹配cagegory,如果有相同就跳转。
2.有一点大家需要注意:

隐式启动中你必须要加上这一条。
但是也有例外:




这一个就不需要要。

  1. 利用系统category

cagegory常用的值如下:

  • CATEGORY_DEFAULT:Android系统中默认的执行方式,按照普通Activity的执行方式执行。 
  • CATEGORY_BROWSABLE:设置该组件可以使用浏览器启动。
  • CATEGORY_TAB:指定该Activity作为TabActivity的Tab页 
  • CATEGORY_LAUNCHER:设置该组件为在当前应用程序启动器中优先级最高的Activity,通常为入口ACTION_MAIN配合使用。 
  • CATEGORY_HOME:设置该组件为Home Activity。
  • CATEGORY_PREFERENCE:设置该组件为Preference。 
  • CATEGORY_GADGET:设置该组件可以内嵌到另外的Activity中。
  • CATEGORY_TEST:该Activity是一个测试
  • CATEGORY_CAR_MODE:设置该Activity可在车载环境下使用
  • CATEGORY_CAR_DOCK:指定手机被插入汽车底座(硬件)时运行该Activity
  • CATEGORY_DESK_DOCK:指定手机被插入桌面底座(硬件)时运行该Activity

extras

**extras属性主要用于传递目标组件所需要的额外的数据。通过putExtras()方法设置。 **
常用的系统extras

  • EXTRA_BCC:存放邮件密送人地址的字符串数组。
  • EXTRA_CC:存放邮件抄送人地址的字符串数组。
  • EXTRA_EMAIL:存放邮件地址的字符串数组。
  • EXTRA_SUBJECT:存放邮件主题字符串。
  • EXTRA_TEXT:存放邮件内容。
  • EXTRA_KEY_EVENT:以KeyEvent对象方式存放触发Intent的按键。
  • EXTRA_PHONE_NUMBER:存放调用ACTION_CALL时的电话号码。
    例如:
1
2
3
4
5
Intent intent  = new Intent("com.czh.study");
intent.putExtra("key", "value");
startActivity(intent);

String value = getIntent().getStringExtra("key");

data

  1. 什么是uri:

我们先得弄明白uri是什么,才能向下讲。
通用资源标志符(Universal Resource Identifier, 简称”URI”)。
Uri代表要操作的数据,Android上可用的每种资源 - 图像、视频片段等都可以用Uri来表示。换句话说:android系统中任何可用的资源(图像、视频、文件)都可以用uri表示。

  1. uri讲解:
  • uri属性有以下4部分组成:android:scheme、android:host、android:port、android:path
    其中host和port2个统称为authority。
  • 要使authority(host和port)有意义,必须指定scheme;要使path有意义,必须使scheme和authority(host和port)有意义
    举例说明:
    URI为: file://com.android.jony.test:520/mnt/sdcard,我们拆分如下:
    scheme–>file:
    host–>com.android.jony.test
    port–>520
    path–>mnt/sdcard
    authority–>com.android.jony.test:520
    图片uri:
    content://media/external/images/media/62026
    tel://:号码数据格式,后跟电话号码。
    mailto://:邮件数据格式,后跟邮件收件人地址。
    smsto://:短息数据格式,后跟短信接收号码。
    content://:内容数据格式,后跟需要读取的内容。
    file://:文件数据格式,后跟文件路径。
    market://search?q=pname:pkgname:市场数据格式,在Google Market里搜索包名为pkgname的应用。
    geo://latitude,longitude:经纬数据格式,在地图上显示经纬度指定的位置。

在intentfilter中指定data属性的实际目的是:要求接收的Intent中的data必须符合intent-filter中指定的data属性,这样达到反向限定Intent的作用。

在AndroidManifest.xml 中进行如下设置:

1
2
3
4
5
6
<activity android:name=".TestActivity">  
<intent-filter>
<action android:name="com.jony.test"/>
<data android:scheme="file"/>
</intent-filter>
</activity>

启动该Activity的Intent必须进行如下设置:

1
2
3
Intent intent = new Intent();  
Uri uri = Uri.parse("file://com.android.test:520/mnt/sdcard");
intent.setData(uri)

data讲解:

1.data属性有以下5部分组成:android:scheme、android:host、android:port、android:path、android:mimeType
data的前四个属性构成了URI的组成部分,
mimeType设置了数据的类型
2.data元素组成的URI模型如下:
scheme://host:port/path

URI和intent-filter匹配:

Intent中URI和intent-filter进行比较的时候只会进行部分的比较:
(1)当intent-filter中只设置了scheme,只会比较URI的scheme部分;
(2)当intent-filter中只设置了scheme和authority,那么只会匹配URI中的scheme和authority;
(3)当intent-filter中设置了scheme、authority和path,那么只会匹配URI中的scheme、authority、path;(path可以使用通配符进行匹配)
(4)当intent-filter中设置了mimeType,还会进行数据类型的匹配。

intent和intentfilter

如果一个 Intent 请求在一片数据上执行一个动作, Android 如何知道哪个应用程序(和组件)能用来响应这个请求呢?

Intent Filter就是 用来注册 Activity 、 Service 和 Broadcast Receiver 具有能在某种数据上执行一个动作的能力。
使用 Intent Filter ,应用程序组件告诉 Android ,它们能为其它程序的组件的动作请求提供服务,包括同一个程序的组件、本地的或第三方的应用程序。

(1)IntentFilter的解释:

IntentFilter就是用于描述intent的各种属性, 比如action, category等

一些属性设置的例子:

<data android:mimeType=”video/mpeg” android:scheme=”http” . . . />

(2)使用场景

activity的隐式启动和广播的匹配

(3)IntentFilter的匹配规则

IntentFilter的过滤信息有action,category,data.一个组件可以包含多个intent-filter,一个intent只要能完全匹配一组intent-filter即可成功的启动对应的组件。

1.action的匹配规则

intent-filter中必须包含一个action,intent信息中也必须指定一个action。intent-filter中可以有一个或者多个action,只要intent匹配其中的一个action即可。

2.category的匹配规则

intent中可以不指定category,在receiver的intent-filter中也可以不指定category,但是当要隐式启动一个activity是就必须指定android.intent.category.DEFAULT的category。否则系统也会报找不到指定的activity.当intent中指定了一个category时,就会在具有category的intent-filter中去找匹配组件。

3.data的匹配规则

data由两部分组成mimeType和URI.mimeType指媒体类型,比如image/jpeg、audio/*等。

URI的结构为

//:/[ | |]

scheme :URI的模式,比如http、file、content等,如果URI中没有指定scheme,那么整个URI的其他参数无效。
host:URI的主机名,比如www.baidu.com,如果host么有指定那么真个URI的其他参数无效。

port:URI的端口号。

path,pathPattern,pathPrefix:这三个参数指定路径信息

data的规则是如果inent-filter中描述了data,那么必须要在intent中描述匹配的data信息,如果intent-filter中没有描述data信息,那么intent中可以不指定data信息。data的规则中如果intent-filter只指定了mimeType,那么其默认的URI的scheme是file或者content。在intent中就必须同时设置mimeType和URI。如果intent-filter中有多组data,那么intent中只需要匹配一组即可。

启动活动

Intent启动Activity

1
2
Intent intent = new Intent("com.example.android.myapplication.ACTION_START");       intent.addCategory("com.example.android.myapplication.MY_CATEGORY";
startActivity(intent);

如果没有在manifest文件中添加

1
<category android:name="com.example.android.myapplication.MY_CATEGORY"></category>

就会出现如下异常信息

1
android.content.ActivityNotFoundException: No Activity found to handle Intent { act=com.example.android.myapplication.ACTION_START cat=[com.example.android.myapplication.MY_CATEGORY] }

如果不添加默认的

1
<category android:name="android.intent.category.DEFAULT"></category>

一样会遇到上面这个Error

     *Intent.ACTION_VIEW 系统内置的动作
         * Uri.parse()将一个网址字符串解析成一个 Uri 对象
         * setData()方法将这个 Uri 对象传递进去,指定当前 Intent正在操作的数据
         * */
         Intent intent = new Intent(Intent.ACTION_VIEW);
         intent.setData(Uri.parse("http://www.baidu.com"));
         startActivity(intent);

Activity使用Intent传递数据

向SecondActivity传递数据

1
2
3
4
5
6
7
8
String data = "This data come from FirstActivity";
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
intent.putExtra("howy",data);
startActivity(intent);
// 取出FirstActivity的数据
Intent intent = getIntent();
String dataFromFirstActivity = intent.getStringExtra("howy");
Log.d(TAG, dataFromFirstActivity);

数据往返传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Intent intent = new Intent(FirstActivity.this,SecondActivity.class);
startActivityForResult(intent, 1);
//在SecondActivity设置数据回传
Intent intent = new Intent();
String data = "This data from SecondActivity come to FirstActivity";
intent.putExtra("howy", data);
setResult(RESULT_OK,intent);
finish();
//在FirstActivity重写onActivityResult方法
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case 1:
if (resultCode == RESULT_OK) {
String dataComeFromSecondActivity = data.getStringExtra("howy");
Log.d(TAG, dataComeFromSecondActivity);
}
if (resultCode == RESULT_CANCELED) {
String content = "Data come form SecondActivity had been CANCELED";
String dataComeFromSecondActivity = data.getStringExtra("howy");
Log.d(TAG, " " + dataComeFromSecondActivity);
Log.d(TAG, content);
}
break;
default:
String content = "No data come form SecondActivity";
Log.d(TAG, content);
}
}

把startActivityForResult方法中的requestCode设置为2
就会打印No data come form SecondActivity,
在onActivityResult方法会先判断requestCode,再判断resultCode。
即使把resultCode设为RESULT_CANCELED也是可以回传数据的。

onBackPressed()方法按返回键回传数据。

1
2
3
4
5
6
public void onBackPressed() {
Intent intent = new Intent();
intent.putExtra("data_return", "Hello FirstActivity");
setResult(RESULT_OK, intent);
finish();
}

Activity生命周期

Android 使用任务(Task)来管理活动,一个任务就是一组存放在栈里的活动的集合,后进先出。

活动状态

每个活动在其生命周期中最多可能会有四种状态。

  1. 运行状态

当一个活动位于返回栈的栈顶时,这时活动就处于运行状态。系统最不愿意回收的就是处于运行状态的活动,因为这会带来非常差的用户体验。

  1. 暂停状态

当一个活动不再处于栈顶位置,但仍然可见时,这时活动就进入了暂停状态,比如对话框形式的活动只会占用屏幕中间的部分区域,处于暂停状态的活动仍然是完全存活着的,系统也不愿意去回收这种活动(因为它还是可见的,回收可见的东西都会在用户体验方面有不好的影响),只有在内存极低的情况下,系统才会去考虑回收这种活动。

  1. 停止状态

当一个活动不再处于栈顶位置,并且完全不可见的时候,就进入了停止状态。系统仍然会为这种活动保存相应的状态和成员变量,但是这并不是完全可靠的,当其他地方需要内存时,处于停止状态的活动有可能会被系统回收。

  1. 销毁状态

当一个活动从返回栈中移除后就变成了销毁状态。系统会最倾向于回收处于这种状态的活动,从而保证手机的内存充足。

活动的生存期

Activity 类定义了下面的回调。你可以不用实现所有的回调方法。当了解其中的每一个非常的重要,实现这些可以保证你的应用行为如用户所期望的那样。

  1. onCreate()

它会在活动第一次被创建的时候调用。应该在这个方法中完成活动的初始化操作,比如说加载布局、绑定事件等。

  1. onStart()

这个方法在活动由不可见变为可见的时候调用。

  1. onResume()

这个方法在活动准备好和用户进行交互的时候调用。此时的活动一定位于返回栈的栈顶,并且处于运行状态。

  1. onPause()

这个方法在系统准备去启动或者恢复另一个活动的时候调用。我们通常会在这个方法中将一些消耗 CPU 的资源释放掉,以及保存一些关键数据,但这个方法的执行速度一定要快,不然会影响到新的栈顶活动的使用。

  1. onStop()

这个方法在活动完全不可见的时候调用。它和 onPause() 方法的主要区别在于,如果启动的新活动是一个对话框式的活动,那么 onPause() 方法会得到执行,而 onStop() 方法并不会执行。

  1. onDestroy()

这个方法在活动被销毁之前调用,之后活动的状态将变为销毁状态。

  1. onRestart()

这个方法在活动由停止状态变为运行状态之前调用,也就是活动被重新启动了。


以上七个方法中除了onRestart()方法,其他都是两两相对的,从而又可以将活动分为三种生存期。

  1. 完整生存期

活动在 onCreate() 方法和 onDestroy() 方法之间所经历的,就是完整生存期。一般情况下,一个活动会在 onCreate() 方法中完成各种初始化操作,而在 onDestroy() 方法中完成释放内存的操作。

  1. 可见生存期

活动在 onStart() 方法和 onStop() 方法之间所经历的,就是可见生存期。在可见生存期内,活动对于用户总是可见的,即便有可能无法和用户进行交互。我们可以通过这两个方法,合理地管理那些对用户可见的资源。比如在 onStart() 方法中对资源进行加载,而在 onStop() 方法中对资源进行释放,从而保证处于停止状态的活动不会占用过多内存。

  1. 前台生存期

活动在 onResume() 方法和 onPause() 方法之间所经历的,就是前台生存期。在前台生存期内,活动总是处于运行状态的,此时的活动是可以和用户进行相互的,我们平时看到和接触最多的也这个状态下的活动。

保存临时数据

当一个活动进入到了停止状态,是有可能被系统回收的。Activity 中还提供了一个 onSaveInstanceState() 回调方法,这个方法会保证一定在活动被回收之前调用,因此我们可以通过这个方法来解决活动被回收时临时数据得不到保存的问题。

  1. 保存临时数据

onSaveInstanceState() 方法会携带一个Bundle 类型的参数,Bundle 提供了一系列的方法用于保存数据,键值对方式。如 putString()、putInt() 等。

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);
}
  1. 恢复临时数据

onCreate()方法其实也有一个 Bundle 类型的参数。这个参数在一般情况下都是 null,但是当活动被系统回收之前有通过 onSaveInstanceState() 方法来保存数据的话,这个参数就会带有之前所保存的全部数据,只需要再通过相应的取值方法将数据取出即可。

1
2
3
4
5
6
7
8
9
10
11
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

if (savedInstanceState != null) {
String tempData = savedInstanceState.getString("data_key");
Log.d(TAG, tempData);
}
......
}

Intent 还可以结合 Bundle 一起用于传递数据的,首先可以把需要传递的数据都保存在 Bundle 对象中,然后再将 Bundle 对象存放在 Intent 里。到了目标活动之后先从 Intent 中取出 Bundle,再从Bundle中一一取出数据。

活动的四种启动模式

安卓中的活动(Activity)有四种不同的启动模式,这四种模式分别是:standard,SingleTop,SingleTask,SingleInstance。这四种模式中,standard模式是默认的模式,其他三个想要使用的话,要在AndroidMainFest中进行修改。(例如:)

standard模式:

系统默认的启动模式。
Android是使用返回栈来管理活动的,在standard模式下,每当启动一个新的活动,它就会在返回栈中入栈,并处于栈顶的位置。
对于使用standard模式的活动,系统不会在乎这个活动是否已经在返回栈中存在,而是每次启动活动都会创建该活动的一个新的实例。

standard模式原理示意图:

这是最基础的模式,在这种模式中,当你进入一个活动,这个活动就会创造一个实例,出现在返回栈的最顶层,上一个你离开的活动就会被压在下面,(注:返回栈是Android管理活动的地方,出现在界面上的活动就在最上面,再出现新的就一层层往下压。)当你无限的点击进入下一个界面,你的每一个活动都会在返回栈中往下压。如果你点击返回上一个的活动,最顶层的活动就会消失,第二层的就会上来,如果你要退出这个应用,要把返回栈中的所有活动都取消掉,才能退出。如果你用一个极端的例子,不停地从这个活动进入同一个活动,你点击了十次,就要返回十次才能退出程序,因为你在返回栈中创造了十个相同的实例,尽管活动是一样的。

SingleTop模式:

1
android:launchMode="singleTop"

在SingleTop模式中, 会检查在返回栈栈顶是不是你要启动的活动,如果是的话,他就不会启动,直接使用。
所以SingleTop不会出现standard中的情况需要点击多次才能退出程序,它只需要点击一次就可以了。

singleTop模式原理示意图:

SingleTask模式:

当活动的启动模式指定为singleTask,每次启动该活动时,首先会在返回栈中检查是否存在该活动的实例,如果发现已经存在就直接使用该实例,并把这个活动之上的所有活动统统出栈,如果没有发现就会创建一个新的活动实例。

1
android:launchMode="singleTask"

singleTask原理示意图:

singleInstance

这种模式是最特殊的模式,这种模式是为了让不同的app之间可以共享同一个活动,如果你的app想让别的app调用你的某一个界面,就可以用这种模式,这种模式会为你想共享的界面单独创造出一个单独使用的返回栈,不会与别的返回栈共同使用。

1
android:launchMode="singleInstance"

singleInstance模式示意图:

数据适配器

什么是数据适配器?

下图展示了数据源、适配器、ListView等数据展示控件之间的关系。我们知道,数据源是各种各样的,而ListView所展示数据的格式则是有一定的要求的。数据适配器正是建立了数据源与ListView之间的适配关系,将数据源转换为ListView能够显示的数据格式,从而将数据的来源与数据的显示进行解耦,降低程序的耦合性。这也体现了Android的适配器模式的使用。对于ListView、GridView等数据展示控件有多种数据适配器,本文讲解最通用的数据适配器——BaseAdapter。

ListView的显示与缓存机制

我们知道,ListView、GridView等控件可以展示大量的数据信息。假如下图中的ListView可以展示100条信息,但是屏幕的尺寸是有限的,一屏幕只能显示下图中的7条。当向上滑动ListView的时候,item1被滑出了屏幕区域,那么系统就会将item1回收到Recycler中,即View缓冲池中,而将要显示的item8则会从缓存池中取出布局文件,并重新设置好item8需要显示的数据,并放入需要显示的位置。这就是ListView的缓冲机制,总结起来就是一句话:需要时才显示,显示完就被会收到缓存。ListView,GridView等数据显示控件通过这种缓存机制可以极大的节省系统资源。

BaseAdapter

使用BaseAdapter比较简单,主要是通过继承此类来实现BaseAdapter的四个方法:

public int getCount(): 适配器中数据集的数据个数;

public Object getItem(int position): 获取数据集中与索引对应的数据项;

public long getItemId(int position): 获取指定行对应的ID;

public View getView(int position,View convertView,ViewGroup parent): 获取没一行Item的显示内容。

下面通过一个简单示例演示如何使用BaseAdapter。

1.创建布局文件

activity_main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
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"
tools:context="com.cbt.learnbaseadapter.MainActivity">

<ListView
android:id="@+id/lv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</RelativeLayout>

item.xml (ListView中每条信息的显示布局)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/iv_image"
android:src="@mipmap/ic_launcher"
android:layout_width="60dp"
android:layout_height="60dp"/>
<TextView
android:id="@+id/tv_title"
android:layout_width="match_parent"
android:layout_height="30dp"
android:layout_toEndOf="@id/iv_image"
android:text="Title"
android:gravity="center"
android:textSize="25sp"/>

<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toEndOf="@id/iv_image"
android:layout_below="@id/tv_title"
android:text="Content"
android:textSize="20sp"/>
</RelativeLayout>

2.创建数据源

ItemBean.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.cbt.learnbaseadapter;

/**
* Created by caobotao on 15/12/20.
*/
public class ItemBean {
public int itemImageResId;//图像资源ID
public String itemTitle;//标题
public String itemContent;//内容

public ItemBean(int itemImageResId, String itemTitle, String itemContent) {
this.itemImageResId = itemImageResId;
this.itemTitle = itemTitle;
this.itemContent = itemContent;
}
}

通过此Bean类,我们就将要显示的数据与ListView的布局内容一一对应了,每个Bean对象对应ListView的一条数据。这种方法在ListView中使用的非常广泛。

MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.cbt.learnbaseadapter;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
ListView mListView ;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<ItemBean> itemBeanList = new ArrayList<>();
for (int i = 0;i < 20; i ++){
itemBeanList.add(new ItemBean(R.mipmap.ic_launcher, "标题" + i, "内容" + i));
}
mListView = (ListView) findViewById(R.id.lv_main);
//设置ListView的数据适配器
mListView.setAdapter(new MyAdapter(this,itemBeanList));
}
}

3.创建BaseAdapter

通过上面的讲解,我们知道继承BaseAdapter需要重新四个方法:getCount、getItem、getItemId、getView。其中前三个都比较简单,而getView稍微比较复杂。通常重写getView有三种方式,这三种方法性能方面有很大的不同。接下来我们使用此三种方式分别实现MyAdapter。
第一种:逗比式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.cbt.learnbaseadapter;
import android.content.Context;
import android.view.*;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;
/**
* Created by caobotao on 15/12/20.
*/
public class MyAdapter extends BaseAdapter{
private List<ItemBean> mList;//数据源
private LayoutInflater mInflater;//布局装载器对象

// 通过构造方法将数据源与数据适配器关联起来
// context:要使用当前的Adapter的界面对象
public MyAdapter(Context context, List<ItemBean> list) {
mList = list;
mInflater = LayoutInflater.from(context);
}

@Override
//ListView需要显示的数据数量
public int getCount() {
return mList.size();
}

@Override
//指定的索引对应的数据项
public Object getItem(int position) {
return mList.get(position);
}

@Override
//指定的索引对应的数据项ID
public long getItemId(int position) {
return position;
}

@Override
//返回每一项的显示内容
public View getView(int position, View convertView, ViewGroup parent) {
//将布局文件转化为View对象
View view = mInflater.inflate(R.layout.item,null);

/**
* 找到item布局文件中对应的控件
*/
ImageView imageView = (ImageView) view.findViewById(R.id.iv_image);
TextView titleTextView = (TextView) view.findViewById(R.id.tv_title);
TextView contentTextView = (TextView) view.findViewById(R.id.tv_content);

//获取相应索引的ItemBean对象
ItemBean bean = mList.get(position);

/**
* 设置控件的对应属性值
*/
imageView.setImageResource(bean.itemImageResId);
titleTextView.setText(bean.itemTitle);
contentTextView.setText(bean.itemContent);

return view;
}
}

为什么称这种getView的方式是逗比式呢?

通过上面讲解,我们知道ListView、GridView等数据展示控件有缓存机制,而这种方式每次调用getView时都是通过inflate创建一个新的View对象,然后在此view中通过findViewById找到对应的控件,完全没有利用到ListView的缓存机制。这种方式没有经过优化处理,对资源造成了极大的浪费,效率是很低的。
第二种:普通式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public View getView(int position, View convertView, ViewGroup parent) {//如果view未被实例化过,缓存池中没有对应的缓存
if (convertView == null) {
convertView = mInflater.inflate(R.layout.item,null);
}
/**
* 找到item布局文件中对应的控件
*/
ImageView imageView = (ImageView) convertView.findViewById(R.id.iv_image);
TextView titleTextView = (TextView) convertView.findViewById(R.id.tv_title);
TextView contentTextView = (TextView) convertView.findViewById(R.id.tv_content);

//获取相应索引的ItemBean对象
ItemBean bean = mList.get(position);
/**
* 设置控件的对应属性值
*/
imageView.setImageResource(bean.itemImageResId);
titleTextView.setText(bean.itemTitle);
contentTextView.setText(bean.itemContent);
return convertView;
}

此方式充分使用了ListView的缓存机制,如果view没有缓存才创建新的view,效率相比于逗比式提升了很多。但是,当ListView很复杂时,每次调用findViewById都会去遍历视图树,所以findViewById是很消耗时间的,我们应该尽量避免使用findViewById来达到进一步优化的目的。
第三种:文艺式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
//如果view未被实例化过,缓存池中没有对应的缓存
if (convertView == null) {
viewHolder = new ViewHolder();
// 由于我们只需要将XML转化为View,并不涉及到具体的布局,所以第二个参数通常设置为null
convertView = mInflater.inflate(R.layout.item, null);

//对viewHolder的属性进行赋值
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.iv_image);
viewHolder.title = (TextView) convertView.findViewById(R.id.tv_title);
viewHolder.content = (TextView) convertView.findViewById(R.id.tv_content);

//通过setTag将convertView与viewHolder关联
convertView.setTag(viewHolder);
}else{//如果缓存池中有对应的view缓存,则直接通过getTag取出viewHolder
viewHolder = (ViewHolder) convertView.getTag();
}
// 取出bean对象
ItemBean bean = mList.get(position);

// 设置控件的数据
viewHolder.imageView.setImageResource(bean.itemImageResId);
viewHolder.title.setText(bean.itemTitle);
viewHolder.content.setText(bean.itemContent);

return convertView;
}
// ViewHolder用于缓存控件,三个属性分别对应item布局文件的三个控件
class ViewHolder{
public ImageView imageView;
public TextView title;
public TextView content;
}

此方式不仅利用了ListView的缓存机制,而且使用ViewHolder类来实现显示数据视图的缓存,避免多次调用findViewById来寻找控件,以达到优化程序的目的。所以,大家在平时的开发中应当尽量使用这种方式进行getView的实现。

总结一下用ViewHolder优化BaseAdapter的整体步骤:

1 创建bean对象,用于封装数据;

2 在构造方法中初始化的数据List;

3 创建ViewHolder类,创建布局映射关系;

4 判断convertView,为空则创建,并设置tag,不为空则通过tag取出ViewHolder;

5 给ViewHolder的控件设置数据。