前言:
android:process=":remote",代表在应用程序里,当需要该service时,会自动创建新的进程。而如果是android:process="remote",没有“:”分号的,则创建全局进程,不同的应用程序共享该进程。
原文地址:http://www.cnblogs.com/franksunny/archive/2012/04/17/2453403.html
很想弄清楚启动一个Activity和Task(任务)的关系,网上也有很多相关资料,由从源码来具体分析的,也有针对launchmode来分析,但都不是自己的,理解起来总不是那么容易,为此,尝试着自己去理解其中的逻辑。不过事先需要弄清楚两个问题:
谁负责管理Activity?
Android的framework框架中采用C/S的方式实现由后台服务ActivityManagerService(很多书上将其简称为Ams)来管理具体的Acitivity实例,虽然Ams命名为ActivityManagerService,但是它其实不仅仅管理Activity,他还管理除Acitvity外的其它三大组件,还包括Intent、pendingintent、apk进程和task等等,具体可以查看源码frameworks\base\services\java\com\android\server\am下面的Ams源码和相关的*Record类。
网上资料显示在2.3以后的SDK中,Ams将原来的HistoryRecord类重新命名为ActivityRecord类,并将其原来Ams中的一些处理抽出来成为一个ActivityStack类,每一个Activity实例对应一个ActivityRecord对象,并存放在ActivityStack中,手机一旦启动,就会启动一个Ams服务,在Ams服务中有一个ActivityStack实例专门管理手机上的ActivityRecord实例。这样一来,不具体分析源码,仅从架构角度来说谁负责管理Activity的问题就清晰多了。
Task到底是什么?
根据对上面问题的解答,既然Activity是由Ams通过ActivityStack来管理的,那么这个Task又是干什么的呢?
以往基于应用(application)的程序开发中,程序具有明确的边界,一个程序就是一个应用,一个应用为了实现功能可以采用开辟新线程甚至新进程来辅助,但是应用与应用之间不能复用资源和功能。而Android引入了基于组件开发的软件架构,虽然我们开发android程序,仍然使用一个apk工程一个Application的开发形式,但是对于Aplication的开发就用到了Activity、service等四大组件,其中的每一个组件,都是可以被跨应用复用的哦,这个就是android的神奇之处。
另外值得一提的是,虽然组件可以跨应用被调用,但是一个组件所在的进程必须是在组件所在的Aplication进程中。由于android强化了组件概念,弱化了Aplication的概念,所以在android程序开发中,A应用的A组件想要使用拍照或录像的功能就可以不用去针对Camera类进行开发,直接调用系统自带的摄像头应用(称其B应用)中的组件(称其B组件)就可以了,但是这就引发了一个新问题,A组件跑在A应用中,B组件跑在B应用中,自然都不在同一个进程中,那么从B组件中返回的时候,如何实现正确返回到A组件呢?Task就是来负责实现这个功能的,它是从用户角度来理解应用而建立的一个抽象概念。因为用户所能看到的组件就是Activity,所以Task可以理解为实现一个功能而负责管理所有用到的Activity实例的栈。
其实查看源码,在Ams内部,并不是真的有这么一个存放Activity的Task栈,Activity还是通过ActivityStack来管理,在ActivityRecord中有一个TaskRecord对象记录了真实的Activity实例是属于哪个Task的。Task通过一个int类型的TaskId来唯一标识,该值在手机重启时将会被置零。
说了这么多,还是找一个Task任务最直观的体现吧。先重启手机,长按home键,发现弹出的最近任务中一个任务也没有,然后开启A应用,长按home键,会发现有一个A应用的任务,查看手机进程,应该还没有B进程的;在A应用的A组件中调B应用的B组件,此时看手机的进程,除了A进程外,还有个B的进程,但是长按home键,能看到的还是只有一个A应用的任务。其实这个时候,B应用已经跑起来了,但是对用户来说,他其实没有开启过B应用,所以Task任务自始至终都是从用户的角度出发而设计的概念,保证用户的调用逻辑。
Activity、Application与进程的关系
理清楚了上述两个概念问题,进入Activity的启动与Task的关系之前,让我们先来了解下启动一个Activity与apk Application和进程的关系。对Activity启动过程具体的分析,工程耗时很庞大,网上有个老罗整理的三篇博客,我看了半天还是云里雾里的,有兴趣的可以查看如下链接:http://blog.csdn.net/luoshengyang/article/details/6685853。我通过我现在的认识和目前通过Demo的测试来看,启动一个Activity时,Ams首先会去查询该Activity所在的应用是否开启,如果没有开启则会启动一个进程去Run这个Application,因此无论通过Launcher还是通过常规的程序内部调用startActivity来启动一个Activity,所要启动的Activity都是跑在其注册的apk所在的Application进程中(或者该组件android:process指定的进程中),而其TaskID一般是和启动它的组件所属的TaskId一直,但是也不尽然,这就要看下面的具体分析了。
另外通过对service的应用,可以得出结论,一个apk,即一个应用(Application)可以跑在多个进程中,一个进程在一个虚拟机中运行,也即一个apk可以启动多个虚拟机。
通过shareuserID可以将多个apk,跑在同一个进程中。
从而得出结论:一个虚拟机只能跑一个进程,一个进程里可以跑多个应用,一个应用也可以跑在多个进程中,这就是他们的关系。
Activity和Task的关系
启动一个Activity有两种方式,一种就是通过Launcher,另外一种是通过程序代码调用startActivity函数实现(验证过AppWidget其实与这种方式是一致的)其实影响Activity启动关键点大致有三个因素:Activity注册信息中的launchMode、启动Activity时Intent中的launchFlags和Activity注册信息中的taskAffinity、allowTaskReparenting、clearTaskOnLaunch、alwaysRetainTaskState、finishOnTaskLaunch等信息。通常情况下,我们只需要针对第一个因素进行合理设置就能满足我们应用开发的需求了。
launchMode的四种方式
launchMode方法是在apk的manifest文件中针对每一个Activity的android:launchMode属性进行设置的方式,共有四种模式可以设置,分别是standard、singleTop、singleTask、singleInstance,下面分别阐述之(由于其中几种因素有设置时,会影响lauchMode的四种模式,所以下面情况下,其它因素都是缺省不设置的情况)。
standard
standard是默认模式,即假设用户在manifest中对Activity不指定android:launchMode的情况下,缺省启动模式即为standard。在启动一个以standard为launchMode的Activity时,Ams只是简单的创建一个新的Activity实例,将其放到ActivityStack(为了行文方便,后面将ActivityStack简称AS),其TaskId则与启动这个Activity的调用者Activity相同(即就算创建一个新的进程,但是其TaskId还是跟调用者一致的,以确保用户回退操作时保持一致)。这种是最常见的使用方式。
singleTop
启动一个以singleTop为lauchMode的Activity时,Ams会查询AS:假如在AS顶端正是要启动的Activity实例,那么Ams就不会重新启动一个Activity实例,而是调用AS栈顶的该Activity实例的OnNewIntent函数(自然不会修改原来的TaskId值);假如在AS栈顶不是该Activity的实例,那么就会创建一个新的实例,将其压入AS,其TaskId与调用者Activity相同。这种方式主要用于避免自调自过程中,产生多个实例的情况。
singleTask
启动一个以singleTask为lauchMode的Activity时,Ams会查询AS:如果AS内有一个该类Activity的实例,那么就会将该实例置于TS的顶端(原来位于该实例上面的其它同TaskId的activity实例,将被销毁),并调用该实例的onNewIntent函数;如果AS内没有该类的实例,就会启动一个新的实例,将其压入AS,其TaskID与启动它的调用者没有必然关系,而是取决于该Activity所在apk进程是否有TaskId,假如没有就会创建一个新的TaskId。在实测中发现,如果singleTask模式启动的Activity是AS中同TaskId的最底部一个(或被称谓Task栈的根实例),那么在通过桌面长按,在近期任务中跳转到Activity所在的任务时,即使该Activity实例不是在栈顶,也会被置到栈顶(还会调用其onNewIntent函数),并将AS上同TaskId的其它Activity实例销毁,具体可以通过附带的demo来验证,其中TaskOne中的Activity1置成singleTask启动模式,其它均为默认的standard,其log输出如下:
//在TaskTwo中启动Activity1,此前TaskOne apk没有在运行,所以启动Activity1时会申请一个新的TaskId,2afcfbd8这个Activity1成为了TaskId为53的Task栈栈底实例
04-05 16:21:27.144: E/ActivityB @2b003230(17933): onPause pid 17933 taskid 52
04-05 16:21:27.404: E/Activity1 @2afcfbd8(18489): onCreate pid 18489 taskid 53
04-05 16:21:29.214: E/ActivityB @2afecaf0(17933): onCreate pid 17933 taskid 53
04-05 16:21:30.254: E/Activity2 @2afdeef0(18489): onCreate pid 18489 taskid 53
04-05 16:21:30.894: E/ActivityB @2aff9328(17933): onCreate pid 17933 taskid 53
04-05 16:21:31.454: E/Activity2 @2afe9330(18489): onCreate pid 18489 taskid 53
04-05 16:21:31.864: E/ActivityB @2b009910(17933): onCreate pid 17933 taskid 53
04-05 16:21:32.424: E/Activity2 @2aff2570(18489): onCreate pid 18489 taskid 53
//通过长按跳转到TaskOne任务,此时在AS中id为53的在Activity1上面有6个
04-05 16:22:35.144: E/ActivityB @2afecaf0(17933): onDestroy pid 17933 taskid 53
04-05 16:22:35.144: E/Activity2 @2afdeef0(18489): onDestroy pid 18489 taskid 53
04-05 16:22:35.234: E/ActivityB @2aff9328(17933): onDestroy pid 17933 taskid 53
04-05 16:22:35.254: E/Activity2 @2afe9330(18489): onDestroy pid 18489 taskid 53
04-05 16:22:35.324: E/ActivityB @2b009910(17933): onDestroy pid 17933 taskid 53
04-05 16:22:35.344: E/Activity2 @2aff2570(18489): onPause pid 18489 taskid 53
04-05 16:22:35.394: E/Activity1 @2afcfbd8(18489): onNewIntent pid 18489 taskid 53
04-05 16:22:35.394: E/Activity1 @2afcfbd8(18489): onStart pid 18489 taskid 53
04-05 16:22:35.394: E/Activity1 @2afcfbd8(18489): onResume pid 18489 taskid 53
04-05 16:22:35.524: E/Activity2 @2aff2570(18489): onStop pid 18489 taskid 53
04-05 16:22:35.524: E/Activity2 @2aff2570(18489): onDestroy pid 18489 taskid 53
singleInstance
启动一个以singleInstance为launchmode的Activity时,假如AS中已经有一个该类实例,那么调用其onNewIntent函数;否则就会创建一个新的TaskId,与该Activity所在的apk进程完全不同的TaskId,而且这个TaskId值以后也不会被用于其他任何Activity实例中。
简单小结
一般我们开发普通的应用程序时,我们只需要使用缺省的standard和singTop方式就够用了,不需要使用singleTask和singleInstance来声明注册的Activity,因为它将破坏用户感觉上的回退操作,给用户使用上带来迷惑,所以一般将这两者用于很耗资源的Activity,通过查看源码发现在源码packages\apps中的程序,有如下一些应用使用了这两者高级设置
AndroidManifest.xml (packages\apps\browser):
AndroidManifest.xml (packages\apps\calendar):
AndroidManifest.xml (packages\apps\contacts):
AndroidManifest.xml (packages\apps\deskclock):
AndroidManifest.xml (packages\apps\deskclock):
AndroidManifest.xml (packages\apps\deskclock):
AndroidManifest.xml (packages\apps\email):
AndroidManifest.xml (packages\apps\launcher2):
AndroidManifest.xml (packages\apps\music):
AndroidManifest.xml (packages\apps\phone):
AndroidManifest.xml (packages\apps\phone):
AndroidManifest.xml (packages\apps\quicksearchbox):
AndroidManifest.xml (packages\apps\settings):
AndroidManifest.xml (packages\providers\downloadprovider):
在上述分析中没有将其它两个因素引入,主要是自己在这方面接触的也比较少,而且这三方面因素共同组合会产生很多种不同效果,所以就没做具体展开。转载一些网友收集的资料如下:
跟Task有关的manifest文件中Activity的特性值介绍
android:allowTaskReparenting
android:alwaysRetainTaskState
android:clearTaskOnLaunch
android:finishOnTaskLaunch
android:launchMode
android:noHistory
android:taskAffinity
跟Task有关的Intent对象中设置的Flag
FLAG_ACTIVITY_BROUGHT_TO_FRONT
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET
FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
FLAG_ACTIVITY_FORWARD_RESULT
FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
FLAG_ACTIVITY_MULTIPLE_TASK
FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_NO_ANIMATION
FLAG_ACTIVITY_NO_HISTORY
FLAG_ACTIVITY_NO_USER_ACTION
FLAG_ACTIVITY_PREVIOUS_IS_TOP
FLAG_ACTIVITY_REORDER_TO_FRONT
FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
If set, and this activity is either being started in a new task or bringing to the top an existing task, then it will be launched as the front door of the task. This will result in the application of any affinities needed to have that task in the proper state (either moving activities to or from it), or simply resetting that task to its initial state if needed.
FLAG_ACTIVITY_SINGLE_TOP