Android四大组件之Activity(四)—— 启动模式(launchMode) + Pause上一个Activity

1、使用的是Android 7.1.2的源代码:

https://pan.baidu.com/s/1XcVD68cC_2wKtm8jJkdNQA
przv

2、感谢IT先森的系列博客:

Android应用进程创建流程大揭秘
Android四大组件之bindService源码实现详解
Android四大组件之Activity启动流程源码实现详解概要
Activity启动流程(一)发起端进程请求启动目标Activity
Activity启动流程(二)system_server进程处理启动Activity请求
Activity启动流程(三)-Activity Task调度算法复盘分析
Activity启动流程(四)-Pause前台显示Activity,Resume目标Activity
Activity启动流程(五)请求并创建目标Activity进程
Activity启动流程(六)注册目标Activity进程到system_server进程以及创建目标Activity进程Application
Activity启动流程(七)初始化目标Activity并执行相关生命周期流程

疑问:
设置启动模式? ActivityStack栈管理,根据启动模式来判断是否启动栈
在ActivityStarter.startActivityUnChecked方法中进行处理

说实话,startActivityUnChecked函数中对栈处理的这部分内容我没看懂,就是了解了对于栈处理的内容在该函数中。

相关伪代码:

======system_server进程端======
AMS解析Intent	  
	AMS.startActivity(...)
	 ActivityStarter.startActivityMayWait(...)
	   ResolveInfo rInfo = ASS.resolveIntent(...)//收集Intent所指向的Activity信息, 当存在多个可供选择的Activity,则直接向用户弹出resolveActivity
	     IPackageManager.Stub.resolveIntent(...)//通过PKMS实体查询
		   PMS.resolveIntent(...)
			 PMS.queryIntentActivitiesInternal(...)
			 PMS.chooseBestActivity(...)
	   ActivityInfo aInfo = ASS.resolveActivity(...) //根据获取的rInfo信息重新组装intent和设置启动的参数信息
	   ActivityStarter.startActivityLocked(...)
		  ActivityRecord r = new ActivityRecord(callerApp,intent,aInfo,mSupervisor,...)
			appToken = new Token(this, _intent);
			  设置state为INITIALIZING
		  ActivityStarter.startActivityUnchecked(...) 
           //包括两方面内容:
           //1、AMS对于Activity比较复杂的部分即Task任务栈和Stack的处理,其中会把目标Activity放置到栈顶端
           //2、resumed相关操作 
           // 目标Activity所属Stack栈存在Resume状态的Activity时会执行其onPause方法,否则执行的就是其它Stack栈中的了
           // 错误说法:启动一个Activity的时候最先被执行的是栈顶的Activity的onPause方法       
    	  ActivityStackSupervisor.resumeFocusedStackTopActivityLocked
    			ActivityStack.resumeTopActivityUncheckedLocked
    				ActivityStack.resumeTopActivityInnerLocked
    					ActivityStack.startPausingLocked
    						ApplicationThreadProxy.schedulePauseActivity


======发起端进程处理schedulePauseActivity======
   ActivityThread.ApplicationThread.schedulePauseActivity
        ActivityThread.H.handleMessage(case PAUSE_ACTIVITY)
             ActivityThread.handlePauseActivity //1.指定前台Activity的onPause();2.通过AMP回调activityPaused()通知AMS
             	ActivityThread.performPauseActivity
              		ActivityThread.performPauseActivityIfNeeded
              			Instrumentation.callActivityOnPause
              				Activity.performPause
              					Activity.onPause()
                ActivityManagerNative.getDefault().activityPaused(token);


======system_server进程端======
    AMS.activityPaused
    	ActivityStack.activityPausedLocked
    		ActivityStack.completePauseLocked
    		   ActivityStackSupervisor.resumeFocusedStackTopActivityLocked  //又调用了resumeFocusedStackTopActivityLocked
    				ActivityStack.resumeTopActivityUncheckedLocked
    					ActivityStack.resumeTopActivityInnerLocked
    						ActivityStackSupervisor.startSpecificActivityLocked
    							AMS.startProcessLocked
    								AMS.startProcessLocked
    									AMS.newProcessRecordLocked

一、启动模式(launchMode)

Activity在 AndroidManifest.xml 文件中定义Activity时,可以通过launchMode属性来指定这个Activity应该如何与任务进行关联,取值有如下四种,不同的启动模式在启动 Activity时会执行不同的逻辑,系统会按不同的启动模式将Activity存放到不同的 Activity 栈中:

  • standard(默认的启动模式):
    LAUNCH_MULTIPLE,每次启动新Activity,都会创建新的Activity
  • singleTop:
    LAUNCH_SINGLE_TOP,当启动新Activity,如果在栈顶存在相同的Activity,则不会创建新的Activity,其余情况同上。
    比如我们使用一款视频APP观看短视频,视频播放页面同时还推荐了类似视频
    当我们点击了推荐视频,事件顺序大概是:打开视频播放页面 -> 点击推荐视频 -> 复用当前页面播放推荐视频,同时刷新推荐列表
    即栈顶对象可复用,没必要新建一个实例。
  • singleTask:
    LAUNCH_SINGLE_TASK,当启动新Activity,在栈中存在相同的Activity(可以是不在栈顶),则不会创建新的Activity,而是移除该Activity之上的所有Activity,其余情况同上。
    比如我们使用一款购物APP购买商品,Activity打开顺序大概是:商品页面 - 下单页面 - 支付页面 - 交易完成界面
    显然,我们完成时,需要返回商品页面,没必要返回支付页面,因为订单已经结束,因此我们需要在回到商品页面时销毁下单和支付页面
    即Activity顶部的任务已过期,没必要再保留。
  • singleInstance:
    LAUNCH_SINGLE_INSTANCE,单例模式,每个Task栈只有一个Activity,其余情况同上
    在一个新栈中创建该Activity的实例,并让多个应用共享该栈中的该Activity实例。一旦该模式的Activity实例已经存在于某个栈中,任何应用再激活该Activity时都会重用该栈中的实例( 会调用实例的 onNewIntent() )。其效果相当于多个应用共享一个应用,不管谁激活该 Activity 都会进入同一个应用中。使用场景如闹铃提醒,将闹铃提醒与闹铃设置分离。singleInstance不要用于中间页面,如果用于中间页面,跳转会有问题,比如:A -> B (singleInstance) -> C,完全退出后,在此启动,首先打开的是B。
//[ActivityInfo.java]
public class ActivityInfo extends ComponentInfo
        implements Parcelable {
   
    ...
    public static final int LAUNCH_MULTIPLE = 0;
    public static final int LAUNCH_SINGLE_TOP = 1;
    public static final int LAUNCH_SINGLE_TASK = 2;
    public static final int LAUNCH_SINGLE_INSTANCE = 3;
	...
}

二、启动的Flag值

  • FLAG_ACTIVITY_NEW_TASK,将Activity放入一个新启动的Task,注意属性task:affinity
  • FLAG_ACTIVITY_CLEAR_TASK,启动Activity时,将目标Activity关联的Task清除,再启动该Task,将该Activity放入该Task,也就是说,这个新启动的activity变为了这个空Task的根activity.所有老的activity都结束掉。该flags跟FLAG_ACTIVITY_NEW_TASK配合使用
  • FLAG_ACTIVITY_CLEAR_TOP,启动非栈顶Activity时,先清除该Activity之上的Activity。例如Task已有A、B、C三个Activity,启动A,则清除B,C。类似于SingleTop
  • FLAG_ACTIVITY_PREVIOUS_IS_TOP,A->B->C,若B启动C时用了这个标志位,那在启动时,B并不会被当作栈顶的Activity,而是用A做栈顶来启动C,此过程中B充当一个跳转页面,典型的场景是在应用选择页面。如果在文本中点击一个网址要跳转到浏览器,而系统中又装了不止一个浏览器应用,此时会弹出应用选择页面。在应用选择页面选择某一款浏览器启动时,就会用到这个Flag。
  • START_FLAG_ONLY_IF_NEEDED,该flag表示只有在需要的时候才启动目标Activity。也就是说如果调用者和被启动的是一个,那么就没有必要去进行重复的步骤了

Android Intent的FLAG标志详解:https://www.jianshu.com/p/537aa221eec4

Activity的flag常用值:

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

Android上进行多任务处理:
点击Home键,长按Home键或通过其它方式可以看到当前启动的任务。每个任务都具有自己的Activity堆栈。用户返回主屏幕并选择启动任务A的应用,现在,任务A进入前台,其堆栈中的所有三个Activity都完好如初,堆栈顶部的Activity恢复运行。此时,用户仍可通过以下方式切换到任务B:①转到主屏幕并选择启动该任务的应用图标 ②从最近的应用中选择该应用的任务。

了解任务和返回堆栈:
https://developer.android.google.cn/guide/components/activities/tasks-and-back-stack

疑问:使用intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)的话,回到主屏幕,显示的还是只有主程序这一个Task啊?
答:因为没有使用 android:taskAffinity

三、AMS对Activity的4种启动模式的处理方式

1、ActivityStarter.startActivityLocked

final int startActivityLocked(IApplicationThread caller, Intent intent, Intent ephemeralIntent,
String resolvedType, ActivityInfo aInfo, ResolveInfo rInfo,
IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid,
String callingPackage, int realCallingPid, int realCallingUid, int startFlags,
ActivityOptions options, boolean ignoreTargetSecurity, boolean componentSpecified,
ActivityRecord[] outActivity, ActivityStackSupervisor.ActivityContainer container,
TaskRecord inTask)

  • ActivityInfo中包含各种的activity信息,都是声明在AndroidManifest.xml文件中的,比较重要的包括launchMode、theme、screenOrientation等。
  • ResolveInfo是一个容器类,里面包含了ActivityInfo、ServiceInfo、ProviderInfo等成员变量来表示四大组件的信息。activity和broadcast信息都是用ActivityInfo来表示的。
  • IBinder resultTo是 发起端Activity的ActivityRecord对象中的Token,其Binder实体在AMS中,这个值是在Activity.startActivity中赋值传递过来的,具体:Activity中的mToken
  • inTask 指定待启动的Activity的任务栈,此处为null

startActivityLocked方法的主要逻辑如下:

  • 进一步对发起端的进程做一些权限检查,然后接着确定 sourceRecord 和 resultRecord 的值
  • 接着通过 上述确认的参数构建关于目标Activity的 ActivityRecord (经过复杂的判定,只是创建了一个ActivityRecord)
  • 创建 ActivityRecord 之后,也有一些处理,包括 AppSwitch,优先启动之前被阻塞的 Activity,然后进入下一阶段 startActivityUnchecked(…) ,从该方法名看出该做的检查已经做完了,剩下的函数调用就不需要进行额外的检查了(Unchecked),在分析Android源码中经常会看到类似的命名规则。
  • 调用startActivityUnchecked(),开展后续的Activity启动

2、ActivityStarter.startActivityUnchecked

负责调度ActivityRecord和TaskRecord、resumed相关操作

该方法的具体作用:

  • 初始化Activity启动状态
  • 计算launchFlag
  • 计算调用者的ActivityStack
  • 检查是否存在复用的TaskRecord
  • 对于存在复用的TaskRecord则进行相应的ActivityStack、TaskRecord的移动 (有难度)
  • 计算当前启动Activity所属的TaskRecord
  • 把当前启动的Activity放到所属TaskRecord的栈顶

大致代码:

ActivityStarter.startActivityUnchecked()
    setInitialState()  //给要启动的ActivityRecord(mStartActivity)等赋值ActivityRecord mStartActivity = r;
    computeLaunchingTaskFlags()  //根据launchMode和 Intent 中的 FLAG_ACTIVITY_NEW_TASK 等 flag 综合计算 Activity 的启动模式,结果保存在mLaunchFlags 中,计算的过程不仅要考虑目标 activity 的 launchMode ,也要考虑原来 Activity 的 launchMode 和 Intent 中所带着的 Flag
    computeSourceStack()  //根据发起方ActivityRecord:mSourceRecord来找到源任务栈:mSourceTask
    getReusableIntentActivity()  //查找可重用的Activity,只对启动模式LAUNCH_SINGLE_INSTANCE和LAUNCH_SINGLE_TASK或者FLAG_ACTIVITY_NEW_TASK不为0的Activity才有用,对于standard的activity,该方法永远返回null。
   
    //if(mReusedActivity != null)
    performClearTaskForReuseLocked() //遍历TaskRecord对象实例中的ActivityRecord列表,然后根据一定的规则清除可复用的activity上面的activity
    
    //经过上面步骤后,不管是否进行了栈顶数据的清除,接下来就要将我们可以复用的Activity所在的TaskRecord移动到其所在的ActivityStack的顶部
    
    setTargetStackAndMoveToFrontIfNeeded() //将复用ActivityRecord所属的TaskRecord和ActivityStack移动到顶端,必要时会进行task的清理工作
    	ActivityStack.moveTaskToFrontLocked()
    setTaskFromIntentActivity()
    ...
    //在同一个应用中从Activity A启动 Activity B不会走此分支
	if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
   
         ··· ···
    }else if(mSourceRecord != null){
   
        // 不是新建task的,重用原activity的task
            final int result = setTaskFromSourceRecord();
			/*****************************************************************************/	
				//这里小伙们就不要关注排版问题了,主要是为了演示整个流程,各位就将就一下 
			    private int setTaskFromSourceRecord() {
   
					//获取启动Activity的任务栈
			        final TaskRecord sourceTask = mSourceRecord.task;
			        //此时的发起端Actiivty所在的TaskRecord就是处于sourceStack栈顶,所以sourceStack.topTask就是要启动的Activity所在的栈
			        //如果目标Activity不允许在屏幕上显示或者源任务栈和目标任务不在同一个栈
			        final boolean moveStackAllowed = sourceTask.stack.topTask() != sourceTask;
					//获取当前要启动activity所属的ActivityStack栈
			        if (moveStackAllowed) {
   //不会进入此分支						
						...
			        }
					
					//目标ActivityStack为空
			        if (mTargetStack == null) {
   
			            mTargetStack = sourceTask.stack;//进入此分支
			        } else if (mTargetStack != sourceTask.stack) {
   
			        	//把启动方的任务栈绑定到目标ActivityStack上
						...
			        }
			        if (mDoResume) {
   
			            mTargetStack.moveToFront("sourceStackToFront");
			        }
			
					//获取目标ActivityStack的顶部task
			        final TaskRecord topTask = mTargetStack.topTask();
			        if (topTask != sourceTask && !mAvoidMoveToFront) {
   //不会走入此分支
						
			        }
			
					//如果目标activity还没有加入到栈中,而且启动标志设置了CLEAR_TOP,那么我们将Activity添加到已经存在的任务栈中,并调用clear方法清空对应的activity
			        if (!mAddingToTask && (mLaunchFlags & FLAG_ACTIVITY_CLEAR_TOP) != 0) {
   //很明显不会进入此分支
						...
			        } else if (!mAddingToTask && (mLaunchFlags & FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {
   //不会进入此分支
						...
			        }
			        mStartActivity.setTask(sourceTask, null);//设置目标Activity B的Task为A Activity所属的Task

			        return START_SUCCESS;
			    }				
			/*****************************************************************************/						            
            if (result != START_SUCCESS) {
   
                return result;
            }
        } else if (mInTask != null) {
   //启动时指定了目标栈(mInTask),ActivityRecord绑定到mInTask,此场景下不会进入此分支
			...
        } else {
   //不会进入此分支,忽略
			..,
        }		
		...
		/*把当前启动的Activity加入TaskRecord以及绑定WindowManagerService*/
        mTargetStack.startActivityLocked(mStartActivity, newTask, mKeepCurTransition, mOptions);	
		/*****************************************************************************/	
			//这里小伙们就不要关注排版问题了,主要是为了演示整个流程,各位就将就一下  
		    final void startActivityLocked(	ActivityRecord r, //此时的r为目标Activity
		    								boolean newTask, //newTask表示是否要创建Task,为true
		    								boolean keepCurTransition,
		            						ActivityOptions options) 
		   {
   
		        TaskRecord rTask = r.task;
		        final int taskId = rTask.taskId;
				
		        if (!r.mLaunchTaskBehind && (taskForIdLocked(taskId) == null || newTask)) {
   //不会进入此分支
					...
		        }
		        TaskRecord task = null
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值