在上一篇文章《安卓开发笔记——重识Activity 》中,我们了解了Activity生命周期的执行顺序和一些基本的数据保存操作,但如果只知道这些是对于我们的开发需求来说是远远不够的,今天我们继续探索Activity,来了解下关于Activity任务栈和Activity四种启动模式的区别。

  为什么需要了解关于Activity的任务栈,其实最直接的体现就是提高用户交互友好性。

  举个例子,当我们去浏览一个新闻客户端的时候,我们进入了新闻详情页,在这个页面有相隔两条的新闻标题,当我们去点击这个标题的时候进入了新的新闻详情页时,如果我们不加以控制会导致什么现象?它会创建出n个新闻详细页的Activity实例,导致用户在退出的时候需要推出多个新闻详情activity,这点在用户体验上是非常不好的,当然对于我们自身的程序也是非常不好的,不断的去创建新的Activity必定会消耗一定的内存,久而久之,应用程序会越来越卡甚至崩溃。在我之前写过的博文《安卓开发笔记——打造属于自己的博客园APP(四) 》中也应用到了这一知识点。

  

在讲Activity任务栈前,我们应该先知道什么是栈?

  简单点来理解,可以把栈比作一个开封的箱子,我们可以往里面塞东西,这里假设塞的东西的底面积和箱子的底面积是相同的,那么这些东西就具备有从下往上一定的顺序,当我们想要取出箱子里面的东西时,我们没有办法一下子拿到箱子最底层的东西,我们只能拿到最上面一层的东西,从上往下。

  来看下这张图,这里的箱子就是栈,箱子口可以看作是栈的入口与出口,东西代表数据。栈的特点:具有一定的次序,后进先出(越先放入的东西,越晚出来)。

1、Activity任务栈  

 好了,在了解了什么是栈之后,我们可以开始进入今天的主题了,在Android的官方文档描述中我们可以知道,任务栈也是栈,具有栈的一切特点。

  Activity任务栈,顾名思义是存放Activity任务的栈,这里的任务栈为上图箱子,Activity为上图的东西。

  当我们每打开一个Activity的时候它会就往Activity任务栈中压入一个Activity,当我们每销毁一个Activity的时候它会从Activity任务栈中弹出一个Activity,由于安卓系统自身的设计,我们只能在手机屏幕上获取当前一个Activity的焦点即栈顶元素(最上面的Activity),其余的Activity会暂居后台等待系统调用。

1.1、关于任务栈的概念:

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

1.2、关于任务栈的缺点:

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

2、Activity的4种启动方式

  为了解决任务栈产生的问题,Android为Activity设计了启动模式,那么下面的内容将介绍Android中Activity的启动模式,这也是最重要的内容之一。

  启动模式(launchMode)在多个Activity跳转的过程中扮演着重要的角色,它可以解决是否生成新的Activity实例,是否重用已经存在的Activity实例,是否和其他实例共用一个任务栈。任务栈是一个具有栈结构的对象,一个任务栈可以管理多个Activity,每启动一个应用,也就创建一个与之对应的任务栈。

  Activity一共有以下四种launchMode模式:1、standard 2、singTop 3、singTask 4、singleInstance,我们可以在AndroidManifest.xml配置<activity>的android:launchMode属性为以上四种之一即可。
2.1、实践是检验真理的唯一标准
 下面写个实例,有2个Activity,每个Activity都有一个跳转按钮,第一个Activity点击按钮跳转第二个Activity,第二个Activity点击按钮跳转自身。
 package com.lcw.rabbit.activitydemo;

 import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button; public class MainActivity extends Activity { private static final String TAG = "Rabbit"; private Button mbButton; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); Log.i(TAG,"第一个Activity,加入任务栈:"+getTaskId()); //点击按钮跳转第二个Activity
mbButton = (Button) findViewById(R.id.bt_button);
mbButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
}); } @Override
protected void onDestroy() {
super.onDestroy();
Log.i(TAG, "第一个Activity,退出任务栈:" + getTaskId());
}
}

MainActivity.java

     package com.lcw.rabbit.activitydemo;

     import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button; public class SecondActivity extends Activity { private static final String TAG = "Rabbit";
private Button mbButton1;
private Button mbButton2; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Log.i(TAG, "第二个Activity,加入任务栈:" + getTaskId());
//点击按钮跳转第二个Activity
mbButton1 = (Button) findViewById(R.id.bt_button1);
mbButton1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(SecondActivity.this, MainActivity.class));
}
});
mbButton2 = (Button) findViewById(R.id.bt_button2);
mbButton2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(SecondActivity.this, SecondActivity.class));
}
});
} @Override
protected void onDestroy() {
super.onDestroy();
Log.i(TAG, "第二个Activity,退出任务栈:" + getTaskId());
}
}

SecondActivity.java

  

实验一:启动模式standard  

  系统默认的Activity启动模式是standard,我们这里为了检验再设置一下:

         <activity
android:name=".MainActivity"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:launchMode="standard"></activity>

  现在我们进入第一个Activity的时候,点击按钮启动第二个Activity,看下当前任务栈:

  当我们点击第二个Activity的按钮,让它跳转自身,看下当前任务栈:

  当我们按下Back键,跳转第一个Activity,销毁第二个Activity时,看下当前任务栈:

  可以发现,这个任务栈和我们刚上图描述的箱子(Activity任务栈)是一致的,从上往下放东西(Activity),越晚放进去的东西(Activity)在越上面,而后面的t1072则代表当前任务栈的编号ID,ID相同代表它们属于同一个任务栈。

  我们从日志文件中也可以看得很清楚:

实验一结论:

  在Activity启动模式为standard(默认)的情况下,不管之前有没有Activity实例,每一次启动Activity都会创建一个新的Activity实例,并置于Activity任务栈栈顶。

实验二:启动模式singleTop

        <activity
android:name=".MainActivity"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:launchMode="singleTop"></activity>

  现在我们进入第一个Activity点击按钮跳转第二个Activity,然后再点击按钮跳转自身,看下当前任务栈:

  系统日志文件:

  然后我们再点击按钮启动第一个Activity,然后点击按钮启动第二个Activity,看下当前任务栈:

  系统日志:

实验二结论:

  在Activity启动模式为singleTop(栈顶任务唯一)的情况下,如果当前Activity处于栈顶,那么它就不会再去实例化一个新的Activity,当Activity不处于栈顶的时候,会重新实例化一个新的Activity并置于栈顶,此时的任务栈编号为1080。

实验三:启动模式singleTask

         <activity
android:name=".MainActivity"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:launchMode="singleTask"></activity>

  当我们进入第一个Activity点击进入第二个,再启动自身,看下当前任务栈:

  系统日志:

  现在我们点击启动第一个Activity,再点击启动第二个Activity,看下当前任务栈:

  系统日志:

实验三结论:

  在Activity启动模式为singleTask(唯一实例)的情况下,当启动Activity的时候,如果当前Activity不存在则实例化一个新的Activity,如果当前Activity在任务栈中已经存在,则会复用这个Activity实例,但这边我们从日志打印可以看出在启动第二个Activity的时候,第一个Activity推出了任务栈,也就意味着当启动模式为singTask的时候,启动已经存在在Activity任务栈中但不在栈顶的Activity时,该Activity会把压在它前面的所有Activity弹出任务栈,此时任务栈编号为1081,属于同一个任务栈。

实验四:启动模式singleInstance

         <activity
android:name=".MainActivity"
android:launchMode="standard">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SecondActivity"
android:launchMode="singleInstance"></activity>

  现在我们进入第一个Activity点击按钮跳转第二个Activity,再让其跳转自身,看下当前任务栈:

  系统日志:

  现在点击按钮启动第一个Activity,再点击按钮启动第二个Activity,看下当前任务栈:

  系统任务:

  点击Back键,直到程序完全退出,看下系统日志:

实验四结论:

  在Activity启动模式为singleInstance的情况下,首先我们可以发现的是启动模式为singleInstance的Activity处于不同的任务栈(Task编号不同),并保证不再有其他的Activity实例进入,它还是和singleTask一样保持唯一实例,然后它的退出顺序是不再是根据调用顺序,而是在不同的任务栈中,从上往下退出。

在共用一个Activity实例时,期间发生了什么?

  在上诉模式中,当我们的Activity涉及到同一实例的时候,期间Activity做了哪些事情?在Android官方文档中我们可以知道期间虽然没有新实例化一个Activity,但是调用了onNewIntent方法。

  现在我们在第二个Activity里添加一个onNewIntent方法:

         @Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.i(TAG, "第二个Activity,执行onNewIntent");
}

1、在standard(默认)启动模式下,我们来回的去跳转Activity,看下日志打印,发现是不会调用onNewIntent方法的,因为它不是一个实例。

2、在singleTop模式下,我们从第一个Activity跳转到第二个Activity,再从第二个Activity跳转自身,再跳转第一个Activity,看下日志打印,我们可以发现,当第二个Activity置于栈顶的时候,由于重用了实例,所以调用了onNewIntent方法。

3、当singleTask和singleInstance模式下也是一样的,因为重用了实例,所以会调用onNewIntent方法,且onNewIntent方法是在前一个Activity的onStop方法后(当前ActivityonReStart方法前)立即调用的。

好了,今天先写到这里,有什么建议或疑问,可以在文章评论给我留言。

作者:李晨玮
出处:http://www.cnblogs.com/lichenwei/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。
正在看本人博客的这位童鞋,我看你气度不凡,谈吐间隐隐有王者之气,日后必有一番作为!旁边有“推荐”二字,你就顺手把它点了吧,相得准,我分文不收;相不准,你也好回来找我!

安卓开发笔记——深入Activity的更多相关文章

  1. 安卓开发笔记——自定义广告轮播Banner(实现无限循环)

    关于广告轮播,大家肯定不会陌生,它在现手机市场各大APP出现的频率极高,它的优点在于"不占屏",可以仅用小小的固定空位来展示几个甚至几十个广告条,而且动态效果很好,具有很好的用户& ...

  2. 安卓开发笔记——丰富多彩的TextView

    随手笔记,记录一些东西~ 记得之前写过一篇文章<安卓开发笔记——个性化TextView(新浪微博)>:http://www.cnblogs.com/lichenwei/p/4411607. ...

  3. 安卓开发笔记——关于开源项目SlidingMenu的使用介绍(仿QQ5.0侧滑菜单)

    记得去年年末的时候写过这个侧滑效果,当时是利用自定义HorizontalScrollView来实现的,效果如下: 有兴趣的朋友可以看看这篇文件<安卓开发笔记——自定义HorizontalScro ...

  4. 安卓开发笔记——打造万能适配器(Adapter)

    为什么要打造万能适配器? 在安卓开发中,用到ListView和GridView的地方实在是太多了,系统默认给我们提供的适配器(ArrayAdapter,SimpleAdapter)经常不能满足我们的需 ...

  5. 安卓开发笔记——关于Handler的一些总结(上)

    接上篇文章<安卓开发笔记——关于AsyncTask的使用>,今天来讲下在安卓开发里"重中之重"的另一个异步操作类Handler. 今天打算先讲下关于Handler的一些 ...

  6. 安卓开发笔记——Fragment+ViewPager组件(高仿微信界面)

    什么是ViewPager? 关于ViewPager的介绍和使用,在之前我写过一篇相关的文章<安卓开发复习笔记——ViewPager组件(仿微信引导界面)>,不清楚的朋友可以看看,这里就不再 ...

  7. 安卓开发笔记——TabHost组件(二)(实现底部菜单导航)

    上面文章<安卓开发复习笔记——TabHost组件(一)(实现底部菜单导航)>中提到了利用自定义View(ImageView+TextView)来设置一个底部菜单的样式 这边再补充一种更为灵 ...

  8. 安卓开发笔记——TabHost组件(一)(实现底部菜单导航)

    什么是TabHost? TabHost组件的主要功能是可以进行应用程序分类管理,例如:在用户使用windows操作系统的时候,经常见到如图所示的图形界面.     TabHost选项卡,说到这个组件, ...

  9. 安卓开发笔记——关于照片墙的实现(完美缓存策略LruCache+DiskLruCache)

    这几天一直研究在安卓开发中图片应该如何处理,在网上翻了好多资料,这里做点小总结,如果朋友们有更好的解决方案,可以留言一起交流下. 内存缓存技术 在我们开发程序中要在界面上加载一张图片是件非常容易的事情 ...

随机推荐

  1. EF Code First Migration总结

    开启Migration 1. 通过 Tools->Nuget Package Manager->Package Manager Console 打开Package Manager Cons ...

  2. Qt 5.3.1 版本应用程序的发布问题

    问题描述:用过Qt的朋友,都知道,完成的Qt程序,只能在QT环境里运行.在debug环境里,没有配置环境路线的情况下,必须包含多个dll库,然而每个dll库的大小确实很大的.但有时候还是会失败的,在一 ...

  3. Gaussian分布下Hinge损失的期望

    SVM的标准形式是\begin{align*} \min_{\boldsymbol{w}} \ \ \ \frac{\lambda}{2} \|\boldsymbol{w}\|^2 + \frac{1 ...

  4. Atitit. C# java 的api 目录封装结构映射总结

    Atitit. C#  java 的api 目录封装结构映射总结 C# java ref System.Reflection System.Type, java.lang.ref concurrent ...

  5. atitit.重装系统需要备份的资料总结 o84..

    atitit.重装系统需要备份的资料总结 o84.. 这里我的系统装在C盘..所以需要备份C盘的东西就好了.. 1.DESKTOP,这个目录要备份.如果重要资料 2.docume nt,这个需要..W ...

  6. mysql闪退或者can not connect 127.0.0.1

    MYSQL 无安装文件 exe执行时闪退 mysql闪退或者can not connect 127.0.0.1 APP 百款主流机型兼容性免费测 »   Mysql  官网上下载的Mysql 但是没有 ...

  7. Leetcode 130 Surrounded Regions DFS

    将内部的O点变成X input X X X XX O O X X X O XX O X X output X X X XX X X XX X X XX O X X DFS的基本框架是 void dfs ...

  8. 转:最简单的视频网站(JavaEE+FFmpeg)

    本文记录一个最简单的视频网站系统.此前做过一些基于JavaEE中的 SSH (Strut2 + Spring + Hibernate)的网站系统,但是一直没有做过一个视频网站系统,所以就打算做一个&q ...

  9. Python:将utf-8格式的文件转换成gbk格式的文件

    需求:将utf-8格式的文件转换成gbk格式的文件 实现代码如下: def ReadFile(filePath,encoding="utf-8"): with codecs.ope ...

  10. linux下redis设置密码登录

    redis设置密码访问 你的redis在真是环境中不可以谁想访问就可以访问,所以必须要设置密码 设置密码的流程如下: vim  /etc/redis.conf #requirepass foobare ...