Activity
MoMo Lv5

image

  1. Activity的生命周期
  2. Activity的启动模式
  3. 切换横竖屏时 Activity的生命周期变化
  4. Activity的启动流程
  5. Android应用程序的启动流程

什么是Activity

Activity(活动)是一个为实现交互而提供的Android应用组件。每个Activity都有一个窗口,该窗口可以全屏幕填充,也可以是一个小窗口浮动在其他窗口上。一个应用程序通常由多个Activity组成,它会指定应用程序中的某个Activity作为主Activity,这意味着当用户第一次启动应用程序时呈现给用户的活动,并且Activity可以相互跳转来执行不同的操作。

启停活动页面

Activity的启动和结束

从当前页面跳到新页面:startActivity(new lIntent(源页面.this,目标页面.class));

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ActStartActivity extends AppCompatActivity implements View.OnClickListener {
@Override
public void onClick(View v) { // 点击事件的处理方法
// 1.在Intent的构造函数中指定
//Intent intent = new Intent(this, ActFinishActivity.class);
// 2.调用意图对象的setClass方法指定
Intent intent = new Intent();
// intent.setClass(this,ActFinishActivity.class);
// 3.调用意图对象的setComponent方法指定
ComponentName component = new ComponentName(this, ActFinishActivity.class);
intent.setComponent(component);
startActivity(intent);
}

从当前页面回到上一个页面,相当于关闭当前页面:finish();//结束当前的活动页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 活动类直接实现点击监听器的接口View.OnClickListener
public class ActFinishActivity extends AppCompatActivity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_act_finish);
findViewById(R.id.iv_back).setOnClickListener(this);
findViewById(R.id.btn_finish).setOnClickListener(this);
}

@Override
public void onClick(View v) { // 点击事件的处理方法
if (v.getId() == R.id.iv_back || v.getId() == R.id.btn_finish) {
finish(); // 结束当前的活动页面
}
}
}

所谓“打开页面”或“关闭页面”沿用了浏览网页的叫法,对于App而言,页面的真实名称是“活动”—Activity。打开某个页面其实是启动某个活动,所以有startActivity方法却无openActivity方法;关闭某个页面其实是结束某个活动,所以有finish方法却无close方法。

Activity的生命周期

image

在正常情况下,一个Activity从启动到结束会以如下顺序经历整个生命周期: onCreate()->onStart()->onResume()->onPause()->onStop()->onDestory()。包含了六个部分,还有一个onRestart()没有调用。创建一个活动->可以见到该活动显示在屏幕->该活动可以触摸操作->该活动停止,不可以操作->该活动从屏幕上消失,看不到了->该活动销毁。这六个步骤就和上面六个函数一一对应。

(1) Oncreate():创建:作为生命周期的第一种方法,仅在新的Activity创建时调用。在这个方法中,可以做一些初始化工作,比如加载接口布局资源和初始化活动所需的数据。

(2) OnStart():开始:表示Activity正在启动,并且即将启动。此时Activity已经出现,但还没有出现在前台,我们还不能交互。

(3) OnResume():活动:表示Activity已经出现在前台,可见可操作。

(4) OnPause():暂停:表示活动即将停止,仍然可见,但是不能操作了。

(5) OnStop():停止:表示活动停止,此时不可见,位于后台。

(6) OnDestory():销毁:表示活动即将被销毁。这是Activity循环的最后一个回调。你可以做一些回收工作和最后的资源回收。

最后介绍第七个生命周期onRestart:

(7) OnRestart():表示Activity重启。在正常情况下,当Activity从不可见状态切换到可见状态,也就是onStop()到onStart()之间,就会调用OnRestart()。这种情况一般是由用户行为引起的。例如,用户在这个活动切换到桌面或打开另一个新的活动,然后用户返回到这个活动。

App引入活动的概念而非传统的页面概念,单从字面意思理解,页面更像是静态的,而活动更像是动态的。每次创建新的活动页面,自动生成的Java代码都给出了onCreate方法,该方法用于执行活动创建的相关操作,包括加载XML布局、设置文本视图的初始文字、注册按钮控件的点击监听,等等。onCreate方法所代表的创建动作,正是一个活动最开始的行为,除了onCreate,活动还有其他几种生命周期行为,它们对应的方法说明如下:

  • onCreate:创建活动。此时会把页面布局加载进内存,进入了初始状态。
  • onStart:开启活动。此时会把活动页面显示在屏幕上,进入了就绪状态。
  • onResume:恢复活动。此时活动页面进入活跃状态,能够与用户正常交互,例如允许响应用户的点击动作、允许用户输入文字等。
  • onPause:暂停活动。此时活动页面进入暂停状态(也就是退回就绪状态),无法与用户正常交互。
  • onStop:停止活动。此时活动页面将不在屏幕上显示。
  • onDestroy:销毁活动。此时回收活动占用的系统资源,把页面从内存中清除掉。
  • onRestart:重启活动。处于停止状态的活动,若想重新开启的话,无须经历onCreate的重复创建过程,而是走onRestart的重启过程。
  • onNewIntent:重用已有的活动实例。

如果一个Activity已经启动过,并且存在当前应用的Activity任务栈中,启动模式为singleTask,singleInstance或singleTop(此时已在任务栈顶端),那么在此启动或回到这个Activity的时候,不会创建新的实例,也就是不会执行onCreate方法,而是执行onNewIntent方法。

1
2
3
4
5
6
7
8
9
10
11
onCreat
onStart
onResume
onPause 跳转到另外一个页面
onStop 当前activity不可见
onRestart 返回到当前activity
onStart
onResume
onPause 返回到桌面
onStop
onDestroy

打开新页面的方法调用顺序为:onCreate→onStart→onResume
关闭旧页面的方法调用顺序为:onPause→onStop→onDestroy

几种常见情况

①启动ActivityA:onCreate()->onStart()->onResume()

②打开新的ActiviyB的时候,上述ActivityA的回调如下:onPause()->onStop()

③接着再次回到原ActivityA时,ActivityA的回调如下:onRestart()->onStart()->onResume()

④按back键回退ActivityA时,ActivityA的回调如下:onPause()->onStop()->onDestory()

⑤在第四个步骤不按back,而是按Home键切换到桌面后又回到该ActitiyA,ActivityA的回调如下:onPause()->onStop()->onRestart()->onStart()->onResume()

⑥调用finish()方法后,ActivityA的回调如下:onDestory()

切换横竖屏时,Activity的生命周期变化

正常情况下,切换横竖屏后,Activity会被销毁并重新创建

正常情况

重新走一遍生命周期:onPause()->onSaveInstanceState()-> onStop()->onDestroy()->onCreate()->onStart()->onRestoreInstanceState->onResume()

其中onSaveInstanceState和onRestoreInstanceState是两个重要的回调函数。当Activity异常退出时,系统调用onSaveInstanceState()来保存Activity当前状态。同样的,当该Activity重新创建时,会调用onRestoreInstanceState()来恢复之前的状态。

配置 android:configChanges

可以通过在AndroidManifest里配置android:configChanges,来避免切换横竖屏时Activity重新走生命周期。

1
android:configChanges = "orientation| screenSize"

此时切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法,在该方法就可以执行其他动作啦。

注意:设置Activity的android:configChanges=”orientation”时,切屏还是会调用各个生命周期,切换横竖屏只会执行一次。

Activity的启动模式

系统给每个正在运行的App都分配了活动栈,栈里面容纳着已经创建且尚未销毁的活动信息。鉴于栈是一种先进后出、后进先出的数据结构,故而后面入栈的活动总是先出栈,假设3个活动的入栈顺序为:活动A→活动B→活动C,则它们的出栈顺序将变为:活动C→活动B→活动A,可见活动C结束之后会返回活动B,而不是返回活动A或者别的地方。

假定某个App分配到的活动栈大小为3,该App先后打开两个活动,此时活动栈的变动情况如图所示

image

image

Android允许在创建活动时指定该活动的启动模式,通过启动模式控制活动的出入栈行为。App提供了两种办法用于设置活动页面的启动模式,其一是修改AndroidManifest.xml,在指定的activity节点添加属性android:launchMode,表示本活动以哪个启动模式运行。其二是在代码中调用Intent对象的setFlags方法,表明后续打开的活动页面采用该启动标志。

打开AndroidManifest.xml,给activity节点添加属性android:launchMode,属性值填入standard表示采取标准模式,当然不添加属性的话默认就是标准模式。具体的activity节点配置内容示例如下:

1
<activity android:name=".JumpFirstActivity" android:launchMode="standard" />  
  • Standard:标准模式,也是系统默认的启动模式。每次启动都会创建一个新的Activity实例,不管这个实例在栈中是否已经存上。Activity所在的任务栈为启动新Activity B的Activity A所在任务栈。具体就是Activity A所在的任务栈为S1,Activity A启动Activity B,那么Activty B的实例被创建后,会将Activity B的实例压入任务栈S1中。

  • SingleTop:栈顶复用模式。在这种启动模式下,如果新Activity已经在任务栈的栈顶,那么,将不会重新创建新Activity,同时onNewIntent()方法被回调,通过此方法参数可以获取到请求信息。需要注意的是此Activity的onCreate()onStart()方法也不会被调用。比如:有四个Activity,分别为ABCD,Activity D的LaunchModeSingleTop模式;

    • 如果,在任务栈中四个Activity的排列是ABCD的顺序,此时,启动Activity D,由于Activity D位于任务栈栈顶,那么就不会创建Activity D实例,系统会调用Activity D的onNewIntent()方法。此时,Activity在任务栈中的顺序依然是ABCD。
    • 如果,在任务栈中四个Activity的排列是ADBC的顺序,此时,启动Activity D,由于Activity D没有位于任务栈栈顶,那么,就会创建Activity D的新实例,并压下任务栈中。此时,Activity在任务栈中的顺序是ADBCD。
  • SingleTask:栈内复用模式。这是一种单实例模式,在这种模式下,只要Activity在一个任务栈中存在,那么多次启动此Activity都不会创建新的实例,和SingleTop一样,系统会调用其onNewIntent()方法。具体一点就是,有一个Activity A,并启动Activity A。首先寻找Activity A所需的任务栈,如果任务栈不存在,则创建任务栈并将Activity A压入栈中。如果Activity A所需的任务栈已经存在,则看Activity A在任务栈中,是否存在实例,如果不存在实例,就会创建新的实例并压入栈中;如果Activity A实例在已经存在的任务栈中,则将Activity A的实例调到栈顶,由于SingleTaskclearTop效果,在Activity A实例之上的所有Activity都会被出栈,直至Activity A到栈顶为止。比如:有四个Activity,分别为ABCD,并且Activity B的launchMode为SingleTask,此时启动Activity B。

    • 如果,Activity B所需的任务栈不存在,此时会创建其任务栈S1并将Activity B压入栈中。
    • 如果,Activity B所需的任务栈S1已经存在,此时会在任务栈S1中查看Activity B的实例是否在栈中存在。假如,Activity B的实例在任务栈S1中存在,并且当前任务栈中已经存在几个Activity,在任务栈中的排列为ABCD,那么,此时启动Activity B,不会再创建Activity B的实例,系统会调用其Activity B的onNewIntent()方法。并且将Activity B调至栈顶,由于SingleTaskclearTop效果,CD两个Activity会被出栈,直至Activity B被调到栈顶。
    • 如果,Activity B所需的任务栈S1已经存在,此时会在任务栈S1中查看是否存在Activity B的实例,如果不存在,则在任务栈S1中创建Activity B的实例,并压入任务栈S1中。
  • SingleInstance:单实例模式。这是一种加强版的SingleTask模式,它除了具有SingleTask的所有特性外,还加强了一点,就是具有此模式的Activity只能单独位于一个任务栈中。换句话说就是,一个Activity B是SingleInstance模式,在启动Activity B时,会创建Activity B所需的任务栈S2,并将Activity B压入栈中,并且这个任务栈S2,只会有一个Activity,那就是Activity B。由于栈内复用模式,再次启动Activity B时,均不会再创建Activity B的实例,系统会调用Activity B的onNewIntent()方法。除非,Activity B所需的任务栈S2被系统销毁,否则不会再创建Activity B的实例。

Android App在创建任务栈时,默认使用的是包名为任务栈名称,当前也可以自定义指定任务栈名称,比如:

1
2
3
4
5
<activity
android:name=".SecondActivity"
android:launchMode="singleInstance"
android:taskAffinity="com.example.task.task.SecondActivity11"
android:allowTaskReparenting="true"/>

其中,属性taskAffinityallowTaskReparenting是配合使用的,taskAffinity指定任务栈名称。属性taskAffinity只有在SingleTaskSingleInstance模式下有效。

默认启动模式standard

默认的启动模式,新启动的Activity放入返回栈栈顶,遵循先进后出原则,同一个Activity可以被实例化多次。

该模式可以被设定,不在 manifest 设定时候,Activity 的默认模式就是 standard。在该模式下,启动的 Activity 会依照启动顺序被依次压入 Task 栈中

image

栈顶复用模式 singleTop

进一步思考,如果同一个ActivityA被实例化5次,那么栈顶就有5个ActivityA,但我们希望如果栈顶已经是ActivityA时,新创建一个ActivityA就不要重新实例化新的ActivityA,而是用此时栈顶的ActivityA。那么就可以用SingleTop模式,分为下面两种情况:

  • 如果当前返回栈的顶部不存在该Activity,则新建该Activity并放入栈顶;
  • 如果当前返回栈的顶部已存在Activity的一个实例,则系统会通过调用该实例的onNewIntent()方法向其传送Intent,不创建该Activity的新实例。

在该模式下,如果栈顶 Activity 为我们要新建的 Activity(目标Activity),那么就不会重复创建新的Activity

适合开启渠道多、多应用开启调用的 Activity,通过这种设置可以避免已经创建过的 Activity 被重复创建,多数通过动态设置使用

image

栈内复用模式 singleTask

单任务模式就是指一个栈里面只有一个指定的Activity。而且通常会和taskAffinity配合使用。

1
2
3
4
<activity android:name="ActivityA"
android:launchMode="singleTask"
android:taskAffinity="com.android.task"
</activity>

affinity就是亲和力的意思,我们可以设置希望ActivityA在com.android.task这个栈里打开,那么就分为以下3种情况:

  • 如果com.android.task返回栈不存在,系统会新建新的返回栈,然后再创建该ActivityA的实例将其压入返回栈中;

  • 如果com.android.task返回栈存在,而且返回栈中没有该ActivityA,则新建Activity A并放入栈顶;

  • 如果com.android.task返回栈存在,而且返回栈中已经有该ActivityA,则重新启动Activity,调用的顺序为:

    1
    onNewIntent()->onRestart()->onStart()->onResume()

    且进一步分为以下两种情况:

    • 如果该ActivityA在栈顶,则不做处理;
    • 如果该ActivityA不在栈顶,会弹出其上面的所有Activity,让该ActivityA置于栈顶(返回栈中的Activity永远不会重新排列,弹出的其他Activity无法恢复);

注意:如果只是指定了android:taskAffinity属性,但是启动模式没有指定为Singletask模式,新启动的Activity依然会在原来的返回栈中。

singleTop模式相似,只不过 singleTop 模式是只是针对栈顶的元素,而 singleTask 模式下,如果task 栈内存在目标 Activity 实例,则将 task 内的对应 Activity 实例之上的所有 Activity 弹出栈,并将对应 Activity 置于栈顶,获得焦点。

应用场景

  • 程序主界面:不希望主界面被创建多次,而且在主界面退出的时候退出整个 App 是最好的效果。
  • 耗费系统资源的Activity:对于那些及其耗费系统资源的 Activity,将其设为 singleTask模式,减少资源耗费。

image

全局唯一模式 singleInstance

这是Singletask的升级版,创建SingleInstance启动模式的Activity时,系统都会直接创建一个新的返回栈并创建Activity的新实例置于新Task返回栈中,有两点要注意:

  • 之后系统不会将任何其他Activity的实例放入这个新建的Task中,该Activity始终是其Task唯一仅有的成员;
  • 一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例。

在该模式下,我们会为目标 Activity 创建一个新的 Task 栈,将目标 Activity 放入新的 Task,并让目标Activity获得焦点。新的 Task 有且只有这一个 Activity 实例。 如果已经创建过目标Activity 实例,则不会创建新的 Task,而是将以前创建过的 Activity 唤醒。

image

动态设置启动模式

通过 Intent 动态设置 Activity启动模式 , 如果同时有动态和静态设置,那么动态的优先级更高。

1
intent.setFlags();

FLAG_ACTIVITY_NEW_TASK

singleInstance 很相似,在给目标 Activity 设立此 Flag 后,会根据目标 Activity 的 affinity 进行匹配,如果已经存在与其affinity 相同的 task,则将目标 Activity 压入此 Task。反之没有的话,则新建一个 task,新建的 task 的 affinity 值与目标 Activity 相同,然后将目标 Activity 压入此栈。

但它与 singleInstance 有不同的点,两点需要注意的地方:

  • 新的 Task 没有说只能存放一个目标 Activity,只是说决定是否新建一个 Task,而singleInstance模式下新的 Task 只能放置一个目标 Activity。
  • 在同一应用下,如果 Activity 都是默认的 affinity,那么此 Flag 无效,而singleInstance 默认情况也会创建新的 Task。

FLAG_ACTIVITY_SINGLE_TOP

与静态设置中的 singleTop 效果相同

FLAG_ACTIVITY_CLEAR_TOP

目标 Activity 会检查 Task 中是否存在此实例,如果没有则添加压入栈。如果有,就将位于 Task 中的对应 Activity 其上的所有 Activity 弹出栈,此时有以下两种情况:

  • 如果同时设置 Flag_ACTIVITY_SINGLE_TOP ,则直接使用栈内的对应 Activity。

    1
    intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
  • 没有设置,则将栈内的对应 Activity 销毁重新创建。

按位或运算符
运算规则:0|0=0 0|1=1 1|0=1 1|1=1
总结:参加运算的两个对象只要有一个为1,其值为1。
例如:3|5即 0000 0011| 0000 0101 = 0000 0111,因此,3|5的值得7

在活动之间传递消息

显式Intent和隐式Intent

Intent就是传递消息,是各个组件之间信息沟通的桥梁

既能在Activity之间沟通,又能在Activity与Service之间沟通,也能在Activity与Broadcast之间沟通。总而言之,Intent用于Android各组件之间的通信,它主要完成下列3部分工作:

  • 标明本次通信请求从哪里来、到哪里去、要怎么走。
  • 发起方携带本次通信需要的数据内容,接收方从收到的意图中解析数据。
  • 发起方若想判断接收方的处理结果,意图就要负责让接收方传回应答的数据内容

Intent的组成部分见表

元素名称 设置方法 说明与用途
Component setComponent 组件,它指定意图的来源与目标
Action setAction 动作,它指定意图的动作行为
Data setData 即Uri,它指定动作要操纵的数据路径
Category addCategory 类别,它指定意图的操作类别
Type setType 数据类型,它指定消息的数据类型
Extras putExtras 扩展信息,它指定装载的包裹信息
Flags setFlags 标志位,它指定活动的启动标志

指定意图对象的目标有两种表达方式,一种是显式Intent,另一种是隐式Intent。

显式Intent

直接指定来源活动与目标活动,属于精确匹配

在构建一个意图对象时,需要指定两个参数,

第一个参数表示跳转的来源页面,即“来源Activity.this”;
第二个参数表示待跳转的页面,即“目标Activity.class”。

具体的意图构建方式有如下3种:

  1. 在Intent的构造函数中指定,示例代码如下:
1
Intent intent = new Intent(this, ActNextActivity.class); // 创建一个目标确定的意图
  1. 调用意图对象的setClass方法指定,示例代码如下:
1
2
Intent intent = new Intent(); // 创建一个新意图
intent.setClass(this, ActNextActivity.class); // 设置意图要跳转的目标活动
  1. 调用意图对象的setComponent方法指定,示例代码如下:
1
2
3
4
Intent intent = new Intent(); // 创建一个新意图
// 创建包含目标活动在内的组件名称对象
ComponentName component = new ComponentName(this, ActNextActivity.class);
intent.setComponent(component); // 设置意图携带的组件信息

隐式Intent

没有明确指定要跳转的目标活动,只给出一个动作字符串让系统自动匹配,属于模糊匹配

通常App不希望向外部暴露活动名称,只给出一个事先定义好的标记串,这样大家约定俗成、按图索骥就好,隐式Intent便起到了标记过滤作用。这个动作名称标记串,可以是自己定义的动作,也可以是已有的系统动作。

常见系统动作的取值说明见表

Intent 类的系统动作常量名 系统动作的常量值 说明
ACTION_MAIN android.intent.action.MAIN App启动时的入口
ACTION_VIEW android.intent.action.VIEW 向用户显示数据
ACTION_SEND android.intent.action.SEND 分享内容
ACTION_CALL android.intent.action.CALL 直接拨号
ACITON_DIAL android.intent.action.DIAL 准备拨号
ACTION_SENDTO android.intent.action.SENDTO 发送短信
ACTION_ANSWER android.intent.action.ANSWER 接听电话

动作名称既可以通过setAction方法指定,也可以通过构造函数Intent(String action)直接生成意图对象。当然,由于动作是模糊匹配,因此有时需要更详细的路径,比如仅知道某人住在天通苑小区,并不能直接找到他家,还得说明他住在天通苑的哪一期、哪栋楼、哪一层、哪一个单元。

Uri和Category便是这样的路径与门类信息,Uri数据可通过构造函数Intent(String action, Uri uri)在生成对象时一起指定,也可通过setData方法指定;Category可通过addCategory方法指定,之所以用add而不用set方法,是因为一个意图允许设置多个Category,方便一起过滤。

1
2
3
4
5
6
String phoneNo = "12345";
Intent intent = new Intent(); // 创建一个新意图
intent.setAction(Intent.ACTION_DIAL); // 设置意图动作为准备拨号
Uri uri = Uri.parse("tel:" + phoneNo); // 声明一个拨号的Uri
intent.setData(uri); // 设置意图前往的路径
startActivity(intent); // 启动意图通往的活动页面

隐式Intent还用到了过滤器的概念,把不符合匹配条件的过滤掉,剩下符合条件的按照优先顺序调用。

譬如创建一个App模块,AndroidManifest.xml里的intent-filter就是配置文件中的过滤器。像最常见的首页活动MainAcitivity,它的activity节点下面便设置了action和category的过滤条件。其中android.intent.action.MAIN表示App的入口动作,而android.intent.category.LAUNCHER表示在桌面上显示App图标,配置样例如下

1
2
3
4
5
6
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

向下一个Activity发送数据

Intent对象的setData方法只指定到达目标的路径,并非本次通信所携带的参数信息,真正的参数信息存放在Extras中。Intent重载了很多种putExtra方法传递各种类型的参数,包括整型、双精度型、字符串等基本数据类型,甚至Serializable这样的序列化结构。只是调用putExtra方法显然不好管理,像送快递一样大小包裹随便扔,不但找起来不方便,丢了也难以知道。所以Android引入了Bundle概念,可以把Bundle理解为超市的寄包柜或快递收件柜,大小包裹由Bundle统一存取,方便又安全。

Bundle内部用于存放消息的数据结构是Map映射,既可添加或删除元素,还可判断元素是否存在。开发者若要把Bundle数据全部打包好,只需调用一次意图对象的putExtras方法;若要把Bundle数据全部取出来,也只需调用一次意图对象的getExtras方法。Bundle对象操作各类型数据的读写方法说明见表

数据类型 读方法 写方法
整型数 getInt putInt
浮点数 getFloat putFloat
双精度数 getDouble putDouble
布尔值 getBoolean putBoolean
字符串 getString putString
字符串数组 getStringArray putStringArray
字符串列表 getStringArrayList putStringArrayList
可序列化结构 getSerializable putSerializable

一个活动之间传递数据的例子,首先在上一个活动使用包裹封装好数据,把包裹塞给意图对象,再调用startActivity方法跳到意图指定的目标活动

1
2
3
4
5
6
7
8
9
// 创建一个意图对象,准备跳到指定的活动页面
Intent intent = new Intent(this, ActReceiveActivity.class);
Bundle bundle = new Bundle(); // 创建一个新包裹
// 往包裹存入名为request_time的字符串
bundle.putString("request_time", DateUtil.getNowTime());
// 往包裹存入名为request_content的字符串
bundle.putString("request_content", tv_send.getText().toString());
intent.putExtras(bundle); // 把快递包裹塞给意图
startActivity(intent); // 跳转到意图指定的活动页面

然后在下一个活动中获取意图携带的快递包裹,从包裹取出各参数信息,并将传来的数据显示到文本视图。

1
2
3
4
5
6
7
8
9
10
11
// 从布局文件中获取名为tv_receive的文本视图
TextView tv_receive = findViewById(R.id.tv_receive);
// 从上一个页面传来的意图中获取快递包裹
Bundle bundle = getIntent().getExtras();
// 从包裹中取出名为request_time的字符串
String request_time = bundle.getString("request_time");
// 从包裹中取出名为request_content的字符串
String request_content = bundle.getString("request_content");
String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s",
request_time, request_content);
tv_receive.setText(desc); // 把请求消息的详情显示在文本视图上

向上一个Activity返回数据

数据传递经常是相互的,上一个页面不但把请求数据发送到下一个页面,有时候还要处理下一个页面的应答数据,所谓应答发生在下一个页面返回到上一个页面之际。如果只把请求数据发送到下一个页面,上一个页面调用startActivity方法即可;如果还要处理下一个页面的应答数据,此时就得分多步处理

  1. 上一个页面打包好请求数据,调用startActivityForResult方法执行跳转动作,表示需要处理下一个页面的应答数据,该方法的第二个参数表示请求代码,它用于标识每个跳转的唯一性。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    String request = "你吃饭了吗?来我家吃吧";
    // 创建一个意图对象,准备跳到指定的活动页面
    Intent intent = new Intent(this, ActResponseActivity.class);
    Bundle bundle = new Bundle(); // 创建一个新包裹
    // 往包裹存入名为request_time的字符串
    bundle.putString("request_time", DateUtil.getNowTime());
    // 往包裹存入名为request_content的字符串
    bundle.putString("request_content", request);
    intent.putExtras(bundle); // 把快递包裹塞给意图
    // 期望接收下个页面的返回数据。第二个参数为本次请求代码
    startActivityForResult(intent, 0);
  2. 下一个页面接收并解析请求数据,进行相应处理。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 从上一个页面传来的意图中获取快递包裹
    Bundle bundle = getIntent().getExtras();
    // 从包裹中取出名为request_time的字符串
    String request_time = bundle.getString("request_time");
    // 从包裹中取出名为request_content的字符串
    String request_content = bundle.getString("request_content");
    String desc = String.format("收到请求消息:\n请求时间为%s\n请求内容为%s",
    request_time, request_content);
    tv_request.setText(desc); // 把请求消息的详情显示在文本视图上
  3. 下一个页面在返回上一个页面时,打包应答数据并调用setResult方法返回数据包裹。setResult方法的第一个参数表示应答代码(成功还是失败),第二个参数为携带包裹的意图对象。

1
2
3
4
5
6
7
8
9
10
11
String response = "我吃过了,还是你来我家吃";
Intent intent = new Intent(); // 创建一个新意图
Bundle bundle = new Bundle(); // 创建一个新包裹
// 往包裹存入名为response_time的字符串
bundle.putString("response_time", DateUtil.getNowTime());
// 往包裹存入名为response_content的字符串
bundle.putString("response_content", response);
intent.putExtras(bundle); // 把快递包裹塞给意图
// 携带意图返回上一个页面。RESULT_OK表示处理成功
setResult(Activity.RESULT_OK, intent);
finish(); // 结束当前的活动页面
  1. 上一个页面重写方法onActivityResult,该方法的输入参数包含请求代码和结果代码,其中请求代码用于判断这次返回对应哪个跳转,结果代码用于判断下一个页面是否处理成功。如果下一个页面处理成功,再对返回数据解包操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 从下一个页面携带参数返回当前页面时触发。其中requestCode为请求代码,
    // resultCode为结果代码,intent为下一个页面返回的意图对象
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent){ // 接收返回数据
    super.onActivityResult(requestCode, resultCode, intent);
    // 意图非空,且请求代码为之前传的0,结果代码也为成功
    if (intent!=null && requestCode==0 && resultCode== Activity.RESULT_OK) {
    Bundle bundle = intent.getExtras(); // 从返回的意图中获取快递包裹
    // 从包裹中取出名叫response_time的字符串
    String response_time = bundle.getString("response_time");
    // 从包裹中取出名叫response_content的字符串
    String response_content = bundle.getString("response_content");
    String desc = String.format("收到返回消息:\n应答时间为:%s\n应答内容为:%s",
    response_time, response_content);
    tv_response.setText(desc); // 把返回消息的详情显示在文本视图上
    }
    }

为活动补充附加信息

可以把字符串参数放到字符串资源文件中,待App运行之时再从资源文件读取字符串值;接着还能在AndroidManifest.xml中给指定活动配置专门的元数据,App运行时即可获取对应活动的元数据信息;然后利用元数据的resource属性配置更复杂的XML定义,从而为App注册在长按桌面之时弹出的快捷菜单。

利用资源文件配置字符串

利用Bundle固然能在页面跳转的时候传送数据,但这仅限于在代码中传递参数,如果要求临时修改某个参数的数值,就得去改Java代码。然而直接修改Java代码有两个弊端:

代码文件那么多,每个文件又有许多行代码,一下子还真不容易找到修改的地方。

每次改动代码都得重新编译,让Android Studio编译的功夫也稍微费点时间。

有鉴于此,对于可能手工变动的参数,通常把参数名称与参数值的对应关系写入配置文件,由程序在运行时读取配置文件,这样只需修改配置文件就能改变对应数据了。res\values目录下面的strings.xml就用来配置字符串形式的参数,打开该文件,发现里面已经存在名为app_name的字符串参数,它配置的是当前模块的应用名称。现在可于app_name下方补充一行参数配置,参数名称叫作“weather_str”,参数值则为“晴天”,具体的配置内容如下所示:

1
<string name="weather_str">晴天</string>

接着打开活动页面的Java代码,调用getString方法即可根据R.string.参数名称获得指定参数的字符串值。

1
2
3
4
5
// 显示字符串资源
private void showStringResource() {
String value = getString(R.string.weather_str); // 从strings.xml获取名叫weather_str的字符串值
tv_resource.setText("来自字符串资源:今天的天气是"+value); // 在文本视图上显示文字
}

上面的getString方法来自于Context类,由于页面所在的活动类AppCompatActivity追根溯源来自Context这个抽象类,因此凡是活动页面代码都能直接调用getString方法。

利用元数据传递配置信息

尽管资源文件能够配置字符串参数,然而有时候为安全起见,某个参数要给某个活动专用,并不希望其他活动也能获取该参数,此时就不方便到处使用getString了。

Activity提供了元数据(Metadata)的概念,元数据是一种描述其他数据的数据,它相当于描述固定活动的参数信息。打开AndroidManifest.xml,在测试活动的activity节点内部添加meta-data标签,通过属性name指定元数据的名称,通过属性value指定元数据的值。仍以天气为例,添加meta-data标签之后的activity节点如下所示:

1
2
3
<activity android:name=".MetaDataActivity">
<meta-data android:name="weather" android:value="晴天" />
</activity>

元数据的value属性既可直接填字符串,也可引用strings.xml已定义的字符串资源,引用格式形如
@string/字符串的资源名称。下面便是采取引用方式的activity节点配置:

1
2
3
4
5
<activity android:name=".MetaDataActivity">
<meta-data
android:name="weather"
android:value="@string/weather_str" />
</activity>

配置好了activity节点的meta-data标签,再回到Java代码获取元数据信息,获取步骤分为下列3步:

  • 调用getPackageManager方法获得当前应用的包管理器。
  • 调用包管理器的getActivityInfo方法获得当前活动的信息对象。
  • 活动信息对象的metaData是Bundle包裹类型,调用包裹对象的getString即可获得指定名称的参数值。
1
2
3
4
5
6
7
8
9
10
11
12
13
// 显示配置的元数据
private void showMetaData() {
try {
PackageManager pm = getPackageManager(); // 获取应用包管理器
// 从应用包管理器中获取当前的活动信息
ActivityInfo act = pm.getActivityInfo(getComponentName(),PackageManager.GET_META_DATA);
Bundle bundle = act.metaData; // 获取活动附加的元数据信息
String value = bundle.getString("weather"); // 从包裹中取出名叫weather的字符串
tv_meta.setText("来自元数据信息:今天的天气是"+value); // 在文本视图上显示文字
} catch (Exception e) {
e.printStackTrace();
}
}

给应用页面注册快捷方式

元数据不单单能传递简单的字符串参数,还能传送更复杂的资源数据

元数据的meta-data标签除了name属性和value属性,还拥有resource属性,该属性可指定一个XML文件,表示元数据想要的复杂信息保存于XML数据之中

借助元数据以及指定的XML配置可以完成快捷方式功能

首先打开res/values目录下的strings.xml,在resources节点内部添加下述的3组(每组两个,共6个)字符串配置,每组都代表一个菜单项,每组又分为长名称和短名称,平时优先展示长名称,当长名称放不下时才展示短名称。这3组6个字符串的配置定义示例如下

1
2
3
4
5
6
<string name="first_short">first</string>
<string name="first_long">启停活动</string>
<string name="second_short">second</string>
<string name="second_long">来回跳转</string>
<string name="third_short">third</string>
<string name="third_long">登录返回</string>

接着在res目录下创建名为xml的文件夹,并在该文件夹创建shortcuts.xml,这个XML文件用来保存3组菜单项的快捷方式定义

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
<?xml version="1.0" encoding="utf-8"?>
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">

<shortcut
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:shortcutId="first"
android:shortcutLongLabel="@string/first_long"
android:shortcutShortLabel="@string/first_short">

<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.dongnaoedu.chapter04.ActStartActivity"
android:targetPackage="com.dongnaoedu.chapter04" />
<categories android:name="android.shortcut.conversation" />
</shortcut>


<shortcut
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:shortcutId="second"
android:shortcutLongLabel="@string/second_long"
android:shortcutShortLabel="@string/second_short">

<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.dongnaoedu.chapter04.JumpFirstActivity"
android:targetPackage="com.dongnaoedu.chapter04" />
<categories android:name="android.shortcut.conversation" />
</shortcut>

<shortcut
android:enabled="true"
android:icon="@mipmap/ic_launcher"
android:shortcutId="third"
android:shortcutLongLabel="@string/third_long"
android:shortcutShortLabel="@string/third_short">

<intent
android:action="android.intent.action.VIEW"
android:targetClass="com.dongnaoedu.chapter04.LoginInputActivity"
android:targetPackage="com.dongnaoedu.chapter04" />
<categories android:name="android.shortcut.conversation" />
</shortcut>
</shortcuts>

由上述的XML例子中看到,每个shortcut节点都代表了一个菜单项,该节点的各属性说明如下:

  • shortcutId:快捷方式的编号。
  • enabled:是否启用快捷方式。true表示启用,false表示禁用。
  • icon:快捷菜单左侧的图标。
  • shortcutShortLabel:快捷菜单的短标签。
  • shortcutLongLabel:快捷菜单的长标签。优先展示长标签的文本,长标签放不下时才展示短标签的文本。

以上的节点属性仅仅指明了每项菜单的基本规格,点击菜单项之后的跳转动作还要由shortcut内部的intent节点定义,该节点主要有targetPackagetargetClass两个属性需要修改,其中targetPackage属性固定为当前App的包名,而targetClass属性描述了菜单项对应的活动类完整路径。

然后打开AndroidManifest.xml,找到MainActivity所在的activity节点,在该节点内部补充如下的元数据配置,其中name属性为android.app.shortcuts,而resource属性为@xml/shortcuts

1
2
<meta-data android:name="android.app.shortcuts"android:resource="@xml/shortcuts"
/>

这行元数据的作用,是告诉App首页有个快捷方式菜单,其资源内容参见位于xml目录下的shortcuts.xml。完整的activity节点配置示例如下

1
2
3
4
5
6
7
8
9
10
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- 指定快捷方式。在桌面上长按应用图标,就会弹出@xml/shortcuts所描述的快捷菜单 -->
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>

image

Powered by Hexo & Theme Keep
Unique Visitor Page View