在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工程中不自动生成Android Dependencies的解决方式
  2. Android ActionBar的源代码分析(二)
  3. 【转】创建和使用Android library工程
  4. Android应用程序注冊广播接收器(registerReceiver)的过程分析

随机推荐

  1. 使用 Xtrabackup 在线对MySQL做主从复制
  2. 阿里云服务器忘记mysql的登录密码时候如
  3. 如何从MySQL DBs的不同表中提取create语
  4. 装机建项目vs2017和mysql5.7下建项目用EF
  5. 是一个mysql临时表,每个用户访问创建它的
  6. MySQL 一对多查询
  7. 中文乱码问题
  8. 可以在SELECT语句中嵌入描述语法吗?
  9. MySQL判断索引存在并删除索引的存储过程
  10. Linux上跑MySQL优化技巧