如今的国产机好在哪里能买山寨机

3004人阅读
Android(57)
翻译自官方文档:Receating an Activity
更透彻的关于onSaveInstanceState()函数保存数据,onRestoreInstanceState()函数和onCreate()函数恢复数据的整个流程及源代码理解请参考博文:
Activity在以下几种情况下由于正常的应用操作会被销毁,例如:当用户按下后退键或者是Activity本身调用了finish()方法。此外,如果你的Activity生命周期处于stop状态而且一段时间内都没有被调用或者是处于前台的Activity需要更多的资源而系统资源紧张的时候,系统也会销毁你的Activity,杀掉后台进程给前台进程留出足够的内存。
当你的Activity由于用户按下后退键或者是Activity本身调用了finish()方法时,系统认为该Activity将会永远消失,因为前面两种情况表明该Activity不再被需要。然而,如果系统由于本身的限制不得不销毁Activity而不是应用本身的行为销毁Activity,尽管该Activity已经消失,但是系统仍然记得它。只要用户重新进入该Activity,系统就会利用系统自己销毁该Activity时保存的一些描述该Activity状态的数据来重新创建一个新的Activity。这些系统用于恢复之前Activity状态而保存的数据叫做“实例状态”,它本质上是一些键值对,并保存在Bundle对象中。
警告:当用户进行横竖屏切换的时候,你的应用的当前Activity机会被销毁和重建。当屏幕更改方向时,由于屏幕的改变,当前Activity需要重新加载交互资源,必须布局资源,此时系统就会销毁和重建前台Acitvity。
默认情况下,系统使用Bundle实例状态来保存你的Activity中的View对象的信息,比如 EditText 可编辑框中输入的数据。所以,如果你的Activity被销毁和重建,你的布局资源的状态会保存当前被销毁的Activity的状态,而这些并不用你自己编写代码实现。然而,你自己本身可能会有想要保存的数据,比如当前要被销毁的Activity中的一些变量的值。
注解:为了android熊够恢复你的Activity的一些View的状态,每一个要保存的View必须包含唯一的ID,这个ID 就是我们在xml文件中定义是所写的 android:id &这个属性。
为了保存一些这个被销毁的Acitvity的额外数据,你必须复写 onSaveInstanceState() 回调函数。当用户将要离开这个Activity的时候,系统就会调用这个回调函数,并且传递 给它一个Bundle对象作为参数,当你的activity被突然销毁时,这个Bundle对象就会保存该Activity的一些状态值,其他情况下该Bundle的值均为null。如果系统之后必须恢复activity实例,它就会传递刚刚保存被销毁的Activity的实例状态这个Bundle对象给onRestoreInstanceState()方法和onCreate()方法。
如上图:当系统要停止你的Activity的时候,系统会调用onSaveInstanceState()方法,所以你可以添加你想保存的额外状态数据以防止Activity被销毁重建。如果activity被销毁而且要重建一个相同的activity实例,系统就会传递之前保存的状态数据(保存在Bundle中)给onCreate()方法和onRestoreInstanceState()方法。
保存Activity状态
当你的Activity要开始进入stop生命周期的时候,系统会调用onSaveInstanceState()方法,所以你的activity能够保存一些状态信息到键值对中。该函数的默认实现保存的信息是该Activity的层次视图(view hierarchy),比如EditText控件中的内容,或者是ListView中的滚动的位置。
为了在Activity中保存额外的状态信息,你必须实现onSaveInstanceState()方法,并添加键值对到Bundle对象中,如下:
static final String STATE_SCORE = &playerScore&;
static final String STATE_LEVEL = &playerLevel&;
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current game state
savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
警告:复写该方法时要调用onSaveInstanceState()方法的父类实现,默认的实现方法才会保存该Activity的阶级视图。
恢复Activity数据
当你的Activity之前被销毁后要重建时,你可以从之前保存的Bundle对象中恢复数据。onCreate()和onRestoreInstanceState()两个回调函数都会接收到相同的,包含之前保存的状态信息的Bundle对象。
由于onCreate()方法不管是新建一个新的Activity实例或者是重建一个Activity实例都会被调用,所以在你要读取Bundle这个对象之前你必须检测一下Bundle对象是否为null,如果是null,系统就新建一个新的Activity实例,如果不是null,则重建一个Activity实例。
下面这个例子展示了如何在onCreate()方法中恢复数据:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // Always call the superclass first
// Check whether we're recreating a previously destroyed instance
if (savedInstanceState != null) {
// Restore value of members from saved state
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
// Probably initialize members with default values for a new instance
你应该选择复写实现onRestoreInstanceState()方法,这个方法是在onStart()方法之后被调用的,而不是选择实现onCreate()方法去恢复数据。系统只有当保存了状态数据要恢复的时候才会调用onRestoreInstanceState()方法,所以我们不需要检测它的参数Bundle对象是否为null。一个实例如下:
public void onRestoreInstanceState(Bundle savedInstanceState) {
// Always call the superclass so it can restore the view hierarchy
super.onRestoreInstanceState(savedInstanceState);
// Restore state members from saved instance
mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
警告:复写该方法时要调用该方法的父类实现,默认的实现方法才会保存该Activity的阶级视图。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:422402次
积分:5233
积分:5233
排名:第4482名
原创:71篇
转载:32篇
评论:234条
华南理工大学硕士在读,新型
人机交互实验室,关注机器学
习,深度学习,计算机视觉,
视频图像处理
新浪微博:
个人GitHub:
文章:10篇
阅读:61110
(1)(1)(2)(2)(2)(1)(4)(7)(11)(4)(13)(7)(34)(13)(4)作为Android入门级别的组件,Activity在Android的开发中承载了太多的东西,我们的项目开发中少不了与Activity打交道。所以需要我们熟练掌握Activity的使用。总体介绍围绕下面的几个议题进行讨论,以前也总结过Activity的特点,那时候刚学习Android总结的也过于死板,这次准备集中把Android知识进行梳理一下,所以比较有总结性。Activity的使用。 Activity的模式。 Activity的通讯. Activity的管理。 Activity常见问题:从一个应用打开另一个应用。 完全退出应用。 动画切换 从另一个页面回调数据。一、Activity基本使用在项目的开发中,Activity承载了我们的页面展示,它通过setContentView(int Id)方法绑定显示的布局,然后进行显示。Activity的基本使用。1、Activity的定义声明(1)、继承Activity,实现Activity的生命周期方法。
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
protected void onStart() {
super.onStart();
Log.d(TAG, &onStart&);
protected void onStop() {
super.onStop();
Log.d(TAG, &onStop&);
protected void onResume() {
super.onResume();
Log.d(TAG, &onResume&);
protected void onPause() {
super.onPause();
Log.d(TAG, &onPause&);
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, &onDestroy&);
}(2)、在Manifest.xml清单文件中注册Activity。四大组件在使用的时候都必须在清单文件中进行列举。
2、Activity的跳转我们知道Activity承载了我们应用几乎全部的页面显示工作,那么我们如何进行页面之前的跳转呢?这里就需要使用Intent类进行页面的跳转。显示跳转,针对我们在同一个应用中知道跳转页面的名字。Intent intent = new Intent(MainActivity.this, LoginActivity.class);startActivity(intent);隐式跳转,当我们不知道一个应用的类名,只知道action、category属性时,进行使用: Intent loginIntent = new Intent(); loginIntent.setAction(&com.intent.action.LOGIN&); loginIntent.addCategory(&com.intent.category.LOGIN&); startActivity(loginIntent);注意,这里的action和category要和声明的Activity在清单文件中注册的保持一致。隐式Intent通过Android解析,将Intent映射给可以处理此Intent的Activity、IntentReceiver或Service。Intent解析机制主要是通过查找已注册在AndroidManifest.xml中的所有IntentFilter及其中定义的Intent,最终找到匹配的Intent。在这个解析过程中,Android是通过Intent的action、type、category这三个属性来进行判断的,判断方法如下:如果Intent指明定了action,则目标组件的IntentFilter的action列表中就必须包含有这个action,否则不能匹配; 如果Intent没有提供type,系统将从data中得到数据类型。和action一样,目标组件的数据类型列表中必须包含Intent的数据类型,否则不能匹配。 如果Intent中的数据不是content: 类型的URI,而且Intent也没有明确指定它的type,将根据Intent中数据的scheme (比如 http: 或者mailto:) 进行匹配。同上,Intent 的scheme必须出现在目标组件的scheme列表中。 如果Intent指定了一个或多个category,这些类别必须全部出现在组建的类别列表中。比如Intent中包含了两个类别:LAUNCHER_CATEGORY 和 ALTERNATIVE_CATEGORY,解析得到的目标组件必须至少包含这两个类别。 每一个通过startActivity()方法发出的隐式Intent都至少有一个category,就是 &android.intent.category.DEFAULT&,所以只要是想接收一个隐式Intent的Activity都应括&android.intent.category.DEFAULT& category,不然将导致 Intent 匹配失败。我们知道Activity有许多startXXX方法,所以我们这次总结下:- startActivity():启动单个Activity- startActivities(Intent[] intent,Bundle options):直接跳到最后一个Intent对应的页面,当点击返回键的时候,会逐个返回。常见常见:我们从主页点击产品进到某个产品的详情页面,然后返回键返回到产品列表页面。就可以使用这个。- startActivityIfNeeded()当启动的Activity设置了singleTop、singleTask时并且request Code小于0时不启动。- startActivityForResult():启动一个你想数据回调的页面,当页面存在的时候,回调数据到onActivityResult方法中。3、Activity的生命周期onCreate():启动创建Activity,系统第一个执行的方法。 onStart():就在(just before)Activity成为可见之前调用 onRestart():Activity从后台重新回到前台时被调用,用户可见但不可交互 onResume():就在(just before)Activity开始与用户交互之前调用。(此时不能完全交互) onWindowFocusChanged():Activity窗口获得或失去焦点时被调用,在onResume之后或onPause之后 onPause():Activity被覆盖到下面或者锁屏时被调用 onStop():退出当前Activity或者跳转到新Activity时被调用 onDestroy():退出当前Activity时被调用,调用之后Activity就结束了 onSaveInstanceState(Bundle outState):Activity被系统杀死时被调用. 例如:屏幕方向改变时,Activity被销毁再重建;当前Activity处于后台,系统资源紧张将其杀死. 另外,当跳转到其他Activity或者按Home键回到主屏时该方法也会被调用,系统是为了保存当前View组件的状态.在onPause之前被调用. onRestoreInstanceState(Bundle savedInstanceState):Activity被系统杀死后再重建时被调用.例如:屏幕方向改变时,Activity被销毁再重建;当前Activity处于后台,系统资源紧张将其杀死,用户又启动该Activity.这两种情况下onRestoreInstanceState都会被调用,在onStart之后。在Activity的生命周期中,我们经常讨论的前台生命周期(onResume&&onPause),可视声明周期,这里我们很少谈论真正在哪个阶段我们是可以与界面交互的。我们都知道在onCreate()方法中对一个View进行getHeight()、getWidth()会获取空值,为什么呢?这是因为此时的View还没有进行宽高的测量,在Activity的生命周期中,真正使我们开始于Activity交互的周期在 onWindowFocusChanged()中,我们可以获取控件的高度和宽度。站在Window视图的角度来看,Activity的生命周期又可具体到:entry: onStart&-&onResume&-&onAttachedToWindow&&&&&onWindowVisibilityChanged&visibility=0&&&-&onWindowFocusChanged(true)&&-& exit: onPause&-&onStop&-&onWindowFocusChanged(false) &&&&&&&- (lockscreen) exit : onPause&&&onWindowFocusChanged(false)&&&&onWindowVisibilityChanged&visibility=8&&&&&onStop(to another activity)4、Activity的基本模式在Android开发中,Activity有四种模式,我们可以在Manifest.xml文件中注册时进行设置:
这里,我们需要补充一点知识,关于Activity的管理方式,这里是借助栈类型数据结构进行管理,所以这四种模式分别对应了Activity的四种管理方式:standard:标准默认模式,每次创建一个Activity都会创建一个对应的实例。 singleTop:可以有多个实例,如果Activity在栈顶的时候,启动相同的Activity,不会创建新的实例,而会调用其onNewIntent方法。 singleTask:只有一个实例。在同一个应用程序中启动他的时候,若Activity不存在,则会在当前task创建一个新的实例,若存在,则会把task中在其之上的其它Activity destory掉并调用它的onNewIntent方法。 singleInstance:只有一个实例,并且这个实例独立运行在一个task中,这个task只有这个实例,不允许有别的Activity存在。合理运用这四种模式,能够使应用的体验和资源使用率更好。比如:singleTask适合作为程序入口点,因为app的入口点就一个。singleInstance适合需要与程序分离开的页面,例如闹铃提醒,将闹铃提醒与闹铃设置分离。5、Activity的通讯数据传递在同一个进程中的Activity之间少不了交互,那么Activity之间怎么进行数据的交互呢?Intent承载传递数据 广播 第三方通讯组件这里,我们主要介绍下前两种的使用,后两种以后再介绍。(1)、Intent传递数据在Android中提供了Intent机制来协助应用间的交互与通讯,Intent负责对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android则根据此Intent的描述,负责找到对应的组件,将 Intent传递给调用的组件,并完成组件的调用。Intent有六大属性值需要注意:Action:用于指定我们要完成的动作,是一个字符串常量。当然我们在注册四大组件时也可以指定对应的action值。系统也有些自带的:ACTION_CALL、ACTION_MAIN。通过setAction()方法进行设置。 Data:Intent的Data属性是执行动作的URI和MIME类型,不同的Action有不同的Data数据指定。比如:ACTION_EDIT Action应该和要编辑的文档URI Data匹配,ACTION_VIEW应用应该和要显示的URI匹配。通过setData()方法进行设置。 Category:Intent中的Category属性是一个执行动作Action的附加信息。比如:CATEGORY_HOME则表示放回到Home界面,ALTERNATIVE_CATEGORY表示当前的Intent是一系列的可选动作中的一个。通过addCategory()进行设置。 Type:Intent的Type属性显式指定Intent的数据类型(MIME)。一般Intent的数据类型能够根据数据本身进行判定,但是通过设置这个属性,可以强制采用显式指定的类型而不再进行推导。通过setType()方法进行设置。 Compent:Intent的Compent属性指定Intent的的目标组件的类名称。通常 Android会根据Intent 中包含的其它属性的信息,比如action、data/type、category进行查找,最终找到一个与之匹配的目标组件。但是,如果 component这个属性有指定的话,将直接使用它指定的组件,而不再执行上述查找过程。指定了这个属性以后,Intent的其它所有属性都是可选的。(很重要,比如我们在一个应用启动另一个应用)。通过setComponent(ComponentName)方法进行设置。 Extra:Intent的Extra属性是添加一些组件的附加信息。比如,如果我们要通过一个Activity来发送一个Email,就可以通过Extra属性来添加subject和body。通过putExtra()方法进行设置。在上面的介绍中,我们可以发现Intent超级强大的用途,可用于启动组件,也可以用于传递数据,所以Intent的设计就是为了在组件之间进行&沟通&传递使用。在启动Activity的时候,我们可以使用putExtra方法进行设置数据。然后传递到跳转到的页面。二、Activity常见场景- 从一个应用打开另一个应用。- 完全退出应用。- 动画切换- 从另一个页面回调数据。1、从一个应用启动另一个应用这里,我们使用Intent的setComponent方法来设置启动的页面。通过建立ComponentName对象来实现。Intent loginIntent = new Intent();ComponentName componentName = new ComponentName(&com.dsw.pluginapp&,&com.dsw.pluginapp.MainActivity&);loginIntent.setComponent(componentName);startActivity(loginIntent);3、从跳转页面回调数据在Android的页面之间,经常有数据回调,这个时候我们就可以采用startActivityForResult方法启动Activity,然后使用onActivityResult进行回调。启动Activity Intent loginIntent = new Intent();ComponentName componentName = new ComponentName(getApplicationContext(),&com.iflytek.testandroid.LoginActivity&);loginIntent.setComponent(componentName);startActivityForResult(loginIntent, requstCode);回调的处理,重写onActivityResult方法。@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == this.requstCode){
if(resultCode == RESULT_OK){
tv.setText(data.getStringExtra(&name&));
}}回调数据同样通过Intent进行封装。
Intent intent
= new Intent();
intent.putExtra(&name&, &From LoginActivity&);
setResult(RESULT_OK, intent);
finish();这样就完成了数据的回调。2、Activity启动动画切换在Activity的跳转时,我们可以增加动画效果,一种是通过设置xml配置Activity的theme进行设置。一种是通过代码进行设置。(1)、通过XML设置style进行设置。首先新建一个style,设置如下属性。
然后创建theme。
最后在manifest中给activity设置theme属性即可。(2)、通过代码设置。通过代码设置使用overridePendingTransition(id,id)进行设置。通过调用overridePendingTransition() 可以实时修改Activity的切换动画。但需注意的是:该函数必须在调用startActivity()或者finish()后立即调用,且只有效一次。4、完全退出应用我们知道,一般我们从进入到一个应用,然后操作很多页面,我们都会遇到这样的场景,比如:点击进入某个页面,然后点击返回键,返回到上一个页面,上一个页面的数据还保存着,但是我们也可能通过多个页面的跳转然后又返回到主页面,此时点击返回键就退出应用。这是就利用到我们的Activity的启动模式,一般在Activity中我们针对首页的页面,通过设置android:launchMode=&singleTask&即可进行解决。就爱阅读网友整理上传,为您提供最全的知识大全,期待您的分享,转载请注明出处。
欢迎转载:
推荐:    Android开发艺术探索——第一章:Activity的生命周期和启动模式
作为这本书的第一章,主席还是把Activity搬上来了,也确实,和Activity打交道的次数基本上是最多的,而且他的内容和知识点也是很多的,非常值得我们优先把他掌握,Activity中文翻译过来就是”活动”的意思,但是主席觉得这样翻译有些生硬
怀着无比崇敬的心情翻开了这本书,路漫漫其修远兮,程序人生,为自己加油!
作为这本书的第一章,主席还是把Activity搬上来了,也确实,和Activity打交道的次数基本上是最多的,而且他的内容和知识点也是很多的,非常值得我们优先把他掌握,Activity中文翻译过来就是”活动”的意思,但是主席觉得这样翻译有些生硬,直接翻译成“界面”可能更好,的确,Activity主要也是用于UI效果的呈现,不过两者翻译都不为过,我们知其意就行了,正常情况下,我们除了Window,Dialog,Toast,我们还能见到的就只有Activity了,他需要setContentView()去绑定一个视图View作为效果的呈现,然而,作为一本高质量的进阶书。肯定不会去围绕着入门知识讲解,本章的侧重点在于对Activity使用过程中搞不清楚的概念,生命周期和启动模式已经IntentFilter的匹配规则分析,毕竟Activity在异常状态下的生命周期是多样化的,至于Activity的启动模式和各种各样的Flags,更是让很多人摸不着头脑,还有隐式启动Activity中也有着复杂的Intent匹配过程,所以我们还是一步步的去学习下去,真正的了解Activity这个小家伙!
二.Activity的生命周期全面分析
Activity的生命周期,本章主要讲解两个方面
典型情况下的生命周期 异常情况下的生命周期
典型情况是指用户参与的情况下,Activity所经过的生命周期的变化,异常情况下的话,就有多种可能了,比如回收或者由于当前设备的Configuration发生改变从而导致Activity被销毁重建,异常情况下的生命周期的关注点和典型情况下有些不同,所以要分开来描述才能描述的清楚些
1.典型情况下的生命周期分析
在正常的情况下,生命周期会经历以下的生命周期
onCreate:表示Activity正在被创建,这是生命周期的第一个方法,在这个方法中,我们可以做一些初始化的工作,比如调用onContentView去加载界面布局资源,初始化Activity所需数据等
onRestart:表示Activity正在重新启动,一般情况下,当当前Activity从不可见重新变为可见时,onRestart就会被调用,这总情况一般是用户行为所导致的,比如用户按home键切换到桌面或者用户打开了一个新的Activity,这时当前的Activity就会被暂停,也就是onPause和onStop方法被执行了,接着用户又回到了这个Activity,就会出现这种情况
onStart:表示Activity正在被启动,即将开始,这个时候Activity已经可见了,但是还没有出现在前台,还无法和用户交互,这个时候我们可以理解为Activity已经启动了,但是我们还没有看见
onResume:表示Activity已经可见了,并且出现在前台,并开始活动了,要注意这个和onStart的对比,这两个都表示Activity已经可见了,但是onStart的时候Activity还处于后台,onResume的时候Activity才显示到前台
onPause:表示Activity正在停止,正常情况下,紧接着onStop就会被调用,在特殊情况下,如果这个时候再快速的回到当前Activity,那么onResume就会被调用,主席的理解是这个情况比较极端,用户操作很难重现这个场景,此时可以做一些数据存储,停止动画等工作,但是注意不要太耗时了,因为这样会影响到新的Activity的显示,onPause必须先执行完,新Activity的onResume才会执行
onStop:表示Activity即将停止,同样可以做一些轻量级的资源回收,但是不要太耗时了
onDestroy:表示Activity即将被销毁,这是Activity生命周期的最后一个回调,在这里我们可以做一些最后的回收工作和资源释放
正常情况下,Activity的常用生命周期用官网的一张图就足够表示了
这里附加几个说明
1.针对一个特定的Activity,第一次启动,回调如下:onCreate ——& onStart ——& onResume
2.当用户打开新的Activity或者切换到桌面的时候,回调如下:onPause ——& onStop ——& 这里有一种特殊的情况就是,如果新的Activity采取了透明的主题的话,那么当前Activity不会回调onStop
3.当用户再次回到原来的Activity,回调如下:onRestart ——& onStart ——& onResume
4.d当用户按back键的时候回调如下:onPause ———& onStZ喎"/kf/ware/vc/" target="_blank" class="keylink">vcCAmbWRhc2g7Jm1kYXNoOyZndDsgb25EZXN0cm95PC9wPg0KPHA+NS61sUFjdGl2aXR5sbvPtc2zu9jK1bXEyrG68tTZtM608r+qo6zJ+sP81tzG2rvYtfe3vbeous0xysfSu9H5tcSjrLWrysfE49Kq16LS4tK7z8K+zcrH1rvKx8n6w/zW3Mba0rvR+aOssru0+rHty/nT0LXEvfizzLa8ysfSu9H5tcSjrNXiuPbOyszitcjPwrvYz+rPuLfWzvY8L3A+DQo8cD42LrTT1fu49sn6w/zW3MbawLTLtaOsb25DcmVhdGW6zW9uRGVzdHJvecrHxeTM17XELLfWsfCx6sq+18VBY3Rpdml0ebXEtLS9qLrNz/q72aOssqLH0ta7v8nE3NPQ0ru0zrX308OjrLTTQWN0aXZpdHnKx7fxv8m8+8C0y7WjrG9uU3RhcnS6zW9uU3RvcMrHxeTM17XEo6zL5tfF08O7p7XEstnX97rNyeixuMbBxLu1xLXjwcG6zc+ow/CjrNXiwb249re9t6i/ycTcsbu199PDtuC0zqOstNNBY3Rpdml0ecrHt/HU2sewzKjAtMu1o6xvblJlc3VtZbrNb25QYXVzZcrHxeTM17XEo6zL5tfF08O7p7LZ1/e78tXfyeixuLXEtePBwbrNz6jD8KOs1eLBvbj2t723qL/JxNyxu7bgtM6199PDPC9wPg0KPHA+1eLA78zhs/bBvbj2zsrM4jwvcD4NCjxwPjEub25TdGFydLrNb25SZXN1bWWjrG9uUGF1c2W6zW9uU3RvcLTTw+jK9snPtryy7rK7tuCjrLbUztLDx8C0y7XT0Mqyw7TKtdbK0NS1xLK7zazE2KO/IDIuvNnJ6LWxx7BBY3Rpdml0ec6qQaOsyOe5+9PDu6e08r+qwcvSu7j20MK1xEFjdGl2aXR5zqpCo6zEx8O0QrXEb25SZXN1bWW6zUG1xG9uUGF1c2XLrc/I1rTQ0MTho788L3A+DQo8cD7O0sPHz8jAtLvYtPC12tK7uPbOyszio6y008q1vMrKudPDuf2zzMC0y7WjrCBvblN0YXJ0us1vblJlc3VtZaOsb25QYXVzZbrNb25TdG9wv7TG8MC0tcTIt7Lusru24KOsyfXWwc7Sw8e/ydLU1ruxo8H0xuTW0LXE0ru21KOsscjI59a7saPB9G9uU3RhcnS6zW9uU3RvcKOsvMjIu8jntMujrMTHzqrKssO0QW5kcm9pZM+1zbO7ubvhzOG5qb+0xvDAtNbYuLS1xL3Tv9rE2KO/uPm+3cnPw+a1xLfWzvajrM7Sw8fWqrXAo6zV4sG9uPbF5LbUtcS72LX3t9ax8LT6se2yu82stcTS4tLlo6xvblN0YXJ0us1vblN0b3DKx7TTQWN0aXZpdHnKx7fxv8m8+9XiuPa9x7bIwLS72LX3tcSjrLP9wcvV4tbWx/ix8KOs1NrKtbzKtcTKudPD1tCjrMO709DG5Mv7w/fP1LXEx/ix8DwvcD4NCjxwPrXatv649s7KzOKjrM7Sw8e+zdKqtNPUtMLrtcS9x7bIwLS31s720tS8sLXDtb294srNwcujrLnY09pBY3Rpdml0ebXEuaTX99StwO274dTasb7K6brz0PjVwr3avfjQ0L2yveKjrNXiwO/O0sPHtPPWwrXEwcu94ry0v8mjrLTTQWN0aXZpdHm1xMb0tq+5/bPMwLS/tKOsztLDx8C0v7TSu8/Cz7XNs7XE1LTC66OsQWN0aXZpdHnG9Lavuf2zzLXE1LTC68/gtbG4tNTTo6zJ6LzGtb3By0luc3RydW1lbnRhdGlvbixBY3Rpdml0us1BY3Rpdml0eU1hbmFnZXJTZXJ2aWNlo6hBTVOjqaOs1eLA77K7z+rPuLfWzvbV4tK7uf2zzKOsvPK1pcDtveKjrMb0tq9BY3Rpdml0ebXEx+vH87vh08kQEBAgSW5zdHJ1bWVudGF0aW9uIMC0tKbA7aOsyLu688v7zai5/UJpbmRlcs/yQU1Tt6LH68fzo6xBTVPE2rK/zqy7pNfF0ru49kFjdGl2aXR5U3RhY2ujrLKiuLrU8NW7xNq1xEFjdGl2aXR5tcTXtMyszayyvaOsQU1Tzai5/UFjdGl2aXR5VGhyZWFkyKXNrLK9QWN0aXZpdHm1xNe0zKy007b4zeqzycn6w/zW3Mbat723qLXEtffTw6Os1NpBY3Rpdml0eVN0YWNr1tC1xHJlc3VtZVRvcEFjdGl2aXR5TG5uZXJMb2NrZWS3vbeo1tCjrNPQ1eLDtNXits60+sLrPC9wPg0KPHByZSBjbGFzcz0="brush:">
//we need to start pausing the current activity so the top one can be resumed
boolean dontWaitForPause = (.flags& ActivityInfo.FLAG_RESUME_WHILE_PAUSING)!=0;
boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, KeyStore.TrustedCertificateEntry,dontWaitForPause);
if(mResumedActivity != null){
pausing != startPaUSINGlOCAKED(userLeaving,false,true,dontWaitForPause);
if(DEBUG_STATES){
Slog.d(TAG,"resumeTopActivityLocked:pausing" + mResumedActivity);
从上述的代码中我们可以看出,在新Activity启动之前,栈顶的Activity需要先onPause后,新的Activity才能启动,最终,在ActvityStackSupervisor中的realStartActivityLocked方法中,会调用如下代码
app.thread.scheduleLaunchActivity(new Intent(r.intent),r.appToken,System.identityHashCode(r),r.info,new Configuration(mService.mConfiguration)
,r.compat,r.task.voiceInteractor,app.repProcState,r.icicle,r.persistentState,results,new Intents,!andResume,mService.isNextTransitionForward()
,profilerInfo);
我们都知道,在这个app.thread的类型是IApplicationThread的具体实现实在ActivityTread中,所以,这段代码实际上遇到了ActivityThread当中,,即ApplicationThread的scheduleLaunchActivity方法,而scheduleLaunchActivity方法最终会完成生命周期的调用过程,因此可以得出结论,是旧Activity县onPause,然后新的Activityy再启动
至于ApplicationThread的scheduleLaunchActivity方法为什么会完成新Activity的生命周期,请看接下来的代码,scheduleLaunchActivty为什么会完成新的Activty
private void handlerLaunchActivity(ActivityClientRecord r, Intent customIntent){
//if we are getting ready to gc after going to the background,well we are back active so skip it
unscheduleGcIdler();
mSomeActivitiesChanged =
if(r.profilerInfo != null){
mProfiler.setProfiler(r.profilerInfo);
mProfiler.startP
//Make sure we are running with the most recent config
handlerConfigurationChanged(null,null);
if(localLOGV)Slog.v
TAG,"Handling launch of"+r);
//在这里新Activity被创建出来,其onCreate和onStart被调用
Activity a = PerformLaunchActivity(r,customIntent);
if(a != null){
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.
handlerResumeActivity(r.token,false,r.isForward,
!r.activity.mFinished && r.startsNotResumed);
c从上面的分析可以看出,当新的Activity启动的时候,旧的Activity的onPause方法会先执行,然后才启动新的Activity,到底是不是这样尼?我们可以写一个小栗子来验证一下,如下是两个Activity的代码,在MainActivity中点击按钮可以跳转到SecondActivity,同时为了分析生命周期,我们把log日志也打出来
MainActivity
package com.liuguilin.
import android.content.I
import android.os.B
import android.support.v7.app.AppCompatA
import android.util.L
import android.view.V
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btnTo).setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
startActivity(new Intent(MainActivity.this, SecondActivity.class));
protected void onPause() {
super.onPause();
Log.i(TAG, "onPause");
protected void onStop() {
super.onStop();
Log.i(TAG, "onStop");
SecondActivity
package com.liuguilin.
import android.os.B
import android.support.v7.app.AppCompatA
import android.util.L
* Created by lgl on 16/8/24.
public class SecondActivity extends AppCompatActivity {
private static final String TAG = "SecondActivity";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Log.i(TAG, "onCreate");
protected void onStart() {
super.onStart();
Log.i(TAG, "onStart");
protected void onResume() {
super.onResume();
Log.i(TAG, "onResume");
这样我们可以观察到他的生命周期
通过这个生命周期我们可以观察到,旧的Activity的onPause先调用,然后新的Activity才启动,这也证实了我们上面的分析原理,也许有人问,你只是分析了Andorid5.0的源码,你怎么所有的版本源码逻辑都相同,的确,我们不能把所有的版本都概括,但是作为Android的一个运行过程的基本逻辑,随着版本的更新并不会很大的改变,因为Android也需要兼容性,,不能说在同一个版本上运行有两种不同的逻辑,那根本不可能,关于这一点,我们要把握一个度,就是对于Android的基本运行机制,的不同,Android不能在onPause中做重量级的操作,因为必须在onPause执行完成以后新的Activity才能Resume,从这一点我们也间接性的证明了我们的结论,通过分析这个问题,我们知道onPause和onStop都不能做耗时的操作,尤其是onPause,这也意味着,我们应当尽量的在onStop中做操作,从而使新的Activity尽快显示出来并且换到前后台
三.异常情况下的生命周期分析
上一节我们分析的是正常事情下的生命周期,但是我们写程序也不要理想化,居多的问题就是出在异常情况下,我们知道,Activity除了受用户操作导致的正常生命周期的调度,同时还会存在一些异常的情况,比如当资源相关的系统配置发生改变以及系统内存不足的时候,Activity就有可能被杀死,下面我们具体来分析下这几种情况
1.情况1:资源相关的系统配置发生改变导致Activity被杀死并重新创建
理解这个问题,首先要对系统的资源加载有一定的了解,这里就不详细分析系统资源加载的机制了,但是我们简单说明一下,拿最简单的图片来说,当我们把一张图片挡在drawable中的时候,就可以通过Resources去获取这张图片了,同时为了兼容不同的设备,我们可能还需要在其他一些目录下放置不同的图片,比如drawable-xhdpi之类的,当应用程序启动时,系统会根据当前设备的情况去加载合适的Resources资源,比如说横屏手机和竖屏手机会拿着两张不同的图片(设定了landscape或者portrait状态下的图片),比如之前Activity处于竖屏,我们突然旋转屏幕,由于系统配置发生了改变,在默认情况下,Activity会被销毁并且重新创建,当然我们也可以阻止系统重新创建我们的Activity
默认情况下,如果我们的Activity不做特殊处理,那么当系统配置发生改变之后,Activity就会销毁并且重新创建,可以看图
当系统配置发生改变的时候,Activity会被销毁,其onPause,onStop,onDestroy均会被调用,同时由于Activity是异常情况下终止的,系统会调用onSaveInstanceState来保存当前Activity的状态,这个方法调用的时机是在onStop之前,他和onPause没有既定的时序关系,他即可能在onPause之前调用,也有可能在之后调用,需要强调的是,这个方法只出现在Activity被异常终止的情况下,正常情况下是不会走这个方法的吗,当我们onSaveInstanceState保存到Bundler对象作为参数传递给onRestoreInstanceState和onCreate方法,因此我们可以通过onRestoreInstanceState和onCreate方法来判断Activity是否被重建。如果被重建了,我们就取出之前的数据恢复,从时序上来说,onRestoreInstanceState的调用时机应该在onStart之后
同时我们要知道,在onSaveInstanceState和onRestoreInstanceState方法中,系统自动为我们做了一些恢复工作,当Activity在异常情况下需要重新创建时,系统会默认我们保存当前的Activity视图架构,并且为我们恢复这些数据,比如文本框中用户输入的数据,ListView滚动的位置,这些View相关的状态系统都会默认恢复,具体针对某一个特定的View系统能为们恢复那些数据?我们可以查看View的源码,和Activity一样,每一个View都有onSaveInstanceState和onRestoreInstanceState这两个方法,看一下他们的实现,就能知道系统能够为每一个View恢复数据
关于保存和恢复View的层次结构,系统的工作流程是这样的:首先Activity被意外终止时,Activity会调用onSaveInstanceState去保存数据,然后Activity会委托Window去保存数据,接着Window再委托上面的顶级容器去保存数据,顶级容器是一个ViewGroup,一般来说他可能是一个DecorView,最后顶层容器再去一一通知他的子元素来保存数据,这样整个数据保存过程就完成了,可以发现,这是一种典型的委托思想,上层委托下层,父容器委托子容器,去处理一件事件,这种思想在Android 中有很多的应用,这里就不再重复介绍了,接下来举个例子,那TextView来说,我们分析一下他到底保存了那些数据
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
// Save state if we are forced to
boolean save = mFreezesT
int start = 0;
int end = 0;
if (mText != null) {
start = getSelectionStart();
end = getSelectionEnd();
if (start &= 0 || end &= 0) {
// Or save state if there is a selection
if (save) {
SavedState ss = new SavedState(superState);
// XXX Should also save the current scroll position!
ss.selStart =
ss.selEnd =
if (mText instanceof Spanned) {
Spannable sp = new SpannableStringBuilder(mText);
if (mEditor != null) {
removeMisspelledSpans(sp);
sp.removeSpan(mEditor.mSuggestionRangeSpan);
ss.text = mText.toString();
if (isFocused() && start &= 0 && end &= 0) {
ss.frozenWithFocus =
ss.error = getError();
return superS
从上述中我们可以看到,TextView为了保存自己的文本选中和文本结构内容,并且通过查看onRestoreInstanceState方法的源码,可以发现它的确恢复了这些数据,具体源码就不在贴出,读者可以自己去看下源码,下面我们看来看下实际的例子,对比一下Activity正常终止和异常终止的不同,同时验证一下系统的数据恢复能力,为了方便测试,我们采用了旋转屏幕来终止Activity,在我们旋转屏幕以后,Activity被销毁重建,我们输入的文本被正确还原了,说明我们的系统能够正确的做一些View层的分析,我们看下代码
package com.liuguilin.
import android.os.B
import android.os.PersistableB
import android.support.v7.app.AppCompatA
import android.util.L
public class MainActivity extends AppCompatActivity {
public static final String TAG = "MainActivity";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState != null) {
String test = savedInstanceState.getString("extra_test");
Log.i(TAG, test);
public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) {
super.onSaveInstanceState(outState, outPersistentState);
Log.i(TAG, "onSaveInstanceState");
outState.putString("extra_test", "test");
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
String test = savedInstanceState.getString("extra_test");
Log.i(TAG, test);
上面的代码很简单,首先我们在onSaveInstanceState中保存一个字符串,然后当我们的Activity被销毁并且重新创建之后,我们再去获取之前存储的字符串,接收的位置可以选择onRestoreInstanceState或者onCreate,两者的区别是:onRestoreInstanceState一旦被调用,其参数Bundler savedInstanceState一定有值,我们不用额外的判断是否为空但是onCreate不行,onCreate如果正常启动的话,其参数Bundler onSaveInstanceState为null,所以需要一些额外的判断,这两个方法我们选择任意一个都是可以进行数据恢复的,但是关键建议我们使用onRestoreInstanceState去恢复数据
Activity销毁后调用了onSaveInstanceState来保存数据,重新创建以后再onCreate和onRestoreInstanceState中能恢复数据,这个正好证明了我们刚才的分析,针对onSaveInstanceState我们有一点要说明,那就是系统只会在即将被销毁并且有机会重新显示的情况下才会去调用它,考虑到这一种情况,当Activity正常销毁的时候,系统不会调用onSaveInstanceState,因为被销毁的Activity不可能再次被显示出来,这句话不好理解,但是我们可以对比一下旋转屏幕所造成的Activity异常销毁,这个过程和正常停止的Activity是不一样的,因为旋转屏幕之后,Activity被销毁的同时会立即创建新的Activity实例,这个时候Activcity有机会再次立刻显示,所以系统进行了数据存储,这里可以简单的这么理解,系统只在Activity异常终止的情况下才会调用onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,其他情况不会触发
2.情况2:资源内存不足导致低优先级的Activity被杀死
这个情况我们不好模拟,但是其数据的存储和恢复过程和情况一是一致的,这里我们描述一下Activity的优先级情况,Activity按照优先级的从高往低,可以分为三种:
1.前台Activity:正在和用户交互的Activity,优先级最高 2.可见但非前台Activity:比如对话框,导致Activity可见但是位于后台无法和用户直接交互 3.后台Activity:已经被暂停的Activity,比如执行了onStop,优先级最低
当系统内存不足的时候,系统就会按照上述优先级去杀死目标Activity所在的进程,并且在后续通过onSaveInstanceState和onRestoreInstanceState来存储和恢复数据,如果一个进程中没有四大在执行,那么这个进程将很快被系统杀死,因此,一些后台工作不适合脱离四大组建而独立运行在后台中,这样进程很容易就被杀死了,比较好的方法就是将后台工作放在Service中从而保证了进程有一定的哟徐爱你集,这样就不会轻易的被杀死
上面分析了系统的数据存储和恢复机制,我们知道,当系统配置发生改变后,Activity会被重新创建,那我们有没有什么办法不重新创建尼?答案是有的,接下来我们来分析一下这个问题,系统配置中有很多内容,如果当某项内容发生改变后,我们不想系统重新创建,就可以给configChangs属性加上orientation这个值
android:configChanges="orientation"
如果想指定多个值的话可以用“|”连接起来
mcc:The IMSI mobile country code (MCC) has changed — a SIM has been detected and updated the MCC.
IMSI(国际移动用户识别码)发生改变,检测到SIM卡,或者更新MCC
mnc:The IMSI mobile network code (MNC) has changed — a SIM has been detected and updated the MNC.
IMSI网络发生改变,检测到SIM卡,或者更新MCC其中mcc和mnc理论上不可能发生变化
locale:The locale has changed — the user has selected a new language that text should be displayed in.
语言发生改变,用户选择了一个新的语言,文字应该重新显示
touchscreen:The touchscreen has changed. (This should never normally happen.)
触摸屏发生改变,这通常是不应该发生的
keyboard:The keyboard type has changed — for example, the user has plugged in an external keyboard.
键盘类型发生改变,例如,用户使用了外部键盘
keyboardHidden:The keyboard accessibility has changed — for example, the user has revealed the hardware keyboard.
键盘发生改变,例如,用户使用了硬件键盘
navigation:The navigation type (trackball/dpad) has changed. (This should never normally happen.)
导航发生改变,(这通常不应该发生) 举例:连接蓝牙键盘,连接后确实导致了navigation的类型发生变化。因为连接蓝牙键盘后,我可以使用方向键来navigate了
screenLayout:The screen layout has changed — this might be caused by a different display being activated.
屏幕的布局发生改变,这可能导致激活不同的显示
ontScale:The font scaling factor has changed — the user has selected a new global font size.
全局字体大小缩放发生改变
orientation:The screen orientation has changed — that is, the user has rotated the device.设备旋转,横向显示和竖向显示模式切换。
screenSize: 屏幕大小改变了
smallestScreenSize: 屏幕的物理大小改变了,如:连接到一个外部的屏幕上
4.2增加了一个layoutDirection属性,当改变语言设置后,该属性也会成newConfig中的一个mask位。所以ActivityManagerService(实际在ActivityStack)在决定是否重启Activity的时候总是判断为重启。
需要在android:configChanges 中同时添加locale和layoutDirection。
在不退出应用的情况下切换到Settings里切换语言,发现该Activity还是重启了。
从上面的属性中我们可以知道,如果我们没有在Activity的configChanges中设备属性的话,当系统发生改变后就会导致Activity重新被创建,上面表格中的项目很多,但是我们常用的只有locale,orientation,keyboardHidden这三个选项,其他用的还是比较少的,这里设置之后显示的效果我就不演示了
四.Activity的启动模式
的启动模式,是很有用的,对于Activity的栈的处理,也是极其讲究的,所以你一定要清除他的标志位和启动模式
一.Activity的LaunchMode
首先说一下Activity为什么需要启动模式,我们知道,在默认的情况下,当我们多次启动同一个Activity的时候,系统会创建多个实例并把他们一一放入任务栈中,当我们点击back键的时候会发现这些Activity会一一回退,任务栈是一种先进先出的栈结构,这个好理解, 每按一次back键就有一个activity退出栈,知道栈空为止,当这个栈为空的时候,系统就会回收这个任务栈,关于任务栈的系统工作原理,这里我们暂且不说,在后续章节也会介绍任务栈,知道了Activity的启动模式,我们可发现一个问题,:多次启动同一个Activity会创建多个实例,这样是不是很逗,Activity在设计的时候不可能不考虑到这个问题,所以他提供了启动模式来修改系统的默认行为,目前有四种启动模式
standard singleTop singleTask singleInstance
我们先来把这几种启动模式都给介绍完
standard:标准模式,这也是系统的默认模式,每次启动一个Activity都会重新创建一个实例,是否这个实例已经存在,被创建的实例的生命周期符合典型情况下Activity的生命周期,如上述:onCreate(),onStart();onResume()都会被调用,这是一种典型的多实例实现,一个任务栈都可以有多个实例,每个实例都可以属于不同的任务栈,在这种模式下,谁启动了这个Activity,那么这个Activity就运行在启动它的Activity所在的栈内,比如Activity A启动了Activity B(B是标准模式),那么B就会进入到A所在的栈内,不知道读者有没有注意到,当我们用ApplicationContext去启动standard模式的Activity的时候就会报错:
E/AndroidRuntime(674): android.util.androidruntiomException: Calling startActivity from outside of an Activity context requires the FLAG_ACTIVITY_TASK flag . Is this really what are want?
相信读者对这句话不会陌生,这是因为我们的standard模式的Activity默认会进入启动它的Activity所属的任务栈中,但是由于非Activity类型的Context(如ApplicationContext)并没有所谓的任务栈,所以这就有问题了,解决这个问题,就是待启动Activity指定FLAG_ACTIVITY_TASK标记位,这样启动的时候就会为他创建一个新的任务栈,这个时候待启动Activity实际上是以singleTask模式启动的,读者可以仔细体会
singleTop:栈顶复用模式,在这个模式下,如果新的Activity已经位于任务栈的栈顶,那么此Activity不会被重新创建,同时他的onNewIntent方法会被调用,通过此方法的参数我们可以取出当前请求的信息,需要注意的是,这个Activity的onCreate,onStart不会被系统调用,因为他并没有发生改变,如果新Activity已存在但不是在栈顶,那么新Activity则会重新创建,举个例子,假设现在栈内的情况为ABCD,其中ABCD为四个Activity,A位于栈底,D位于栈顶,这个时候假设要再启动D,如果D的启动模式为singleTop,那么站栈内的情况仍然是ABCD,如果D的启动模式是standard,那么由于D会被重新创建,导致情况就是ABCDD
singTask:栈内复用模式,这是一种单实例模式,在这种模式下,只要Activity在一个栈内存在,那么多次启动此Activity都不会创建实例,和singTop一样,系统也会回调其onNewIntent方法,具体一点,当一个具有singleTask模式的Activity请求启动后,比如Activity A,系统首先会去寻找是否存在A想要的任务栈,如果不存在,就小红心创建一个任务栈,然后创建A的实例把A放进栈中,如果存在A所需要的栈,这个时候就要看A是否在栈中有实例存在,如果实例存在,那么系统就会把A调到栈顶并调用它的onNewIntent方法,如果实例不存在,就创建A的实例并且把A压入栈中,举几个例子
比如目前任务栈S1中的情况为ABC,这个时候Activity D以singleTask模式请求启动,其所需的任务栈为S2,由于S2和D的实例都不存在,所以系统会先创建任务栈S2,然后创建D的实例将其入栈到S2 另外一种情况,假设D所需的任务栈为S1,其他情况如如上面的一样,那么由于S1已经存在,所以系统会直接创建D的实例并将其引入到S1中 如果D所需要的任务栈为S1,并且当前任务栈S1的情况为ABCD,根据栈内复用的原则,此时D不会被重新创建,系统会把D切换到栈顶并且调用其oNnNewIntent方法,同时由于singleTask默认具有clearTop的效果,会导致栈内所有在D上面的Activity全部出栈,于是最终S1中的情况为AD,这一点比较特殊,在后面还会对此情况详细的分析
通过上述的三个例子,读者应该还是比较清晰的理解singTask的含义吧
singleInstance:单实例模式,这是一种加强的singleTask的模式,他除了具有singleTask的所有属性之外,还加强了一点,那就是具有此模式下的Activity只能单独的处于一个任务栈中,换句话说,比如Activity A是singleInstance模式,当A启动的时候,系统会为创建创建一个新的任务栈,然后A独立在这个任务栈中,由于栈内复用的特性,后续的请求均不会创建新的Activity,除非这个独特的任务栈被系统销毁了
上面介绍了几种启动模式,这里需要指出一种情况,我们假设目前有两个任务栈,前台任务栈的情况为AB,而后台任务栈的情况是CD,这里假设CD的启动模式都是singleTask,现在请求启动D,那么整个后台任务站都会被切换到前台,这个时候整个后退列表变成了ABCD,当用户按back键的时候,列表中的Activity会一一出栈,如图
如果不是请求D,而是请求C,那么情况就不一样了,如图,具体原因我们在后续章节中分析
另外一个问题,在singkleTask启动模式中,多次提到了某个Activity所需的任务栈,什么是Activity所需的任务栈尼?这要从一个参数说起:TaskAffinity,可以翻译成任务相关性,这个参数标示了一个Activity所需要的任务栈的名字默认情况下,所有的Activity所需要的任务栈的名字为应用的包名,当然,我们可以为每个Activity都单独指定TaskAffinity,这个属性值必须必须不能和包名相同,否则就相当于没有指定,TaskAffinity属性主要和singleTask启动模式或者allowTaskReparenting属性配合使用,在其他状况下没有意义,另外,任务栈分为前台任务栈和后台任务栈,后台任务栈中的Activity位于暂停状态,用户可以通过切换将后台任务栈再次调为前台
当TaskAffinity和singleTask启动模式配对使用的时候,他是具有该模式Activity目前任务栈的名字,待启动的Activity会运行在名字和TaskAffinity相同的任务栈中
当TaskAffinity和allowTaskReparentiing结合的时候,这种情况比较复杂,会产生特殊的效果,当一个应用A启动了应用B的某一个Activity后,如果这个Activity会直接从应用A的任务栈转移到应用B的任务栈中,这还是很抽象的,再具体点,比如现在有2个应用A和B,A启动了B的一个Activity C ,然后按Home键回到桌面,然后再单击B的桌面图标,这个时候并不是启动; B的主Activity,而是重新显示了已经被应用A启动的Activity C,或者说,C从A的任务栈转移到了B的任务栈中,可以这么理解,由于A启动了C,这个时候C只能运行在A的任务栈中,但是C属于B应用,正常情况下,他的TaskAffinity值肯定不可能和A的任务栈相同(因为包名不同),所以,当B启动后,B会创建自己的任务栈,这个时候系统发现C原本所想要的任务栈已经被创建出来了,所以就把C从A的任务栈中转移过来,这种情况读者可以写一个例子测试一下,这里就不做演示了
如何给Activity指定启动模式?有两种方法,第一种是通过清单文件为Activity指定
另一种启情况就是通过intent的标志位为Activity指定启动模式
Intent intent = new Intent();
intent.setClass(this,SecondActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
这两种方式都可以为Activity指定启动模式,但是二者还是有一些区别的,首先,优先级上,第二种比第一种高,当两种同时存在的时候,以第二种为准,其次,上述两种方式在限定范围内有所不同,比如,第一种方式无法直接为Activity设置FLAG_ACTIVITY_CLEAR_TOP标识,而第二种方式无法指定singleInstance模式
关于Intent中为Activity指定的各种标记位,在下面的小节中会继续说道,下面通过一个实例来体验启动模式的使用效果,还是前面的例子,我们把MainActivity的启动模式设置成singleTask,然后重复启动它,看看他是否会重复创建
//点击事件
findViewById(R.id.btnTo).setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent intent = new Intent();
intent.setClass(MainActivity.this,MainActivity.class);
intent.putExtra("time", System.currentTimeMillis());
startActivity(intent);
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "onNewIntent , time =" + intent.getLongExtra("time", 0));
上述修改,我们做了如下操作,连续点击三次按钮启动三次,算上原本的MainActivity实例,正常情况下,任务栈中应该有四个MainActiivty实例,但是我们为其指定了singTask模式,我们一起来看下有何不同
adb shell dumpsys activity
ok,输入日志
从上面的信息中不难看出,尽管启动了四次MainActivity,但是他始终只有一个实例在任务栈中,从log中我们也可以看到
Activity的确没有被创建,只是暂停了一下,然后调用了onNewIntent,接着又onResume又继续了,现在我们去掉singTask,再来比对一下我们的操作,同样是点击三次按钮,执行adb命令
我们能够得到目前总共有2个任务栈,前台任务栈taskAffinity值为包名。里面有四个Activity,后台是一个com.android.launcher,他里面就一个桌面
从上面的导出信息可以看到,在任务栈中有四个MainActivity,这也就验证了Activity的启动模式的工作方式
上述四种启动模式,standard和singleTop都比较好理解,singleInstance由于其特殊性也比较好理解,但是关于singleTask还有一种情况需要理解,比如我们刚才那张启动D的图,在Activity B中请求的不是D而是C,那情况该如何尼?这里可以告诉读者的是,任务列表变成了ABC,是不是很奇怪,Activity为什么直接出栈了,我们用一个实例来说明情况
我们把SecondActivity和CodeActivity的启动模式都改成了singleTask,并且把android:taskAffinity设置为com.liuguilin.activitysample,注意这个taskAffinity的值属于字符串,切中间必须有包名分割符&.&,然后我们做如下的动作,在MainActivity中单击按钮启动SecondActivity,在SecondActivity中单击按钮启动CodeActivity,在CodeActivity启动MainActivity,最后在MainActivity中启动SecondActivity,,现在按Back,你知道会回那个Activity吗?答案是桌面,是不是有点摸不着头脑,我们看图理解一下
首先,我们来分析一下这个问题,我们知道,A的启动模式是standard,按照规定,A的taskAffinity继承的是application的taskAffinity,而application默认taskAffinity是包名,所以A的taskAffinity是包名,由于我们在XML中为B和C指定了taskAffinity和启动模式,且有相同的taskAffinity,所以B和C是singleTask模式且有相同的taskAffinity,所以A启动B的时候,按照singleTask的规则,这个时候需要为B重新创建一个任务栈了,B再启动C,按照singleTask的规则,由于C所需要的任务栈已经被B给创建了,所以无需再创建新的任务栈,这个时候系统只是创建C的实例放进任务栈,接着C再启动A,A是标准模式,所以系统会为他创建一个新的实例并将他加入到启动的任务栈中,由于是C启动了A,所以A会进入C的栈内并处于栈顶,这个时候已经有两个任务栈了,接着A再启动B,由于B是singleTask,B需要回到任务栈的栈顶,由于栈的模式为&先进先出&,B想要回到栈顶,就只能是CA出栈,所以到这里就很好理解,按back键,B就出栈了,然后这个任务栈就是空的,被系统回收了,这个时候就只能是回到后台任务栈把A显示出来,注意这个A是后台任务栈的A,不是BC栈的A,接着再按back就回到了桌面,分析到这里,我们就得到了一条结论,singtleTask模式的Activity切换到栈顶会到导致在他之上的栈内activity出栈,我们可以看下运行的结果
接着我们再实验中再次验证这个问题,我们采用dumpasys命令,看看输出的是什么?
可以看到在B任务栈中只剩下B了,其他的都出栈了,这个时候按back肯定就回收了,分析到这里,我相信读者对Activity的启动模式有了很深入的了解吧,下面我们再来说下Activity的标志位
二.Activity 的 Flags
Activity的Flags有很多,这里主要是分析一些常用的标记位,标记位的作用很多,有些标志位可以设置Activity的启动模式,比如FLAG_ ACTIVITY _ NEW _ TASK,还有一些直接影响Activity的运行状态,比如FLAG_ ACTIVITY_ CLEAR_ TOP,下面我们来说下一些常用的标记位,剩下的读者可以去看下官方文档,大部分的情况下,Activity不需要设置标记位,因此对于标记位理解即可,在使用标记位的时候,要注意有些标记位是系统内部使用的,应用不需要去设置这些以防出问题。
FLAG_ ACTIVITY_ NEW _ TASK
这个标志位的作用是为Activity指向&singleTask&启动模式,其效果和XML中指定该模式相同
FLAG_ ACTIVITY_ SINGLE _ TOP
这个标志位的作用是为Activity指向&singleTop&启动模式,其效果和XML中指定该模式相同
FLAG_ ACTIVITY_ CLEAR _ TOP
具有此标记位的Activity,当他启动时,在同一个任务栈中所有位于他上面的Activity都要出栈,这个模式一般需要和FLAG_ ACTIVITY_ NEW _ TASK配合使用,在这种情况下,被启动的Activity的实例如果已经存在,那么系统就会调用它的onNewIntent,如果被启动的Activity采用标准模式,那么他连同他之上的Activity都要出栈,系统会创建新的Activity实例并放入栈顶
FLAG_ ACTIVITY_ EXCLUDE_ FROM _ RECENTS
具有此标记位的Activity,不会出现在历史Activity的列表当中,当某种情况下我们不希望用户通过历史列表回到我们的Activity的时候就使用这个标记位了,他等同于在XML中指定Activity的属性:
android:excludeFromRecents="true"
三.intentFilter的匹配规则
我们知道,启动Activity分为两种,显示调用和隐式调用,二者的区别这里就不多讲了,显示调用需要明确的指定被启动对象的组件信息,包括包名和类名,而隐式意图则不需要明确指定调用信息,原则上一个intent不应该即是显式调用又是隐式调用,如果二者共存的话以显式调用为主,显式调用很简单,这里主要介绍隐式调用,隐式调用需要intent能够匹配目标组件的IntentFilter中所设置的过滤信息,如果不匹配将无法启动目标Activity,IntentFilter中的过滤信息有action,category,data,下面是一个过滤规则的实例:
为了匹配过滤列表,需要同时匹配过滤列表中的action,category,data信息,否则匹配失败,一个过滤列表中的action,category,data可以有多个,所有的action,category,data分别构成不同类别,同一类型的信息共同约束当前类别的匹配过程,只有一个intent同时匹配action类别,category类别,data类别才算是匹配完成,只有完全匹配才能成功启动目标Activity,另外一点,一个Activity钟可以有多个intent-filter,一个intent只要能匹配一组intent-filter即可成功启动Activity
下面详细分析各种属性的匹配规则
1.action的匹配规则
action是一个字符串,系统预定了一些action,同时我们也可以在应用中定义自己的action,action的匹配规则是intent中的action必须能够和过滤规则中的action匹配,这里说的匹配是指action的字符串值完全一样,一个过滤规则中的可以有多个action,那么只要intent中的action能够和过滤规则匹配成功,针对上面的过滤规则,需要注意的是,intent如果没有指定action,那么匹配失败,总结一下,action的匹配需求就是intent中的action存在且必和过滤规则一样的action,这里需要注意的是他和category匹配规则的不同,另外,action区分大小写,大小写不同的字符串匹配也会失败
2.category的匹配规则
category是一个字符串,系统预定义了一些category,同时我们也可以在应用中定义自己的category。category的匹配规则和action不同,它要求Intent中如果含有category,那么所有的category都必须和过滤规则中的其中一个category相同。换句话说,Intent如果出现了category,不管有几个category,对于每个category来说,它必须是过滤规则中已经定义的category。当然,Intent中可以没有category,如果没有category的话,按照上面的描述,这个Intent仍然可以匹配成功。这里要注意下它和action匹配过程的不同,action
是要求Intent中必须有一个action且必须能够和过滤规则中的某个action相同,而category要求Intent可以没有category,但是如果你一旦有category,不管有几个,每个都要能和过滤规则中的任何一个category相同。为了匹配前面的过滤规则中的category,我们可出下面的Intent,intent.addcategory (“com.ryg.category.c”)或者Intent.addcategory (“com rcategory.d)亦或者不设category。为什么不设置category也可以匹配呢?原因是系统在调用startActivity或者startActivityForResult的时候会默认为Intent加上“android.intent.category.DEFAULT”这个category,所以这个category就可以匹配前面的过滤规则中的第三个category。同时,为了我们的activity能够接收隐式调用,就必须在intent-filter中指定“android intent categor.DEFAULT”这个category,原因刚才已经说明了。
3.data匹配规则
data的匹配规则和action有点类似,如果过滤规则中定义了data,那么intent中必须也要定义可匹配的data,在介绍data的匹配规则之前,我们需要来了解一下data的结构,因为data稍微有点复杂
data由两部分组成,mimeType和URI,前者是媒体类型,比如image/jpeg等,可以表示图片等,而URI包含的数据可就多了,下面的URI的结构:
这里再给几个实际的例子就好理解了
content://com.liuguilin.project:200/folder/subfolder/etc
:80/search/info
看了上面的两个例子你是不是瞬间就明白了,没错,就是这么简单,不过下面还是要一一介绍含义的:
Scheme:URI的模式,比如http.file.content等,如果URI中没有指定的scheme,那么整个URI的其他参数无效,这也意味着URI无效 Host:URI的主机,比如,如果host未指定,那么整个URI中的其他参数无效,这也意味着URI无效 Port:URI中的端口号,比如80,不过需要指定上面两个才有意义 Path、pathPattem 和 pathPrefix:这三个参数表述路径信息,其中path表示完整的路径信息:pathPattern也表示完整的路径信息,但是它里面可以包含通配符“ * ”,“ * ” 表示0个或多个任意字符,需要注意的是,由于正则表达式的规范,如果想表示真实的字符串,那么“* ” 要写成 “ \*”,“ \ ”要写成“ \\ ”,pathPrefix表示路径的前缀信息。
介绍完data的数据格式后,我们要说一下data的匹配规则了。前面说到,data的匹配规则和action类似,它也要求Intent中必须含有data数据,并且data数据能够完全匹配过滤规则中的某一个datn.这里的完全匹配是指过滤规则中出现的data部分也出现在了 Intent
中的data中。下面分情况说明。
(1) 如下过来规则
这种规则指定了所有类型为图片,那么intent中的mineType属性必须为“image/*”才能匹配,这种情况下虽然过来规则没有指定URI,但是却有默认值,URI的默认值为content何file,也就是说,虽然没有指定URI,但是Intent中的URI部分的scheme必须为content或者file才能匹配,这点事需要注意的,为了匹配一种的规则我们可以这样写:
intent.setDataAndType(Uri.parse("file://abc"),"image/png");
另外,如果要为Intent指定完整的data,必须调用setDataAndType方法,不能县调用setData在调用setType,因为这两个方法彼此会清除对方的值,这个看源码就比较好理解了,比如setData:
public Intent setData(Uri data) {
可以发现,setData会把类型设置为null,同样的,对方也是
(2)如下规律规则
这种规则指定了两组data规则,且每个data都指出了完整的属性值,既有URI又有类型,为了匹配类型二,我们这样写“
intent.setDataAndType(Uri.parse("http://abc"),"video/png");
intent.setDataAndType(Uri.parse("http://abc"),"audio/png");
通过上面的实例,我们应该知道了data的匹配规则,关于data还有一些特殊的情况需要说明一下,这也是他和action不同的地方
到这里我们已经把IntentFilter的过滤规则都讲了一遍了,还记得本书前面给出的一个实例吗?现在我们给出完整的intent匹配规则
Intent intent = new Intent();
intent.addCategory("com.liuguilin.category.c");
intent.setDataAndType(Uri.parse("file//abc"),"text/plain");
startActivity(intent);
还记得URI中的scheme中的默认值吗?如果把上面的intent.setDataAndType(Uri.parse(“file//abc”),”text/plain”);这句改成intent.setDataAndType(Uri.parse(“http//abc”),”text/plain”);打开的actiivty就会报错,提示无法找到Activity,另外一点,intent-filter的匹配规则对于服务和广播也是同样的道理,不过系统对于Service的建议是尽量使用显式意图来启动服务。
最后,当我们通过隐式方式启动一个Activity的时候,可以做一下判断,看是否Activity能够匹配我们的隐式Intent,如果不做判断就有可能出现上述的错误了。判断方法有两种:采用PackageManager的resolveActivity方法或者Intent 的resolveActivity方法,
果它们找不到匹配的Activity就会返回null,我们通过判断返回值就可以规避上述错误了,另外,PackageManager还提供了queryIntentActivities方法,这个方法和resolveActivity方法法不同的是:它不是返回最佳匹配的Activity信息而是返回所有成功匹配的Activity信息,我们看一下queryIntentActivities和resolveActivity的方法原型:
public abstract ListqueryIntentActivities(Intent intent,int fladgs);
public abstract ResolveInfo resolveActivity(Intent intent,int flags);
上述两个方法的第一个参数比较好理解,第二个参数需要注意,我们要使用MATCH_ DEFAULT _ ONLY这个标记位,这个标记位的含义是仅仅匹配那些在intentfilter中声明了 & category android-name=”android.intent.category DEFAULT”&这个category的 Activity。使用这个标记位的意义在于,只要上述两个方法不返回null,那么startActivity一定可以成功,如果不用这个标记位,就可以把intent-filter 中 category不含DEFAULT的那些Activity给匹配出来,从而导致startActivity可能失败。因为不含有DEFAULT这个category的Activity是无法接收隐式Intent的。在action和 category中,有一类action和category比较重要,他们是:
这二者共同作用是用来标明这是一个入口Activity并且会出现在系统的应用列表中,少了任何一个都没有实际意义,也无法出现在系统的应用列表中,也就是二者缺一不可,另外,针对 Service和BroadcastReceiver,PackageManager同样提供了类似的方法去获取成功匹配的组件信息。
好的,我们的第一章就写到这里了,不得不说这是一本好书,非常的详细,也希望大家仔细的

我要回帖

更多关于 哪里能买山寨机 的文章

 

随机推荐