Android Activity

【Android Activity】

什么是 Activity?

四大组件之一,通常一个用户交互界面对应一个 activity。activity 是Context 的子类,同时实现了 window.callback和 keyevent.callback, 可以处理与窗体用户交互的事件。

常见的 Activity 类型有 FragmentActivitiy,ListActivity,TabAcitivty 等。 如果界面有共同的特点或者功能的时候,还会自己定义一个 BaseActivity。

Activity 生命周期


两两对应:onCreate 创建与 onDestroy 销毁;onStart 可见与 onStop 不可见;onResume 可编辑(即焦点)与 onPause;

什么情况下Activity走了onCreat(),而不走onStart();
当onCreate中发生Crash的时候。
何时调用onRestart

在 Activity 被 onStop 后,但是没有被 onDestroy,在再次启动此 Activity 时就调用 onRestart(而不再调用 onCreate)方法;如果被 onDestroy 了,则是调用 onCreate 方法。

onPause vs onStop

(a) 只要activity还可见,就不会调用onStop。所以我们可以知道onStop调用时机是:当一个activity对用户已经不可见的时候就会调用。
(b) 官方说明:onStop可能永远不会被调用:当处于低内存状态下,系统没有足够的内存保持你的activity的处理运行在activity的onPause调用之后。
(c) 从(b)知道onStop在低内存情况下不会被调用,但是:onPause一定会被调用

onResume

对于你的activity来说,onResume调用后就可以和用户交互,这是很好的地方去开始动画,打开互斥访问设备元件(例如:照相机),etc.
但是有一点我们必须认清:onResume不是最好的指向标说明你的activity对于用户是可见的,例如系统的窗口可能在你的activity前面。所以应该用:onWindowFocusChanged来知道我们的activity是否对用户可见。

onWindowFocusChanged

当前activity的窗口获得或失去焦点的时候会调用。(activity是否对用户可见)

大多数情况下只要调用了onResume 就会调用 onWindowFocusChanged,也有例外,比如下拉系统菜单的时候只会调用onWindowFocusChanged。
应用:我们在下拉菜单中改变了网络的状态(开启或者关闭),我们这时候就不能在onResume()中处理更新网络状态,而应该将更新网络状态放到onWindowFocusChanged中处理。

官方解释
当前activity的窗口获得或失去焦点的时候会调用。
这个函数是最好的方向标对于activity是否对用户可见,它默认的实现是清除键跟踪状态,所以应该总是被调用。
它也提供了全局的焦点状态,它的管理是独立于activity生命周期的。当焦点改变时一般都伴随着生命周期的改变,你不应该依赖onWindowFocusChanged 调用和其他生命周期的方法(例如onResume) 的先后顺序,来处理我们要做的事情。
通常的规则是,当一个activity被唤醒,那么就拥有窗口焦点。除非这个窗口已经显示了对话框或者其他弹出框抢占焦点,这个窗口只有等到这些对话框关闭后,才能获取焦点,同理,当系统显示系统级的窗口,系统级的窗口会临时的获取窗口输入焦点同时不会暂停前景 activity。

onStart()和onResume()有什么区别?
onStart()中视图不可见,在onResume()中视图可见;onStart()属于可见进程,onResume()属于前台进程;

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

此时的生命周期跟清单文件里的配置有关系。
1、不设置 Activity 的 android:configChanges 时,切屏会重新调用各个生命周期 默认首先销毁当前 activity,然后重新加载
如下图,当横竖屏切换时先执行 onPause/onStop 方法

2、设置 Activity 的 android:configChanges=”orientation|screenSize“时(两个都要设置),切屏不会重新调用各个生命周期,会执行 onConfigurationChanged 方法。
3、设置**Activity的**android:screenOrientation属性时,切屏任何方法都不调用
注意:
1.如果<activity>配置了android:screenOrientation属性,则会使android:configChanges=”orientation”失效。
2.模拟器与真机差别很大:模拟器中如果不配置android:configChanges属性或配置值为orientation,切到横屏执行一次销毁->重建,切到竖屏执行两次。真机均为一次。模拟其中如果配置android:configChanges=”orientation|keyboardHidden”,切竖屏执行一次onConfigurationChanged,切竖屏执行两次。真机均为一次。

两个Activity之间跳转的生命周期过程

一般情况下比如说有两个 activity,分别叫 A,B,当在 A 里面激活 B 组件的时候, A 会调用 onPause()方法,然后 B 调 用 onCreate() ,onStart(), onResume()。
这个时候 B 覆盖了窗体, A 会调用 onStop()方法. 如果 B 是个透明的,或者是对话框的样式, 就不会调用 A 的onStop()方法

Activity 的状态

a) foreground activity
b) visible activity
c) background activity
d) empty process

如何保存 Activity 状态

Activity 的状态通常情况下系统会自动保存
一般来说, 调用 onPause()和 onStop()方法后的 activity 实例仍然存在于内存中, activity 的所有信息和状态数据不会消失, 当 activity 重新回到前台之后, 所有的改变都会得到保留。
但是当系统内存不足时, 调用 onPause()和 onStop()方法后的 activity 可能会被系统摧毁, 此时内存中就不会存有该 activity 的实例对象了。如果之后这个 activity 重新回到前台, 之前所作的改变就会消失。

解决方案:覆写 onSaveInstanceState()方法。
onSaveInstanceState()方法接受一个 Bundle 类型的参数, 开发者可以 将状态数据存储到这个 Bundle 对象中, 这样即使 activity 被系统摧毁, 当用户重新启动这个 activity 而调用它的 onCreate()方法时, 上述的 Bundle 对象会作为实参传递给 onCreate()方法, 开发者可以从 Bundle 对象中取出保存的 数据, 然后利用这些数据将 activity 恢复到被摧毁之前的状态。

需要注意的是, onSaveInstanceState()方法并不是一定会被调用的, 因为有些场景是不需要保存状态数据的。比如用户按下 BACK 键退出 activity 时, 用户显然想要关闭这个 activity, 此时是没有必要保存数据以供下次恢复的, 也就是 onSaveInstanceState()方法不会被调用。
如果调用 onSaveInstanceState()方法, 调用将发生在 onPause()或onStop()方法之前

@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO Auto-generated method stub
super.onSaveInstanceState(outState);
}

各种情况下函数调用过程

第一次进入ActivityA:

A | onCreate
A | onStart
A | onResume
A | onWindowFocusChanged | hasFocus:true

下拉系统菜单(已开启程序,从屏幕上往下拉)

A | onWindowFocusChanged | hasFocus:false

收回系统下拉菜单(已开启程序,且下拉菜单已显示)

A | onWindowFocusChanged | hasFocus:true

从ActivityA中退出:

A | onPause
A | onWindowFocusChanged | hasFocus:false
A | onStop
A | onDestory

ActivityA启动ActivityB(普通Activity):

A | onWindowFocusChanged | hasFocus:false
A | onPause
B | onCreate
B | onStart
B | onResume
B | onWindowFocusChanged | hasFocus:true
A | onSaveInstanceState | param: 1
A | onStop

ActivityA启动ActivityB(对话框Activity):

A | onWindowFocusChanged | hasFocus:false
A | onPause
B | onCreate
B | onStart
B | onResume
B | onWindowFocusChanged | hasFocus:true

从AcitivityB(普通Activity)返回到ActivityA:

B | onPause
A | onRestart
A | onStart
A | onResume
A | onWindowFocusChanged | hasFocus:true
B | onWindowFocusChanged | hasFocus:false
B | onStop
B | onDestory

从ActivityB(对话框Activity)返回到ActivityA:

B | onPause
A | onResume
A | onWindowFocusChanged | hasFocus:true
B | onWindowFocusChanged | hasFocus:false
B | onStop
B | onDestory

手机黑屏时:

A | onPause
A | onSaveInstanceState | param: 1
A | onStop
A | onWindowFocusChanged | hasFocus:false

手机亮屏时:

A | onWindowFocusChanged | hasFocus:true
A | onRestart
A | onStart
A | onResume

按home键(已启动ActivityA)

A | onPause
A | onWindowFocusChanged | hasFocus:false
A | onSaveInstanceState | param: 1
A | onStop

多任务切回程序(开启程序,home键切换程序到后台)

A | onRestart
A | onStart
A | onResume
A | onWindowFocusChanged | hasFocus:true

点击应用图标重启程序(开启程序,home键切换到后台)

A | onRestart
A | onStart
A | onResume
A | onWindowFocusChanged | hasFocus:true

点击back键(已开启程序,back键未自己处理)

A | onPause
A | onWindowFocusChanged | hasFocus:false
A | onStop
A | onDestory

点击多任务键(已开启程序)

A | onWindowFocusChanged | hasFocus:false
A | onPause
A | onSaveInstanceState | param: 1
A | onStop

切回程序(已开启程序,且已点击多任务键)

A | onRestart
A | onStart
A | onResume
A | onWindowFocusChanged | hasFocus:true

横竖屏切换(未配置android:configChanges)

A | onPause
A | onSaveInstanceState | param: 1
A | onStop
A | onDestory
A | onCreate
A | onStart
A | onRestoreInstanceState | param: 1
A | onResume
A | onWindowFocusChanged | hasFocus:true

横竖屏切换(配置android:configChanges=”orientation”)

竖切横
A | onConfigurationChanged | newConfig:{1.0 460mcc3mnc [zh_CN_#Hans] ldltr sw360dp w592dp h336dp 320dpi nrml land finger -keyb/v/h -nav/h suim:1 s.22}
A | onPause
A | onSaveInstanceState | param: 1
A | onStop
A | onDestory
A | onCreate
A | onStart
A | onRestoreInstanceState | param: 1
A | onResume
A | onWindowFocusChanged | hasFocus:true
横切竖
A | onConfigurationChanged | newConfig:{1.0 460mcc3mnc [zh_CN_#Hans] ldltr sw360dp w360dp h580dp 320dpi nrml port finger -keyb/v/h -nav/h suim:1 s.23}

横竖屏切换(配置android:configChanges=”orientation|scre

竖切横
A | onConfigurationChanged | newConfig:{1.15 460mcc3mnc [zh_CN_#Hans] ldltr sw360dp w592dp h336dp 320dpi nrml land finger -keyb/v/h -nav/h suim:1 s.46}
横切竖
A | onConfigurationChanged | newConfig:{1.15 460mcc3mnc [zh_CN_#Hans] ldltr sw360dp w360dp h580dp 320dpi nrml port finger -keyb/v/h -nav/h suim:1 s.47}

ActivityA启动ActivityB,再从ActivityB回到ActivityA,此时ActivityB的onDestory先调用还是ActivityA的onResume先调用?
ActivityA的onResume()先调用,ActivityB的onDestory后调用。

Activity 之间的跳转

Activity 之间的跳转分为 2 种:
显式跳转:在可以引用到那个类, 并且可以引用到那个类的字节码时可以使用。一般用于自己程序的内部。显式跳转不可以跳转到其他程序的页面中。
隐式跳转:可以在当前程序跳转到另一个程序的页面。隐式跳转不需要引用到那个类,但是必须得知道那个界面的动作(action)和信息(category)

Activity 之间通过 Intent 进行通信。Intent 即意图,用于描述一个页面的信息,同时也是一个数据的载体。

1、显式跳转

//创建一个 Intent 对象,并传递当前对象(Context 对象)和要跳转的Activity 类字节码
Intent intent = new Intent(this, SecondActivity.class);
//启动第二个 Activity
startActivity(intent);

2、隐式跳转

隐式跳转可以跳转到其他程序的 Activity,只要知道 Activity 的动作(action)以及信息(category)。
因此,能够被隐式跳转的 Activity,在清单文件中声明时必须指定动作和信息这两个属性。
修改工程清单文件中 SecondActivity 的配置信息。

<activity android:name="com.itheima.activitySkip.SecondActivity">
    <!-- 配置意图过滤器 -->
    <intent-filter >
        <!-- 在意图过滤器中设置 action 和 category,当有匹配的 action 和 category的时候启动该 Activity。这里使用 Android 提供的默认 category 即可 -->
        <action android:name="com.itheima.activitySkip.SecondActivity"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>
//创建一个 Intent 对象
Intent intent = new Intent();
//设置 Action
intent.setAction("com.itheima.activitySkip.SecondActivity");
//对于 android.intent.category.DEFAULT 类型的信息为 Android 系统默认的信息,省略也可以
intent.addCategory("android.intent.category.DEFAULT");
//启动 Activity
startActivity(intent);
跳转到浏览器界面
    //跳转到浏览器界面
    public void skip2Browser(View view) {
//创建一个 Intent 对象
        Intent intent = new Intent();
//设置 Action
        intent.setAction("android.intent.action.VIEW");
//设置 category
        intent.addCategory("android.intent.category.BROWSABLE");
//设置参数
        intent.setData(Uri.parse("http://www.itheima.com"));//通过浏览器打开此链接
//启动 Activity
        startActivity(intent);
    }
跳转到发送短信界面
//跳转到发送短信界面
public void skip2Mms(View view){
//创建一个 Intent 对象
    Intent intent = new Intent();
//设置 Action
    intent.setAction("android.intent.action.VIEW");
//设置 category
    intent.addCategory("android.intent.category.BROWSABLE");
//设置参数
    intent.setData(Uri.parse("sms:10086"));
//设置短信内容
    intent.putExtra("sms_body", "301");
//启动 Activity
    startActivity(intent);
}

Activity 的任务栈

任务栈是用来提升用户体验而设计的:
1. 程序打开时就创建了一个任务栈, 用于存储当前程序的 activity,所有的 activity 属于一个任务栈。
2. 一个任务栈包含了一个 activity 的集合, 去有序的选择哪一个 activity 和用户进行交互:
只有在任务栈栈顶的 activity 才可以跟用户进行交互。
3. 任务栈可以移动到后台, 并且保留了每一个 activity 的状态. 并且有序的给用户列出它们的任务, 而且还不丢失它们状态信息。
4. 退出应用程序时:当把所有的任务栈中所有的 activity 清除出栈时,任务栈会被销毁,程序退出。

任务栈的缺点
1. 每开启一次页面都会在任务栈中添加一个 Activity,而只有任务栈中的 Activity 全部清除出栈时,任务栈被销毁,程序才会退出,这样就造成了用户体验差, 需要点击多次返回才可以把程序退出了。
2. 每开启一次页面都会在任务栈中添加一个Activity还会造成数据冗余, 重复数据太多, 会导致内存溢出的问题(OOM)。

为了解决任务栈产生的问题,Android 为 Activity 设计了启动模式。

Activity 启动模式

启动模式(launchMode)在多个 Activity 跳转的过程中扮演着重要的角色,它可以决定是否生成新的 Activity 实例,是否重用已存在的 Activity 实例,是否和其他 Activity 实例公用一个 task 里。

task 是一个具有栈结构的对象,一个 task 可以管理多个 Activity,启动一个应用,也就创建一个与之对应的 task。

Activity 一共有以下四种 launchMode:
1.standard
2.singleTop
3.singleTask
4.singleInstance
可以在 AndroidManifest.xml 配置的 android:launchMode 属性为以上四种之一即可。

1 standard

默认启动模式,每次激活Activity时都会创建Activity,并放入任务栈中。

standard 模式是默认的启动模式,不用为配置 android:launchMode 属性即可,当然也可以指定值 为 standard。

原理如下:

如图所示,每次跳转系统都会在 task 中生成一个新的 FirstActivity 实例,并且放于栈结构的顶部,当我们按下后退键时,才能看到原来的 FirstActivity 实例。

2 singleTop

如果在任务的栈顶正好存在该Activity的实例, 就重用该实例,否者就会创建新的实例并放入栈顶(即使栈中已经存在该Activity实例,只要不在栈顶,都会创建实例)。
原理:

3 singleTask

如果在栈中已经有该Activity的实例,就重用该实例(会调用实例的onNewIntent())。重用时,会让该实例回到栈顶,因此在它上面的实例将会被移除栈。如果栈中不存在该实例,将会创建新的实例放入栈中。
原理:

在图中的下半部分是 SecondActivity 跳转到 FirstActivity 后的栈结构变化的结果,我们注意到,SecondActivity消失了,没错,在这个跳转过程中系统发现有存在的 FirstActivity 实例,于是不再生成新的实例,而是将 FirstActivity之上的 Activity 实例统统出栈,将 FirstActivity 变为栈顶对象,显示到幕前。

4 singleInstance

在一个新栈中创建该Activity实例,并让多个应用共享改栈中的该Activity实例。一旦该模式的Activity的实例存在于某个栈中,任何应用再激活改Activity时都会重用该栈中的实例,其效果相当于多个应用程序共享一个应用,不管谁激活该Activity都会进入同一个应用中

我们修改 FirstActivity 的 launchMode=”standard”,SecondActivity 的launchMode=”singleInstance”,由于涉及到了多个栈结构,我们需要在每个 Activity 中显示当前栈结构的 id,所以我们为每个 Activity 添加如下代码:

textView.setText(this.toString());
taskIdView.setText("current task id: " + this.getTaskId());

然后我们再演示一下这个流程:

我们发现这两个 Activity 实例分别被放置在不同的栈结构中,关于 singleInstance 的原理图如下

我们看到从 FirstActivity 跳转到 SecondActivity 时,重新启用了一个新的栈结构,来放置 SecondActivity 实例,然后按下后退键,再次回到原始栈结构;图中下半部分显示的在 SecondActivity 中再次跳转到 FirstActivity,这个时候系统会在原始栈结构中生成一个 FirstActivity 实例,然后回退两次,注意,并没有退出,而是回到了 SecondActivity,

为什么呢?是因为从 SecondActivity 跳转到 FirstActivity 的时候,我们的起点变成了 SecondActivity 实例所在的栈结构,这样一来,我们需要“回归”到这个栈结构。

如果我们修改 FirstActivity 的 launchMode 值为 singleTop、singleTask、singleInstance 中的任意一个,流程将会如图所示:

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值