四大组件之Activity的再学习

来源:互联网 发布:mysql字段约束 编辑:程序博客网 时间:2024/06/15 05:05

activity作为核心组件有7大生命周期方法,必须得在清单注册;
作为Context的一员具有强大的资源访问能力;
作为控制器负责UI显示和事件处理 是与用户交互的窗口/屏幕,不是视图View,
但是在它的windowphne窗口中包含View
四大组件:
共性:都有生命周期方法、都得在清单注册
特性:分工不同(作用不同)、实现不同
Activity:呈现View并与用户交互
Service:后台执行耗时操作且提高其优先级
BroadcastReceiver:实现跨进程、跨组件的一对多通讯
ContentProvider:实现私有数据的对外共享
能够让不同应用之间可以相互保存或读取彼此的内容
这里写图片描述
这里写图片描述
Activity中包含界面(但说就是一个界面/手机的一屏界面的说法是不正确的,在面向对象里
是这个字不能乱说,是通常表示的是继承关系;而有表示的是组和关系)引擎有启动和停止的方法,
汽车也有启动和停止的方法,而汽车的启动和停止就是来自引擎的启动和停止;
但不能说汽车就是引擎;而是汽车里面有引擎;
在面向对象中重用一个类的方式有二种:
继承和组合(在一个类里new另一个类的对象)
Class A {
public void x(){}
public void y(){}
}
要用类的x/y方法,二种方式:拓展一个类的能力
1.继承:是
Class B extends A{//继承就会拥有那二个方法,就类似拼爹
}
2.组合:有
Class B {//把A的对象组合到B里,就类似掉个金龟婿
private A a;//拿到你的引用
public B(){
a = new A();
}
}
结论:Activity不是界面,他的父类不是任何一个界面类;
android里能真正看见的界面是view,只有View是可见的;Activity本身是不应该可见的;
你看见的是activity里包含的视图树;Activity从本质上就是一个控制器,
如果抛开他里面所包含的这个界面的话,他和service的功能几乎是相似的;
Activity中包含了一个PhoneWindow手机窗口对象,这是activity能被看见的原因,
但是PhoneWindow手机窗口对象也是不可见的,它是Window的子类,那么在android里Window窗口也是不可见的;Window这个类在android里是一个抽象类,他只是对应了窗口的行为,而不包含有窗口的表现;说白了他看不见,但定义了所有窗口应该具有的行为;PhoneWindow手机窗口对象中包含了一个DecorView对象(是ViewGroup的子类,ViewGroup是View的子类),
所以android中我们真正能看见的整棵视图树是DecorView;一个Activity包含了一屏界面;
PhoneWindow的内部类DecorView:线性布局:ActionBar上+中间FramLayout+ActionBar下
这里写图片描述
视图树:是Android中的界面组织方式;
在Android中任何可见的对象一定是View,这是永恒的真理!
它可以说就是一个可以放置界面的窗口, Activity窗口中的界面也是用xml文件表示的,
放置在res-layout下面,这个窗口中的界面中可以放置各种控件。每生成一个新的Activity后,我们需要在AndroidManifest.xml中注册一下这个Activity;通俗讲Activity就是一个应用程序的门面,也可以理解成就是WEB程序中的一个页面,当然与web程序中的页面不同的是,web中的一个页面可能只是一个纯粹的展示页面不与用户进行任何交互,而几乎所有的Activity都会与用户交互。当然两者在架构上也有本质区别,Activity与用户的交互通过触发UI的不同事件完成的。而Web程序是通过请求,响应来完成交互的。
还有在android中颠覆了很多常规想法,比如在一个Activity中可以打开另一个不在同一应用的Activity。这在其他程序是不可想象的。当然这种设计的出发点也是为了节省系统资源。从View层的角度来看,Activity承载了与用户交互的不同控件。从控制层看,也就是内部逻辑,Activity需要保持各个界面的状态,背后会做很多持久化的操作。包括妥善管理生命周期的各个阶段。首先Activity是android四大组件之一。它是单独的作为用户与程序交互的一个载体。几乎所有的Activity都与用户交互。Activity创建了一个窗口,你可以通过setContentView这个方法将需要的UI放置在窗口中。任何一个应用程序都可以调用单独的一个Activity。Activity的继承关系:Activity→ContextThemeWrapper→ContextWrapper→Context;
最后大部分的Activity的子类都需要实现以下两个方法:
onCreate(Bundle savedInstanceState)方法是初始化activity的地方.
在这儿通常可以调用setContentView(int)设置在资源文件中定义的UI,
使用findViewById(int) 可以获得UI中定义的控件.
onPause()方法是使用者准备离开activity的地方,任何的修改都应该被提交(通常用于ContentProvider保存数据).
所有Activity必须在清单文件里注册一下才能使用。实际应用中包含多个activity,不同的activity向用户呈现不同的操作,android应用的多个activity组成activity栈,当前活动的Activity位于栈顶;Activity对于Android应用的作用类似于Servlet对于Web应用的作用,都是负责与用户交互,并向应用呈现相应状态;在系统中的Activity被一个Activity栈所管理。当一个新的Activity启动时,将被放置到栈顶,成为运行交互中的Activity,
前一个Activity保留在栈中,不再放到前台,直到新的Activity退出为止。
Activity的编写及对象的构建:
1.Activity编写:
一般会直接或间接的继承Activity
2.Activity的注册:
每个Activity都要在清单配置文件进行注册
3.Activity对象:
此对象由android AF 创建并管理(底层会用到反射)
Activity中必须包含一个无参的构造方法;
不能自己构建Activity类的对象;
Android中发生内存泄露的原因和解决措施:
将activity对象的引用交给生存期比自己长的位置,导致他在结束时对方还没有结束;
对方没有结束,还持有你的引用,耗着你就不让你死;
避免内存泄露:
1. 尽量不要将引用交给生存期比自己长的位置;更严格的是尽量不要把自己的引用交给别人,
让别人控制你的生死;这样最安全;
2. 如果已经交了,那么在你死之前得把他先给弄死(即在组件结束前先结束持有自己的引用的对象);
可以用一个控制变量isloop,默认是true执行死循环,用isLoop而不是true就是给自己留了一个活口,
之后想结束actvity的时候先把他设置成false;那他循环结束了,线程也就结束了,
然后你的activity就可以正常结束了;所以不是不能把引用交给线程,而是要保证你activity结束时能随时结束持有你引用的线程;如果你保证不了这一点那么你尽量就不要起这个线程,因为这可能造成内存泄露;

Activity对象生命周期管理(生命周期方法(三对加一7个)):
onCreate(),onStart(),onResume(), onPause(),onStop(),onDestory(), onRestart()

这里写图片描述

stop到start中有一个restart方法
所谓Activity生命周期就是此类的对象从创建、初始化、服务、到销毁所经历的几个阶段;每个阶段都会经历几个生命周期方法,具体在这些方法中做什么取决于你的具体业务;现在重点掌握的就是这几个生命周期方法运行的顺序;和其他手机平台的应用程序一样,Android应用程序的生命周期是被统一掌控的,也就是说我们写的应用程序命运掌握在别人(系统)的手里,我们不能改变它,只能学习并适应它。简单地说一下为什么是这样:我们手机在运行一个应用程序的时候,有可能打进来电话发进来短信,或者没有电了,这时候程序都会被中断,优先去服务电话的基本功能,另外系统也不允许你占用太多资源,至少要保证电话功能吧,所以资源不足的时候也就有可能被干掉。
我们自己写的Activity会按需要重载这些方法,onCreate是免不了的,在一个Activity正常启动的过程中,他们被调用的顺序是 onCreate -> onStart -> onResume, 在Activity被干掉的时候顺序是onPause -> onStop -> onDestroy ,这样就是一个完整的生命周期,但是有人问了,程序正运行着呢来电话了,这个程序咋办?中止了呗,如果中止的时候新出的一个Activity是全屏的那么:onPause->onStop ,恢复的时候onRestart->onStart->onResume ,如果打断这个应用程序的是一个Theme为Translucent 或者Dialog的Activity那么只是onPause ,恢复的时候onResume 。
每一个活动( Activity )都处于某一个状态,对于开发者来说,是无法控制其应用程序处于某一个状态的,这些均由系统来完成。 但是当一个活动的状态发生改变的时候,开发者可以通过调用 onXX() 的方法获取到相关的通知信息。 在实现 Activity 类的时候,通过覆盖这些方法即可在你需要处理的时候来调用。
onCreate: 《实例被创建》对于任何一个Activity的实例,该方法只在其创建时执行一次。 生
onStart: 《进入可见状态》每次新启动或者重新启动Activity实例时执行此方法(部分可见)
onResume: 每次进入Running屏前可见可交互状态《进入交互状态》
onPause: 进入Paused屏后暂停可见不可交互状态《离开交互状态》
onStop: 每次将Activity实例切换到后台时执行的方法(不可见不可操作)
onDestroy: 死亡销毁状态 对于任何一个Activity实例,该方法只在实例被销毁时执行一次死
onPause暂停可见》onResume运行交互 该Activity再次返回前台
onStop后台停止》onReStart重新启动《重新进入可见状态》–>onStart启动–>onResume运行交互该Activity再次返回前台
1)运行交互》暂停可见 onPause
2)运行交互》后台停止 onPause-》onStop
3)暂停可见》后台停止 onStop
4)后台停止》运行交互 onReStart-》onStart-》onResume
5)暂停可见》运行交互 onResume
启动Activity实例: onCreate-》onStart-》onResume
销毁Activity实例:
处于onResume运行交互状态的Activity实例:onPause暂停可见->onStop后台停止->onDestroy销毁
处于onPause暂停可见状态的Activity实例:onStop停止可见->onDestroy销毁
处于onStop停止状态的Acitivty实例:onDestroy销毁

这里写图片描述
由于下一个activity在这个方法返回之前不会resume,所以代码执行要快
七大生命周期方法中三大关键循环
1.关乎生死的完整生命周期:从onCreate(Bundle)开始到onDestroy()结束
Activity在onCreate()设置所有的“全局”状态,在onDestory()释放所有的资源。
首先我们运行程序打开MainActivity依次调用onCreate->onStart->onResume,这时MainActivity在栈顶(与我们交互的Activity都在栈顶)。然后按下back返回键,则会依次调用onPause->onStop->onDestory。这属于一个关乎生死的完整生命周期。
onCreate 在这里创建界面,做一些数据的初始化工作;对于任何一个activity实例都是只执行一次的:适合做在构造方法里面做的事情;对于任何一个对象他的构造方法也是只会执行一次的,如果再执行那就是new另外一个对象了;如像全局成员变量的初始化,都适合写在onCreate方法里面,因为一创建这个对象就可以调用onCreate方法对他进行初始化;对于每个对象,这个方法只会执行一次,又不用担心重复初始化的问题;如果将初始化代码写到onStart方法里,他可能会反复执行,就会重复初始化,可能之前保存在成员变量里的状态就会丢失;创建Activity时调用,设置在该方法中,还以Bundle的形式提供对以前存储的任何状态的访问;
onDestroy:适合用来进行资源释放,你这个对象在运行期间可能占用了一些资源,那么当这个对象结束的时候把资源都释放掉;把你占用的所有资源,把所有持有你引用的位置的对象都释放掉;你才能保证你的activity实例也能够被销毁;在activity被完全从系统内存中移除时调用;这是activity被干掉前最后一个被调用方法了,可能是外面类调用finish方法或者是系统为了节省空间将它暂时性的干掉;可以用isFinishing()来判断它,如果你有一个ProgressDialog在线程中转动,请在onDestroy里把他cancel掉,
不然等线程结束的时候,调用Dialog的cancel方法会抛异常的。

2.可见生命周期:
可见生命周期,从onStart()开始到onStop()结束。说白了就是一个Activity被另一个activity完全覆盖掉,
然后又重新回到前台这一个过程称之为可见生命周期。首先我们打开OtherActivity这个窗口,
此时MainActivity将被覆盖掉,则会依次调用onPause->onStop。在内存不足的时候,系统也会杀死MainActivity进程。然后,按下back返回键,MainActivity又回到前台;此时会调用onRestart->onStart->onResume。onStart onStop这一对叫可见周期;决定你的activity是否可见;执行了onStart启动方法至少可以保证部分可见了;而执行了onStop停止方法后决定了你的activity完全进入后台停止状态了,你就看不见了;用于我们的acivity前后台频繁替换时使用;可重复执行;
onStart在Activity变为在屏幕上对用户可见时调用(此时可见但不可交互);
onStop在activity被停止并转为不可见阶段及后续的生命周期事件时调用;变得不可见,被下一个activity覆盖了

3.前台交互生命周期:
前台交互生命周期,从onResume()开始到onPause()结束。在这段时间里,该Activity处于所有Activity的最前面和用户进行交互。Activity可以经常性地在resumed和paused状态之间切换。说白了就是一个Activity覆盖到另一个Activity上面,但是并没有完全覆盖掉。首先,我们将OtherActivity打开,这时OtherActivity以对话框模式打开,悬浮在MainActivity上面,直接调用的onPause()。然后我们按下返回键,则调用的是onResume。对于这种没有完全覆盖的状态只会在onPause与onResume两个方法之间切换。onResume在activity开始与用户交互时调用(无论是启动还是重新启动一个活动,该方法总是被调用);变成和用户可交互的,(在activity 栈系统通过栈的方式管理这些个Activity的最上面,运行完弹出栈,则回到上一个Activity);
onPause在activity被暂停或回收cpu和其他资源时调用,该方法用于保存活动状态的,也是保护现场,压栈的;到这一步是可见但不可交互的,系统会停止动画等消耗CPU 的事情从上文的描述已经知道,应该在这里保存你的一些数据,因为这个时候你的程序的优先级降低,有可能被系统收回。在这里保存的数据,应该在onResume里读出来,注意:这个方法里做的事情时间要短,因为下一个activity不会等到这个方法完成才启动

Activity实例四种本质区别的状态:前提是已经存在activity实例并且没有被销毁
1) Running屏前可见可交互状态:一个新activity启动入栈后,它在屏幕最前端,处于栈的最顶端,是可见可交互的,并且可以获得焦点;在屏幕的前台(Activity栈顶),叫做活动状态或者屏前运行交互状态(active or Running)Running<可见可操作>
2)Paused屏后暂停可见不可交互状态:当Activity被另一个透明或者Dialog样式的Activity覆盖的状态,此时它依然与窗口管理器保持连接,系统继续维护其内部状态,所以它仍然可见,但已失去焦点,故处于可见不可交互状态;如果一个Activity失去焦点但依然可见(一个新的非全屏的Activity 或者一个透明的Activity 被放置在栈顶),叫做暂停可见状态Paused。一个暂停状态的Activity依然保持活力(保持所有的状态,成员信息,和窗口管理器保持连接),但是在系统内存极端低下的时候将被杀掉。Paused<可见不可操作>
3)Stopped后台停止状态:完全被切换到后台或者被另外的Activity完全覆盖掉,导致无法看见它,它依然保持所有状态和成员信息,但是它不再可见,所以它的窗口被隐藏,当系统内存需要被用在其他地方的时候,Stopped的Activity将被杀掉;点击HONE键就启动了桌面activity,遮盖了刚才启动的处于交互状态的activity,即被切换到了后台状态,它还活着,只是被切换到了后台,你看不见它了;在一个activity中启动了另外一个activity,前面的那个activity被切换到了后台状态,它还活着,只是被切换到了后台,你看不见到它了;当Activity处于Stopped状态时,一定要保存当前数据和当前的UI状态,否则Activity一旦退出或关闭时,当前的数据和UI状态就丢失了;Stopped<不可见也不可操作>
4) Killed死亡销毁状态:Activity被杀掉以后或者启动以前,处于Killed状态;这时Activity已经被移除Activity堆栈中,需要重新启动才能显示和使用;如果一个Activity是Paused或者Stopped状态,系统可以将该Activity从内存中删除,Android系统采用两种方式进行删除,要么要求该Activity结束,要么直接杀掉它的进程。当该Activity再次显示给用户时,它必须重新开始和重置前面的状态。

注意:
onPause,onStop, onDestroy,三种状态下activity都有可能被系统干掉,为了保证程序的正确性,你要在onPause()里写上持久层操作的代码,将用户编辑的内容都保存到存储介质上(一般都是数据库)。
实际工作中因为生命周期的变化而带来的问题也很多,比如你的应用程序起了新的线程在跑,这时候中断了,你还要去维护那个线程,是暂停还是杀掉还是数据回滚,是吧?因为Activity可能被杀掉,所以线程中使用的变量和一些界面元素就千万要注意了,一般都是要采用Android的Handler机制来处理多线程和界面交互的问题。
Activity对象状态的保存:
一般在activity由前台转到后台执行可能会被系统kill掉(即非正常被杀死时,可以用横竖切换或DDMS中杀死模拟),
那么在此之前我们应该考虑系统状态信息的保存;
保存和恢复的时间、地点:
从前台进入后台时保存:
onPause() 在该方法保存也可以 外存保存 借助偏好设置或者Sqlite 属于永久保存
onSaveInstanceState(Bundle outState) 内存保存 借助Bundle对象(map中) 仅保存在缓存区
onStop()

从后台回到前台时恢复;
onCreate(Bundle state) 在该方法也可以恢复
onStart()
onRestoreInstanceState() 恢复
onResume()

Activity被销毁了但是进程(空进程 还有个空的任务栈 这样的情况多了手机就慢了)没有被杀死
可以定制android系统:有这种空进程就直接干掉 像IOS一样

在onCreate方法中有saveInstanceState这个参数,其实这个参数对应两个方法。
void onSaveInstanceState(Bundle outState);
void onRestoreInstanceState(Bundle savedInstanceState)。
当某个activity变得“容易”被系统销毁时,说白了就是系统在内存不足或者其他异常情况下把你的Activity销毁时,将调用这个方法。
需要注意的是它是系统调用的,并且你的Activity是被动得被销毁。
你可以在销毁的时候保存一下数据,然后在onCreate方法中拿出来。那什么情况下能触发这两个方法呢?
当用户按下HOME键时,这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,
自然也不知道activity A是否会被销毁,故系统会调用onSaveInstanceState,让用户有机会保存某些非永久性的数据。

以下几种情况的分析都遵循该原则
2)、长按HOME键,选择运行其他的程序时。
3)、按下电源按键(关闭屏幕显示)时。
4)、从activity A中启动一个新的activity时。
5)、屏幕方向切换时,例如从竖屏切换到横屏时。

横竖屏切换:
在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,
所以onSaveInstanceState一定会被执行;比如由竖屏调整为横屏。生命周期的方法依次用:
onPause->onStop->onDestroy->onCreate->onStart->onResume。
这么一个过程。它会销毁掉原先的activity,重新创建。
1、不设置Activity的android:configChanges时,切屏会重新调用各个生命周期,
切横屏时会执行一次,切竖屏时会执行两次;
2、设置Activity的android:configChanges=”orientation”时,
切屏还是会重新调用各个生命周期,切横、竖屏时只会执行一次;
3、设置Activity的android:configChanges=”orientation|keyboardHidden”时,
切屏不会重新调用各个生命周期,只会执行onConfigurationChanged方法

总而言之,onSaveInstanceState的调用遵循一个重要原则,即当系统“未经你许可”时销毁了你的activity,
则onSaveInstanceState会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。

至于onRestoreInstanceState方法,需要注意的是和onRestoreInstanceState方法“不一定”是成对的被调用的,
onRestoreInstanceState被调用的前提是activity A“确实”被系统销毁了,而如果仅仅是停留在有这种可能性的情况下,则该方法不会被调用,例如,当正在显示activity A的时候,用户按下HOME键回到主界面,然后用户紧接着又返回到activity A,这种情况下activity A一般不会因为内存的原因被系统销毁,故activity A的onRestoreInstanceState方法不会被执行;
另外,onRestoreInstanceState的bundle参数也会传递到onCreate方法中,你也可以选择在onCreate方法中做数据还原。

返回键与Home键的区别:
back键默认行为是finish处于前台的Activity,即Activity的状态为Destroy状态为止,
再次启动该Activity是从onCreate开始的(不会调用onSaveInstanceState方法);

Home键默认是stop前台的Activity,即状态为onStop为止而不是Destroy,
若再次启动它,会调用onSaveInstanceState方法,
保持上次Activity的状态则是从OnRestart开始的—->onStart()—>onResume()。

你后台的Activity被系统回收怎么办:onSaveInstanceState
当你的程序中某一个Activity A 在运行中时,主动或被动地运行另一个新的Activity B;
这个时候A会执行onSaveInstanceState()方法(pause之后、stop之前调用): Java代码
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putLong(“id”, 1234567890);
}
//如果后台的Activity由于某原因被系统回收了,如何在被系统回收之前保存当前状态?
被回收前调用onSaveInstanceState()方法保存当前状态;

B 完成以后又回来找A, 这个时候就有两种情况,一种是A被回收,一种是没有被回收;
系统会帮我们记录下回收前Activity的状态,再次调用被回收的Activity A
就要重新调用onCreate()方法,不同于直接启动的是这回onCreate()里是带上参数 savedInstanceState的;
没被回收的话就还是onResume就好了,跳过onCreate()。

savedInstanceState是一个Bundle对象,你基本上可以把他理解为系统帮你维护的一个Map对象,
我们使用saveInstanceState可以恢复到回收前的状态。在onCreate()里你可能会用到它,
如果正常启动onCreate就不会有它,所以用的时候要判断一下是否为空。 Java代码:  
if(savedInstanceState != null){
long id = savedInstanceState.getLong(“id”);
}
就像官方的Notepad教程里的情况,你正在编辑某一个note,突然被中断,那么就把这个note的id记住,
再起来的时候就可以根据这个id去把那个note取出来,程序就完整一些。
这也是看你的应用需不需要保存什么,比如你的界面就是读取一个列表,那就不需要特殊记住什么;但没准你需要记住滚动条的位置…

如何让Activity变成一个窗口主题及其他主题:
设置Activity的主题可以在AndroidManifest.xml中定义Activity属性设定:
android:theme=”@android:style/Theme.Dialog”//对话框形式
android:theme=”@android:style/Theme.Translucent”//变成半透明的
类似的这种activity的属性可以在android.R.styleable 类的AndroidManifestActivity 方法中看到,
AndroidManifest.xml中所有元素的属性的介绍都可以参考这个类android.R.styleable上面说的是属性名称,
具体有什么值是在android.R.style中可以看到,比如这个”@android:style/Theme.Dialog” 就对应于android.R.style.Theme.Dialog.

这里写图片描述
如何退出Activity:
这里写图片描述

二个Activity之间怎样传递数据:
这里写图片描述
可以启动Activity的方法:
startActivity和startActivityForResult
这里写图片描述
Activity中的任务栈(TaskStack):
一个Android系统中可以有很多APP,每个APP都可以有一个或多个任务栈,这些任务栈中用于存储Activity对象,
正在前台运行的Activity位于栈顶位置。栈底位置为最先启动的Activity.
说明:这个任务栈有时又称之为为回退栈(Back Stack)或历史栈(history stack)
例如:对于三个Activity(A,B,C),启动顺序为:A–>B–>C,此时三个Activity对象
是否运行于同一栈中,取决于他们的启动模式,假如处于同一栈中则C为栈顶,A为栈底。

这里写图片描述
Activty启动提供了四种启动模式launchMode:
每个Activity在启动时都有一个启动模式,此启动模式决定了:
是重新创建新的对象还是使用栈中已有对象)以及Activity对象的一个存储位置

默认的标准模式standard每次启动都会重新创建activity对象
在一个任务栈中可以包含同一个Activity类的多个实例
同一个Activity类的多个实例可以存在于不同的任务栈中
创建时默认总是在当前任务中启动
除非是从桌面程序或launch程序中启动的第一个Activity

singleTop栈顶重用模式
栈顶时再启动不会创建对象 而是将启动意图直接传入栈顶实例

singleTask栈内重用模式
栈中只能有一个此Activit类的实例
没有在栈顶的它再次被启动时会销毁它上面的所有activity实例
activity对象比较大,创建比较耗时使用此启动模式。例如浏览器)是全局单实例的
当前系统中不存在该Activity的实例,则创建新实例:
如果它的taskAffinity与当前任务TaskX的taskAffinity相同 则在当前任务TaskX中创建新实例
如果不同则在新任务TaskX+1中创建该Activity的新实例
如果当前系统中已存在该Activity的实例,则不会创建新的实例,
而是将启动意图传入已存在实例,没有在栈顶的它再次被启动时会销毁它上面的所有activity实例
即将已存在的实例重新设置为栈顶实例
如果再通过该Activity启动singleTask或者singleInstance模式的Activity时不管亲族值是否相同
都会存储在该新创建的任务栈中

singleInstance:任务栈独享
整个内存中此Activity的实例只有一份,在这个任务栈中只能有此activity一个对象
创建新实例时一定是在新的任务栈中创建
被其它很多APP共享访问的activity可以设置为此启动模式(例如拨号程序、相机程序)

singleTask时由此所开启的活动也在同一任务中,即taskId相同。
而singleInstance时由此开启的活动在新的任务中.即栈中只有一个活动,taskid不同
如果通过该Activity的实例启动其他Activity的新实例时
首先会判断新实例的taskAffinity值是否与已存在的任务的taskAffinity值相同
如果相同:在该任务中创建新实例
如果不同:在新的任务中创建新实例且新的任务栈的亲族值为这个被启动的activity的亲族值

Activity的任务吸附值或任务亲族值(taskAffinity)
1、每个Application都有一个亲族值,如果未明确设置则与应用程序主包名一致
2、每个Activity都有一个亲族值,如果未明确设置,则所在的|Application一致
3、每个任务栈都有一个亲族值,该值与其任务栈栈底的Acitivity的亲族值相同
android 中亲族值需要借助application或activity中的android:taskAffinity属性进行配置,
此属性的应用要结合activity的singleTask和singleInstance二种启动模式,其他模式一般会采用默认亲族值。
activity的启动模式和亲族值关系的设置决定了被启动的activity是否存储在新建的任务栈中。

任务和返回栈
一个应用程序当中通常都会包含很多个Activity,每个Activity都应该设计成为一个具有特定的功能,并且可以让用户进行操作的组件。另外,Activity之间还应该是可以相互启动的。比如,一个邮件应用中可能会包含一个用于展示邮件列表的Activity,而当用户点击了其中某一封邮件的时候,就会打开另外一个Activity来显示该封邮件的具体内容。
除此之外,一个Activity甚至还可以去启动其它应用程序当中的Activity。打个比方,如果你的应用希望去发送一封邮件,你就可以定义一个具有”send”动作的Intent,并且传入一些数据,如对方邮箱地址、邮件内容等。这样,如果另外一个应用程序中的某个Activity声明自己是可以响应这种Intent的,那么这个Activity就会被打开。在当前场景下,这个Intent是为了要发送邮件的,所以说邮件应用程序当中的编写邮件Activity就应该被打开。当邮件发送出去之后,仍然还是会回到你的应用程序当中,这让用户看起来好像刚才那个编写邮件的Activity就是你的应用程序当中的一部分。所以说,即使有很多个Activity分别都是来自于不同应用程序的,
Android系统仍然可以将它们无缝地结合到一起,之所以能实现这一点,
就是因为这些Activity都是存在于一个相同的任务 (Task)当中的。

任务是一个Activity实例的集合,它使用栈的方式来管理其中的Activity,
这个栈又被称为返回栈(back stack),栈中Activity的顺序就是按照它们被打开的顺序依次存放的。
手机的Home界面是大多数任务开始的地方,当用户在Home界面上点击了一个应用的图标时,这个应用的任务就会被转移到前台。如果这个应用目前并没有任何一个任务的话(说明这个应用最近没有被启动过),系统就会去创建一个新的任务,并且将该应用的主Activity放入到返回栈当中。
当一个 Activity启动了另外一个Activity的时候,新的Activity就会被放置到返回栈的栈顶并将获得焦点。前一个Activity仍然保留在返回栈当中,但会处于停止状态。当用户按下Back键的时候,栈中最顶端的Activity会被移除掉,然后前一个Activity则会得重新回到最顶端的位置。返回栈中的Activity的顺序永远都不会发生改变,我们只能向栈顶添加Activity,或者将栈顶的Activity移除掉。因此,返回栈是一个典型的后进先出(last in, first out)的数据结构。下图通过时间线的方式非常清晰地向我们展示了多个Activity在返回栈当中的状态变化:
这里写图片描述
如果用户一直地按Back键,这样返回栈中的Activity会一个个地被移除,直到最终返回到主屏幕。
当返回栈中所有的Activity都被移除掉的时候,对应的任务也就不存在了。
任务除了可以被转移到前台之外,当然也是可以被转移到后台的。当用户开启了一个新的任务,或者点击Home键回到主屏幕的时候,之前任务就会被转移到后台了。当任务处于后台状态的时候,返回栈中所有的Activity都会进入停止状态,但这些Activity在栈中的顺序都会原封不动地保留着,如下图所 示:
这里写图片描述
这个时候,用户还可以将任意后台的任务切换到前台,这样用户应该就会看到之前离开这个任务时处于最顶端的那个Activity。举个例子来说,当前任务A的栈中有三个Activity,现在用户按下Home键,然后点击桌面上的图标启动了另外一个应用程序。当系统回到桌面的时候,其实任务A就已经进入后台了,然后当另外一个应用程序启动的时候,系统会为这个程序开启一个新的任务(任务B)。当用户使用完这个程序之后,再次按下Home键回到桌面,这个时候任务B也进入了后台。然后用户又重新打开了第一次使用的程序,这个时候任务A又会回到前台,A任务栈中的三个Activity仍然会保留着刚才的顺序,最顶端的Activity将重新变为运行状态。之后用户仍然可以通过Home键或者多任务键来切换回任务B,或者启动更多的任务,这就是Android中多任务切换的例子。
由于返回栈中的Activity的顺序永远都不会发生改变,所以如果你的应用程序中允许有多个入口都可以启动同一个Activity,那么每次启动的时候就都会创建该Activity的一个新的实例,而不是将下面的 Activity的移动到栈顶。这样的话就容易导致一个问题的产生,即同一个Activity有可能会被实例化很多次,如下图所示:
这里写图片描述
但是呢,如果你不希望同一个Activity可以被多次实例化,那当然也是可以的,马上我们就将开始讨论如果实现这一功能,现在我们先把默认的任务和Activity的行为简单概括一下:
当Activity A启动Activity B时,Activity A进入停止状态,但系统仍然会将它的所有相关信息保留,比如滚动的位置,还有文本框输入的内容等。如果用户在Activity B中按下Back键,那么Activity A将会重新回到运行状态。
当用户通过Home键离开一个任务时,该任务会进入后台,并且返回栈中所有的Activity都会进入停止状态。系统会将这些Activity的状态进行保留,这样当用户下一次重新打开这个应用程序时,就可以将后台任务直接提取到前台,并将之前最顶端的Activity进行恢复。
当用户按下Back键时,当前最顶端的Activity会被从返回栈中移除掉,移除掉的Activity将被销毁,然后前面一个Activity将处于栈顶位置并进入活动状态。当一个Activity被销毁了之后,系统不会再为它保留任何的状态信息。
每个Activity都可以被实例化很多次,即使是在不同的任务当中。
管理任务

Android 系统管理任务和返回栈的方式,正如上面所描述的一样,就是把所有启动的Activity都放入到一个相同的任务当中,通过一个“后进先出”的栈来进行管理的。这种方式在绝大多数情况下都是没问题的,开发者也无须去关心任务中的Activity到底是怎么样存放在返回栈当中的。但是呢,如果你想打破这种默认的行为,比如说当启动一个新的Activity时,你希望它可以存在于一个独立的任务当中,而不是现有的任务当中。或者说,当启动一个Activity 时,如果这个Activity已经存在于返回栈中了,你希望能把这个Activity直接移动到栈顶,而不是再创建一个它的实例。再或者,你希望可以将返回栈中除了最底层的那个Activity之外的其它所有Activity全部清除掉。这些功能甚至更多功能,都是可以通过在manifest文件中设置元素的属性,或者是在启动Activity时配置Intent的flag来实现的。
在元素中,有以下几个属性是可以使用的:
taskAffinity
launchMode
allowTaskReparenting
clearTaskOnLaunch
alwaysRetainTaskState
finishOnTaskLaunch
而在Intent当中,有以下几个flag是比较常用的:
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TOP
下面我们就将开始讨论,如何通过manifest参数,以及Intent flag来改变Activity在任务中的默认行为。
定义启动模式

启动模式允许你去定义如何将一个Activity类的实例和当前的任务进行关联,
你可以通过以下两种不同的方式来定义启动模式:
1.使用manifest文件
当你在manifest文件中声明一个Activity的时候,你可以指定这个Activity在启动的时候该如何与任务进行关联。
2.使用Intent flag
当你调用startActivity()方法时可以在Intent中加入一个flag,从而指定新启动的Activity该如何与当前任务进行关联。
也就是说,如果Activity A启动了Activity B,Activity B可以定义自己该如何与当前任务进行关联,
而Activity A也可以要求Activity B该如何与当前任务进行关联。
如果Activity B在manifest中已经定义了该如何与任务进行关联,
而Activity A同时也在Intent中要求了Activity B该怎么样与当前任务进行关联,
那么此时Intent中的定义将覆盖manifest中的定义。
需要注意的是,有些启动模式在manifest中可以指定,但在Intent中是指定不了的。
同样,也有些启动模式在Intent中可以指定,但在manifest中是指定不了的,下面我们就来具体讨论一下。
使用manifest文件

当在manifest文件中定义Activity的时候,你可以通过元素的
launchMode属性来指定这个Activity应该如何与任务进行关联。launchMode属性一共有以下四种可选参数:
“standard”(默认启动模式) 默认模式 每次在当前任务创建实例
standard 是默认的启动模式,即如果不指定launchMode属性,则自动就会使用这种启动模式。这种启动模式表示每次启动该Activity时系统都会为创建一 个新的实例,并且总会把它放入到当前的任务当中。声明成这种启动模式的Activity可以被实例化多次,一个任务当中也可以包含多个这种 Activity的实例。
“singleTop” 栈顶重用模式 要启动的实例在栈顶时不再创建新实例 否则再次创建新实例
这种启动模式表示,如果要启动的这个Activity在当前任务中已经存在了,并且还处于栈顶的位置,
那么系统就不会再去创建一个该Activity的实例,而是调用栈顶Activity的onNewIntent()方法。
声明成这种启动模式的Activity也可以被实例化多次,一个任务当中也可以包含多个这种Activity的实例。
举个例子来讲,一个任务的返回栈中有A、B、C、D四个Activity,其中A在最底端,D在最顶端。这个时候如果我们要求再启动一次D,并且D的启动模 式是”standard”,那么系统就会再创建一个D的实例放入到返回栈中,此时栈内元素为:A-B-C-D-D。而如果D的启动模式 是”singleTop”的话,由于D已经是在栈顶了,那么系统就不会再创建一个D的实例,而是直接调用D Activity的onNewIntent()方法,此时栈内元素仍然为:A-B-C-D。
“singleTask” 当前任务栈有要启动的该程序的Activity实例时不再创建保持实例在站内唯一没有就在当前栈新建
启动其他程序的Activity实例时会创建新的任务栈存放该实例
这种启动模式表示,系统会创建一个新的任务,并将启动的Activity放入这个新任务的栈底位置。但是,如果现有任务当中已经存在一个该Activity 的实例了,那么系统就不会再创建一次它的实例,而是会直接调用它的onNewIntent()方法。声明成这种启动模式的Activity,在同一个任务 当中只会存在一个实例。注意这里我们所说的启动Activity,都指的是启动其它应用程序中的Activity,因为”singleTask”模式在默认情况下只有启动其它程序的Activity才会创建一个新的任务,启动自己程序中的Activity还是会使用相同的任务,具体原因会在下面 处理affinity 部分进行解释。
“singleInstance” 实例独占一个任务栈
这种启动模式和”singleTask”有点相似,只不过系统不会向声明成”singleInstance”的Activity所在的任务当中再添加其它Activity。也就是说,这种Activity所在的任务中始终只会有一个Activity,通过这个Activity再打开的其它Activity 也会被放入到别的任务当中。
再举一个例子,Android系统内置的浏览器程序声明自己浏览网页的Activity始终应该在一个独立的任务当中打开,也就是通过在元素中设置”singleTask”启动模式来实现的。这意味着,当你的程序准备去打开 Android内置浏览器的时候,新打开的Activity并不会放入到你当前的任务中,而是会启动一个新的任务。而如果浏览器程序在后台已经存在一个任务了,则会把这个任务切换到前台。

其实不管是Activity在一个新任务当中启动,还是在当前任务中启动,返回键永远都会把我们带回到之前的一个Activity中的。
但是有一种情况是比较特殊的,就是如果Activity指定了启动模式是”singleTask”,
并且启动的是另外一个应用程序中的Activity,
这个时候当发现该Activity正好处于一个后台任务当中的话,
就会直接将这整个后台任务一起切换到前台。
此时按下返回键会优先将目前最前台的任务(刚刚从后台切换到最前台)进行回退,下图比较形象地展示了这种情况:
这里写图片描述
使用Intent flags

除了使用manifest文件之外,你也可以在调用startActivity()方法的时候,
为Intent加入一个flag来改变Activity与任务的关联方式,下面我们来一一讲解一下每种flag的作用:
FLAG_ACTIVITY_NEW_TASK
设置了这个flag,新启动Activity就会被放置到一个新的任务当中(与”singleTask”有点类似,但不完全一样),
当然这里讨论的仍然还是启动其它程序中的Activity。这个flag的作用通常是模拟一种Launcher的行为,
即列出一推可以启动的东西,但启动的每一个Activity都是在运行在自己独立的任务当中的。
FLAG_ACTIVITY_SINGLE_TOP
设置了这个flag,如果要启动的Activity在当前任务中已经存在了,并且还处于栈顶的位置,那么就不会再次创建这个Activity的实例,而是直接调用它的onNewIntent()方法。这种flag和在launchMode中指定”singleTop”模式所实现的效果是一样的。

FLAG_ACTIVITY_CLEAR_TOP
设置了这个flag,如果要启动的Activity在当前任务中已经存在了,就不会再次创建这个Activity的实例,
而是会把这个Activity之上的所有Activity全部关闭掉。比如说,一个任务当中有A、B、C、D四个Activity,
然后D调用了startActivity()方法来启动 B,并将flag指定成FLAG_ACTIVITY_CLEAR_TOP,
那么此时C和D就会被关闭掉,现在返回栈中就只剩下A和B了。

那么此时Activity B会接收到这个启动它的Intent,
你可以决定是让Activity B调用onNewIntent()方法(不会创建新的实例),
还是将Activity B销毁掉并重新创建实例。

如果Activity B没有在manifest中指定任何启动模式(也就是”standard”模式),
并且Intent中也没有加入一个 FLAG_ACTIVITY_SINGLE_TOP flag,
那么此时Activity B就会销毁掉,然后重新创建实例。

而如果Activity B在manifest中指定了任何一种启动模式,
或者是在Intent中加入了一个FLAG_ACTIVITY_SINGLE_TOP flag,那么就会调用Activity B的onNewIntent()方法。
(不会创建新的实例)

FLAG_ACTIVITY_CLEAR_TOP和 FLAG_ACTIVITY_NEW_TASK结合在一起使用也会有比较好的效果,
比如可以将一个后台运行的任务切换到前台,
并把目标Activity之上的其它Activity全部关闭掉。
这个功能在某些情况下非常有用,比如说从通知栏启动Activity的时候。
处理affinity

affinity 可以用于指定一个Activity更加愿意依附于哪一个任务,在默认情况下,
同一个应用程序中的所有Activity都具有相同的affinity,
所以,这些Activity都更加倾向于运行在相同的任务当中。
当然了,你也可以去改变每个Activity的affinity值,
通过元素的taskAffinity属性就可以实现了。
taskAffinity属性接收一个字符串参数,
你可以指定成任意的值(经我测试字符串中至少要包含一个.),
但必须不能和应用程序的包名相同,因为系统会使用包名来作为默认的affinity值。

affinity主要有以下两种应用场景:
当调用startActivity()方法来启动一个Activity时,默认是将它放入到当前的任务当中。

但是,如果在Intent中加入了一个 FLAG_ACTIVITY_NEW_TASK flag的话
(或者该Activity在manifest文件中声明的启动模式是”singleTask”),
系统就会尝试为这个Activity单独创建一个任务。
但是规则并不是只有这么简单,系统会去检测要启动的这个Activity的affinity和当前任务的affinity是否相同,
如果相同的话就会把它放入到现有任务当中,如果不同则会去创建一个新的任务。
而同一个程序中所有Activity的affinity默认都是相同的,这也是前面为什么说,
同一个应用程序中即使声明成”singleTask”,也不会为这个Activity再去创建一个新的任务了。

当把 Activity的allowTaskReparenting属性设置成true时,Activity就拥有了一个转移所在任务的能力。
具体点来说,就是一个Activity现在是处于某个任务当中的,
但是它与另外一个任务具有相同的affinity值,那么当另外这个任务切换到前台的时候,
该 Activity就可以转移到现在的这个任务当中。
那还是举一个形象点的例子吧,比如有一个天气预报程序,它有一个Activity是专门用于显示天气信息的,
这个Activity和该天气预报程序的所有其它Activity具体相同的affinity值,
并且还将 allowTaskReparenting属性设置成true了。
这个时候,你自己的应用程序通过Intent去启动了这个用于显示天气信息的 Activity,
那么此时这个Activity应该是和你的应用程序是在同一个任务当中的。
但是当把天气预报程序切换到前台的时候,
这个Activity又会被转移到天气预报程序的任务当中,并显示出来,
因为它们拥有相同的affinity值,并且将 allowTaskReparenting属性设置成了true。
清空返回栈

如何用户将任务切换到后台之后过了很长一段时间,
系统会将这个任务中除了最底层的那个Activity之外的其它所有Activity全部清除掉。
当用户重新回到这个任务的时候,最底层的那个Activity将得到恢复。
这个是系统默认的行为,因为既然过了这么长的一段时间,用户很有可能早就忘记了当时正在做什么,
那么重新回到这个任务的时候,基本上应该是要去做点新的事情了。
当然,既然说是默认的行为,那就说明我们肯定是有办法来改变的,
在元素中设置以下几种属性就可以改变系统这一默认行为:
alwaysRetainTaskState
如果将最底层的那个Activity的这个属性设置为true,那么上面所描述的默认行为就将不会发生,
任务中所有的Activity即使过了很长一段时间之后仍然会被继续保留。
clearTaskOnLaunch
如果将最底层的那个Activity的这个属性设置为true,那么只要用户离开了当前任务,
再次返回的时候就会将最底层Activity之上的所有其它 Activity全部清除掉。
简单来讲,就是一种和alwaysRetainTaskState完全相反的工作模式,
它保证每次返回任务的时候都会是一种初始化状态,即使用户仅仅离开了很短的一段时间。
finishOnTaskLaunch
这个属性和clearTaskOnLaunch是比较类似的,不过它不是作用于整个任务上的,
而是作用于单个Activity上。如果某个Activity将这个属性设置成true,
那么用户一旦离开了当前任务,再次返回时这个Activity就会被清除掉。

standard启动模式:无条件新生
singletop启动模式:栈顶重用
singletask启动模式:存在则销毁置顶
singleinstance启动模式:新栈独占
http://blog.csdn.net/liuhe688/article/details/6754323

android 状态栏、标题栏、屏幕高度
http://xqjay19910131-yahoo-cn.iteye.com/blog/1435249

onWindowFocusChanged触发简介
http://blog.csdn.net/yueqinglkong/article/details/44981449

0 0
原创粉丝点击