在Android应用程序中,可以配置Activity以四种方式来启动,其中最令人迷惑的就是"singleTask"这种方式了,官方文档称以这种方式启动的Activity总是属于一个任务的根Activity。果真如此吗?本文将为你解开Activity的"singleTask"之谜。

在解开这个谜之前,我们先来简单了解一下在Android应用程序中,任务(Task)是个什么样的概念。我们知道,Activity是Android应用程序的基础组件之一,在应用程序运行时,每一个Activity代表一个用户操作。用户为了完成某个功能而执行的一系列操作就形成了一个Activity序列,这个序列在Android应用程序中就称之为任务,它是从用户体验的角度出发,把一组相关的Activity组织在一起而抽象出来的概念。

对初学者来说,在开发Android应用程序时,对任务的概念可能不是那么的直观,一般我们只关注如何实现应用程序中的每一个Activity。事实上,Android系统中的任务更多的是体现是应用程序运行的时候,因此,它相对于Activity来说是动态存在的,这就是为什么我们在开发时对任务这个概念不是那么直观的原因。不过,我们在开发Android应用程序时,还是可以配置Activity的任务属性的,即告诉系统,它是要在新的任务中启动呢,还是在已有的任务中启动,亦或是其它的Activity能不能与它共享同一个任务,具体配置请参考官方文档:

http://developer.android.com/guide/topics/fundamentals/tasks-and-back-stack.html

它是这样介绍以"singleTask"方式启动的Activity的:

The system creates a new task and instantiates the activity at the root of the new task. However, if an instance of the activity already exists in a separate task, the system routes the intent to the existing instance through a call to its onNewIntent() method, rather than creating a new instance. Only one instance of the activity can exist at a time.

它明确说明,以"singleTask"方式启动的Activity,全局只有唯一个实例存在,因此,当我们第一次启动这个Activity时,系统便会创建一个新的任务,并且初始化一个这样的Activity的实例,放在新任务的底部,如果下次再启动这个Activity时,系统发现已经存在这样的Activity实例,就会调用这个Activity实例的onNewIntent成员函数,从而把它激活起来。从这句话就可以推断出,以"singleTask"方式启动的Activity总是属于一个任务的根Activity。

但是文档接着举例子说明,当用户按下键盘上的Back键时,如果此时在前台中运行的任务堆栈顶端是一个"singleTask"的Activity,系统会回到当前任务的下一个Activity中去,而不是回到前一个Activity中去,如下图所示:

真是坑爹啊!有木有!前面刚说"singleTask"会在新的任务中运行,并且位于任务堆栈的底部,这里在Task B中,一个赤裸裸的带着"singleTask"标签的箭头无情地指向Task B堆栈顶端的Activity Y,刚转身就翻脸不认人了呢!

狮屎胜于熊便,我们来做一个实验吧,看看到底在启动这个"singleTask"的Activity的时候,它是位于新任务堆栈的底部呢,还是在已有任务的顶部。

首先在Android源代码工程中创建一个Android应用程序工程,名字就称为Task吧。关于如何获得Android源代码工程,请参考在Ubuntu上下载、编译和安装Android最新源代码一文;关于如何在Android源代码工程中创建应用程序工程,请参考在Ubuntu上为Android系统内置Java应用程序测试Application Frameworks层的硬件服务一文。这个应用程序工程定义了一个名为shy.luo.task的package,这个例子的源代码主要就是实现在这里了。下面,将会逐一介绍这个package里面的文件。

应用程序的默认Activity定义在src/shy/luo/task/MainActivity.java文件中:

        
  1. packageshy.luo.task;
  2. importandroid.app.Activity;
  3. importandroid.content.Intent;
  4. importandroid.os.Bundle;
  5. importandroid.util.Log;
  6. importandroid.view.View;
  7. importandroid.view.View.OnClickListener;
  8. importandroid.widget.Button;
  9. publicclassMainActivityextendsActivityimplementsOnClickListener{
  10. privatefinalstaticStringLOG_TAG="shy.luo.task.MainActivity";
  11. privateButtonstartButton=null;
  12. @Override
  13. publicvoidonCreate(BundlesavedInstanceState){
  14. super.onCreate(savedInstanceState);
  15. setContentView(R.layout.main);
  16. startButton=(Button)findViewById(R.id.button_start);
  17. startButton.setOnClickListener(this);
  18. Log.i(LOG_TAG,"MainActivityCreated.");
  19. }
  20. @Override
  21. publicvoidonClick(Viewv){
  22. if(v.equals(startButton)){
  23. Intentintent=newIntent("shy.luo.task.subactivity");
  24. startActivity(intent);
  25. }
  26. }
  27. }

它的实现很简单,当点击它上面的一个按钮的时候,就会启动另外一个名字为“shy.luo.task.subactivity”的Actvity。
名字为“shy.luo.task.subactivity”的Actvity实现在src/shy/luo/task/SubActivity.java文件中:

        
  1. packageshy.luo.task;
  2. importandroid.app.Activity;
  3. importandroid.os.Bundle;
  4. importandroid.util.Log;
  5. importandroid.view.View;
  6. importandroid.view.View.OnClickListener;
  7. importandroid.widget.Button;
  8. publicclassSubActivityextendsActivityimplementsOnClickListener{
  9. privatefinalstaticStringLOG_TAG="shy.luo.task.SubActivity";
  10. privateButtonfinishButton=null;
  11. @Override
  12. publicvoidonCreate(BundlesavedInstanceState){
  13. super.onCreate(savedInstanceState);
  14. setContentView(R.layout.sub);
  15. finishButton=(Button)findViewById(R.id.button_finish);
  16. finishButton.setOnClickListener(this);
  17. Log.i(LOG_TAG,"SubActivityCreated.");
  18. }
  19. @Override
  20. publicvoidonClick(Viewv){
  21. if(v.equals(finishButton)){
  22. finish();
  23. }
  24. }
  25. }

它的实现也很简单,当点击上面的一个铵钮的时候,就结束自己,回到前面一个Activity中去。

再来看一下应用程序的配置文件AndroidManifest.xml:

        
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <manifestxmlns:android="http://schemas.android.com/apk/res/android"
  3. package="shy.luo.task"
  4. android:versionCode="1"
  5. android:versionName="1.0">
  6. <applicationandroid:icon="@drawable/icon"android:label="@string/app_name">
  7. <activityandroid:name=".MainActivity"
  8. android:label="@string/app_name">
  9. <intent-filter>
  10. <actionandroid:name="android.intent.action.MAIN"/>
  11. <categoryandroid:name="android.intent.category.LAUNCHER"/>
  12. </intent-filter>
  13. </activity>
  14. <activityandroid:name=".SubActivity"
  15. android:label="@string/sub_activity"
  16. android:launchMode="singleTask">
  17. <intent-filter>
  18. <actionandroid:name="shy.luo.task.subactivity"/>
  19. <categoryandroid:name="android.intent.category.DEFAULT"/>
  20. </intent-filter>
  21. </activity>
  22. </application>
  23. </manifest>

注意,这里的SubActivity的launchMode属性配置为"singleTask"。
再来看界面配置文件,它们定义在res/layout目录中,main.xml文件对应MainActivity的界面:

        
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. android:gravity="center">
  7. <Button
  8. android:id="@+id/button_start"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:gravity="center"
  12. android:text="@string/start">
  13. </Button>
  14. </LinearLayout>

而sub.xml对应SubActivity的界面:

        
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3. android:orientation="vertical"
  4. android:layout_width="fill_parent"
  5. android:layout_height="fill_parent"
  6. android:gravity="center">
  7. <Button
  8. android:id="@+id/button_finish"
  9. android:layout_width="wrap_content"
  10. android:layout_height="wrap_content"
  11. android:gravity="center"
  12. android:text="@string/finish">
  13. </Button>
  14. </LinearLayout>

字符串文件位于res/values/strings.xml文件中:

        
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <resources>
  3. <stringname="app_name">Task</string>
  4. <stringname="sub_activity">SubActivity</string>
  5. <stringname="start">StartsingleTaskactivity</string>
  6. <stringname="finish">Finishactivity</string>
  7. </resources>

最后,我们还要在工程目录下放置一个编译脚本文件Android.mk:

        
  1. LOCAL_PATH:=$(callmy-dir)
  2. include$(CLEAR_VARS)
  3. LOCAL_MODULE_TAGS:=optional
  4. LOCAL_SRC_FILES:=$(callall-subdir-java-files)
  5. LOCAL_PACKAGE_NAME:=Task
  6. include$(BUILD_PACKAGE)

这样,原材料就准备好了,接下来就要编译了。有关如何单独编译Android源代码工程的模块,以及如何打包system.img,请参考如何单独编译Android源代码中的模块一文。
执行以下命令进行编译和打包:

        
  1. USER-NAME@MACHINE-NAME:~/Android$mmmpackages/experimental/Task
  2. USER-NAME@MACHINE-NAME:~/Android$makesnod

这样,打包好的Android系统镜像文件system.img就包含我们前面创建的Task应用程序了。

更多相关文章

  1. Android(安卓)iOS测试区别
  2. 关于android开机速度性能方面
  3. Android内核开发:学会分析系统的启动log
  4. [Android(安卓)L]SEAndroid增强Androd安全性背景概要及带来的影
  5. Android(安卓)判断应用 第一次启动
  6. Android(安卓)AlarmManager实现定时任务(也就是闹钟) 附Demo源码
  7. Android(安卓)启动时 出现黑屏
  8. [日更-2019.4.22、23、24] cm-14.1 Android系统启动过程分析(三)-S
  9. Android(安卓)渗透测试学习手册(三)Android(安卓)应用的逆向和审计

随机推荐

  1. android 音效
  2. android添加各种权限整理
  3. 【OOM】Android加载大图片OOM异常解决
  4. Android Fragment页打开相册
  5. net :: ERR_CLEARTEXT_NOT_PERMITTED
  6. checkbox style
  7. 【Android】详解Android动画之Interpolat
  8. 完成android的manven项目管理
  9. Android - 解决onSaveInstanceState的Bug
  10. 常用Android系统调用