这是我的第二篇文章,前一篇关于Intent的文章是我人生的第一篇技术性文章,写得匆忙,发的也匆忙,没来得及自我介绍。我不算是一个Android初学者,但是我承认自己是菜鸟,在大二接触了安卓,后来因为考研就没有继续,现在考完了也是想重拾安卓,所以呢,即是为了提高自己的技术,也是想和志同道合的人交流,我就决定自己每学习一块内容,弄明白之后写成文章进行总结,和大家分享,如果有错的地方、不好的地方,或者是缺漏的地方,也是希望大家可以指正、交流,
好了,扯了一些没用的,当然得回到我的主题,继续我今天的文章。
Activity作为四大组件之一,其重要性不言而喻,通过掌握活动的状态、生命周期以及启动方法,可以更好的管理应用资源。
活动状态
Activity是什么?
听到这个问题,你可能会说,这么简单的问题谁不会啊,其实我觉得还是很有必要写,万一被面试官问到了,咱是不是也算压中题了。根据Android Developers上的解释:Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。说白了,Activity就是提供用来与用户进行交互的界面,并对之做出响应。
返回栈
返回栈这个概念也不得不提,活动是可以层叠的,一个活动的存在可以被新的活动所覆盖,新的活动不断打开,旧的活动就会不断压在下面。我们把活动的层叠比作是堆方块,一个一个往上放,放一个方块好比启动了一个新的活动,最后你想要拿到最下面的方块就必须拿开上面的所有方块,活动也一样,而这个过程就是数据结构里面的栈,先进后出,最先放着的方块,最后才能拿出来。而在这里,就称之为返回栈。
明白了上面两个概念以后,就可以来看看活动的具体状态。
运行状态
运行状态其实非常好理解。它位于返回栈的栈顶,也就是最上面的方块。当你俯身看这些方块的时候,最上面的那块方块(活动)也是你最先看到,所以运行状态的活动就是你一眼看到的活动(界面)。再比如你在用微信聊天,那个聊天界面就是一个活动,而且是处在运行状态的活动。
暂停状态
暂停状态的活动就不是处在返回栈的栈顶,但是呢,我们依旧可以看到它,什么情况下呢?比如还是那个方块,现在我们在最上面放一个非常薄的、透明的冰块,而下面的还是实心的方块,我们俯身的时候,就能透过第一块冰块,看到第二块实心方块。但是我们第一眼看到的还是冰块,所以说冰块是运行状态的活动,而第二块实心方块是暂停状态的活动。
现在我们来给暂停状态具体的定位,暂停状态就是当新的一个活动没有布满整个屏幕,或者是新的活动的界面是透明的,我们透过它还能看到上一个活动,在这两种情况下,我们称上一个活动就是暂停状态。
停止状态
停止状态就是活动完全不可见了。还是方块的例子,此时我们把最上面的方块也变成是实心的方块,这时候,俯身往下看,我们就看不到第二块方块(你要说侧着看,那我也没办法了),此时,第二块方块就是停止状态,除了第二块,第二块以下的所有都是停止状态,只要在这摞方块中。再如,我们使用手机,你打开一个软件,可能映入你眼帘的首先是一个登入页面,当你输入账号密码以后,就到了主页,而刚才的登入页面就是停止状态了。
其实,停止状态的活动,虽然看不见,但是还在,而且一般情况下,不会被系统回收,除非系统内存不够了。
销毁状态
销毁状态是最后一个状态。继续是熟悉的方块例子,因为在停止状态中,我们把最上面的方块换成了实心的方块,导致看不到第二块方块,而现在我又非常急切想看第二块方块,那就没有办法了,只能是把第一块方块移出去,这样我们就能看到第二块方块,这种情况下,我们就把第一块方块的状态称之为销毁状态。返回到活动中,就是栈顶活动被移出了栈,也就变成销毁状态了。那什么时候活动被销毁了呢?最简单的就是,你按了手机上的Back键,一个界面消失了,就是一个活动销毁了。
活动的生命周期
Activity中定义了7种回调方法,也就是一个活动的整个生命周期。看了别人写的关于Activity的文章,都放了一张官方的活动生命周期图,那我就。。。也不例外了,管它看不看的懂,放了再说。
虽然上面有中文信息,但是不细看,不细琢磨,还是对一些概念迷糊。
首先解释一下7个生命周期:
onCreate()
onCreate()方法是我们最常见的了,因为每一次你在Activity中写代码的时候,都是在重写了这个方法,它的功能也是非常明确,首先调用父类方法,然后就是加载布局,当然这两部分都是你的平台帮你直接写好了,你也就可能疏忽了。而就是这,也让我们知道了,一个活动是从onCreate()开始。
onStart()
onStart()方法,是让一个活动由不可见变成可见必经之路。
onResume()
onResume()方法,就是我们在活动状态中提到的运行状态所调用的方法,处在栈顶,并且与用户交互。
onPause()
onPause()方法,就是在活动状态中提到的暂停状态所需要调用的方法。(见活动状态的暂停状态)
onStop()
onStop()方法,就是在活动状态中提到的停止状态所调用的方法。(见活动状态的停止状态)
onDestroy()
onDestroy()方法,在活动被销毁之前调用,之后的活动就处在了上文提到销毁状态。
onRestart()
onRestart()方法,在活动调用完onStop()以后,处于停止状态的时候,想要再次变为运行状态所调用的方法。
说完了7个方法,当然不能就这么草草了事,不然我自己都看不起我自己。下面就用实践来解释那个图:
public class LifeCycleActivity extends AppCompatActivity {
private static final String TAG="data";
private Button button_1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_life_cycle);
Log.d(TAG, "onCreate: LifeCycleActivity");
button_1=(Button)findViewById(R.id.button_1);
button_1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(LifeCycleActivity.this,TestActivity.class);
startActivity(intent);
}
});
}
@Override
protected void onStart() {
super.onStart();
Log.d(TAG, "onStart: LifeCycleActivity");
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume: LifeCycleActivity");
}
@Override
protected void onPause() {
super.onPause();
Log.d(TAG, "onPause: LifeCycleActivity");
}
@Override
protected void onStop() {
super.onStop();
Log.d(TAG, "onStop: LifeCycleActivity");
}
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: LifeCycleActivity");
}
@Override
protected void onRestart() {
super.onRestart();
Log.d(TAG, "onRestart: LifeCycleActivity");
}
}
代码很简单,就是重写了上面所提到的方法,并且将对应的状态打印出来,界面也很简单,就是一个按钮,用来跳转到一会儿需要用到的TestActivity。写完之后,开始就运行:
可以很明显的看到,运行程序之后,调用的方法依次是onCreate()->onStart()->onResume()。这也就非常好解释了,一个活动的开启首先就是要onCreate(),创建出来,之后由不可见的状态变为可见的状态就需要调用onStart()方法,活动可见之后当然也就是要运行了,所以就调用了onResume()方法。之后,我们在按一下back键:
发现,程序的方法依次是onPause()->onStop()->onDestroy(),活动在按了back键以后,意味着将移出栈顶,那么首先要调用的就肯定是onPause()。其实可以想象一下,我们在主屏幕上,进行左右翻页的时候,当你的手滑到了一半,旧的那一页占据了屏幕的一半,新的那一页占据了屏幕的一半,此时就是活动的暂停状态,所以调用的就是onPause()方法,然后当你全部滑过去以后,旧的活动就变得完全不可见了,那么调用的就是onStop()方法,最后调用onDestroy(),意味着一个活动的销毁。此后,如果还想启动该活动,那么就需要重新onCreate()。
剩下还有一个onRestart方法没有被用到,现在我们重新创建一个活动,名叫TestActivity,让原先的活动LifeCycleActivity中的按钮跳转到该活动,代码很简单,几乎没有要手写的,就不贴了,那么就直接操作,先通过按钮跳转到TestActivity,之后按back键回到LifeCycleActivity,再来看LifeCycleActivity所调用的方法:
通过之前的描述,我们知道当一个活动被覆盖了,就会调用onStop()方法。现在我们把被覆盖的活动(也就是LifeCycleActivity)重新取出来,看到调用的方法依次是:onRestart()->onStart()->onResume(),终于看到onRestart()方法了,原来它是在当一个活动被覆盖之后,重回天日的方法。
上面的那张Activity生命周期图解释得已经差不过了,还有两条路径没有讲到,一条就是杀死进程,这个实现起来有点困难,毕竟那是人家系统做的决定。但是没办法,就是必须要看,那自然也是有方法来实现,按下Home键以后,让原来的活动挂起,也就是处于停止状态,调用onStop(),但是没有被销毁,之后大量打开了手机手机的App,大约开了7、8个以后,再去从任务管理器中找到原来的活动,发现真的被系统kill了,因为通过Log日志看到打印出来的是onCreate(),而不是onRestart(),看来倔强还是有好处的的。另外一条是通过调用完onPause()方法直接到onResume()方法,这个其实比较容易实现,只需在AndroidManifest.xml中注册一下:
<activity android:name=".TestActivity"
android:theme="@android:style/Theme.Dialog" >
</activity>
或者是
<activity android:name=".TestActivity"
android:theme="@style/Theme.AppCompat.Dialog">
</activity>
我们操作是,将TestActivity变成一个主题是对话框式的活动,因为是对话框式的活动,所以不会占用整个屏幕,能够看到原活动。这里需要注意的是,主题是对话框式的活动,而不是一个对话框,如果仅仅是一个对话框,是不会让原活动调用onPause()方法的,因为对话框不是一种活动,虽然也是能产生不占据主屏幕的效果。所以这里用的是主题是对话框式的活动,具体参考链接描述。还有一点就是,我在上面写了两者添加主题的代码,这是因为现在我们用AS平台写代码的时候,创建一个活动,都会自动继承AppCompatActivity,而在这种情况下,我们要改变主题就需要使用第二种,如果写的是第一种形式,就会出现IllegalStateException这个异常。当然如果你非得使用第一种也行,只要让你的活动继承Activity就可以了。具体你可以参考链接描述。
通过在活动标签里写上主题之后,再次通过LifeCycleActivity的按钮跳转到TestActivity,就会:
在LifeCycleActivity调用了onPause()方法之后,TestActivity马上就调用了onCreate(),而LifeCycleActivity则不会再调用onStop()方法。
活动启动模式
活动的启动模式,说白了,就是你的活动是怎么被启动的,当你在不同情况下要用到这个Activity了,它是重新onCreate(),还是onRestart()了?
standard
特点:重复创建
standard是一种系统默认的活动启动模式,使用该启动模式,系统是不会在乎返回栈中是否存在这个活动,每次启动都会创建该活动的一个新的实例。当然,活动处在栈顶的时候也是一样,只要你再次启动了该活动,系统都会重新创建一个实例。下面我用一个实例做了演示,先上代码:
public class FirstLaunchModeActivity extends AppCompatActivity {
private Button button1;
private Button button2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_first_launch_mode);
Toast.makeText(FirstLaunchModeActivity.this,"FirstLaunchModeActivity is Created",Toast.LENGTH_SHORT).show();
button1=(Button)findViewById(R.id.launchMode_button1);
button2=(Button)findViewById(R.id.launchMode_button2);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(FirstLaunchModeActivity.this,FirstLaunchModeActivity.class);
startActivity(intent);
}
});
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent=new Intent(FirstLaunchModeActivity.this,SecondLaunchModeActivity.class);
startActivity(intent);
}
});
}
}
在FirstLaunchModeActivity活动中,定义了两个button按钮,然后在onCreate()方法下面我写了一个toast,可以使得每次创建FirstLaunchModeActivity时候,都打印一次,方便查看。这里暂时只用到第一个button,我在里面设置的监听事件是从FirstLaunchModeActivity跳转到FirstLaunchModeActivity,没错,就是自己调到自己,以此来说明,即使活动在栈顶,再次启动的时候还是会创建。下面是实现图:
可以看到,当我打开App的时候,FirstLaunchModeActivity创建了一次,我按下第一个按钮二次,活动被创建了两次。所以,standard启动模式就如上所示那样,不断重复创建。
singleTop
特点:对于栈顶活动不会重新创建,别的活动仍然和standard一样。
实现形式:
<activity android:name=".FirstLaunchModeActivity"
android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
singleTop启动模式,相比于standard启动模式有一点不同,就是只要这个活动是正在运行状态的,也就是正处于栈顶的,也就是调用了onResume()方法以后没有再调用别的方法,那么你想再次启动这个活动,系统不会创建该活动新的实例,依旧是你正在使用的活动,但是如果你想启动一个不是栈顶的活动,那么系统就会给你重新创建一个实例。
依旧是上面的代码,现在用到第二个按钮,跳转到SecondLaunchModeActivity,在SecondLaunchModeActivity中我也定义了一个按钮,可以跳转回到FirstLaunchModeActivity,同时也写了toast,方便查看。还是看效果图:
在打开App以后,因为是第一次打开,所以FirstLaunchModeActivity被创建了,然后我按了第一个按钮,可以没有任何提示信息,说明FirstLaunchModeActivity没有被再次创建,这也说明了在活动正在运行的时候,再次启动不会被创建,然后按了第二个按钮,跳转到SecondLaunchModeActivity,SecondLaunchModeActivity就会被创建,之后又跳转返回FirstLaunchModeActivity,可以看到的是FirstLaunchModeActivity又被创建了,所以,不在栈顶的活动,当你再次启动的时候就会被创建,无论你是否存在过。
singleTask
特点:只要返回栈中存在这个活动,系统都不会帮你创建该活动新的实例。
<activity android:name=".FirstLaunchModeActivity"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
如果说singleTop是在standard的基础之上,节省了资源,那么singleTask在singleTop的基础之上更加节省了资源,降低了内存的消耗。singleTask模式,使得只要这个活动存在返回栈里面,那么就不会再去创建该活动新的实例,无论它在这个栈中埋得多深,都将被拉出来。当然,被拉出来的前提是在该活动上面的所有活动都会被移出返回栈,而如果这些被移除的活动想重新被启动的话,只能再次创建了。这次代码还是一样,只是我把SecondLaunchModeActivity的启动模式也改成了singleTask,效果图如下:
打开App还是首先创建了一个FirstLaunchModeActivity,然后按在第二个按钮之后,创建SecondLaunchModeActivity,之后,当按下按钮跳转回到FirstLaunchModeActivity的时候,屏幕没有显示,说明FirstLaunchModeActivity在返回栈,即使不是在栈顶也会被启动,接着,我又按了跳转到SecondLaunchModeActivity的按钮,发现屏幕显示SecondLaunchModeActivity被创建,这也就说明,我们刚才在启动FirstLaunchModeActivity的时候,把它上面的活动都移出了返回栈,这也就导致了SecondLaunchModeActivity必须重新创建。
singleInstance
特点:活动单独存储在一个栈里面,别的应用程序都可以来访问,共享活动实例。
<activity android:name=".SecondLaunchModeActivity"
android:launchMode="singleInstance">
</activity>
singleInstance启动模式,和上面的三种有那么些许的区别。如果一个活动被定义成了singleInstance,那么就会开启一个新的返回栈来管理这个活动,我们可以把返回栈比作一套房子,那么singleInstance就是房产证,你有了singleInstance,就是拥有了房产证,就有了新的房子,而不需要在以前的房子住着了。但是你虽然有了新的房子,房子却是其他所有"人"合资给你买的,你自己买不起,这样的后果就是,别人要向你借东西,你就要借给别人,也就是你的东西是共享的。但是你的房子不能给别人住,因为别人既然已经给你买了,那就是你的了,他们也不好意思住。返回到活动,就是singleInstance启动模式,可以让活动变成共享活动,任何App想要启动该活动,都不会新创建该活动的实例,而是来访问这个单独栈的活动。
这次为了方便观看效果图,我多加了两个Activity,分别是ThirdLaunchModeActivity,ForthLaunchModeActivity,名字有点长,但是好记,多多包涵。每个Activity里面的都是只有一个button按钮,对应的监听事件是从该活动跳转到下一个活动。把SecondLaunchModeActivity和ThirdLaunchModeActivity的启动方式变成singleInstance,并且在每一个活动中通过toast打印出对应的taskId:
从第一个界面到第四个界面,我按了2遍,可以发现第一遍的时候,第一个界面显示的taskId是192,第二个是193,第三个是194,第四个是192,这是因为我在SecondLaunchModeActivity和ThirdLaunchModeActivity中将启动模式变成了singleInstance,而他们对应的刚好就是第二三个界面,剩下的两个都是默认的standard启动模式,那么也就在同一个返回栈中,当我按第二遍的时候,发现第一个界面和第四个界面,重新打印了一遍,而二三没有,那是因为standard模式当你启动的时候就要调用onCreate()方法,而singleInstance模式我们刚才说了不会重新创建。下面还有一点就是,按back键返回的时候,也会有点不同:
我再次从第一个界面按到了第四个,之后通过back键返回到主界面,可以看到,返回的时候,界面直接从第四个跳到了第一个,之后是第三个,第二个,这是因为我从第四个界面开始跳,第四个界面在按了back键以后就会从返回栈中移出,剩下该返回栈中就只剩下了第一个活动界面,当然会先将它移出,之后才会“移步”到别的栈中,相继删除其它栈中的活动。
活动启动模式总结
上面描述了这么多,最后对于启动模式概括性的来一点,现在就需要通过启动模式中的图来说了(图片来自网络)。
standard启动模式:无论活动在返回栈中的位置如何,只要启动就重新创建实例。
singleTop启动模式:只有当活动在栈顶的时候,不会重新创建实例,别的情况同standard模式。
singleTask启动模式:只要返回栈中存在这个活动,就不会创建实例,如果不存在,那就肯定会被创建啦。
singleInstance启动模式:最复杂的一种,创建了的活动,就会启用一个单独的返回栈来管理这个活动,别的应用程序想要启动该活动,只需访问就可以了。
以上就是所有我对Activity的生命周期与启动模式的理解,如有不对的地方,欢迎交流!