以前改过一个分屏的需求,跟了一下SystemUI
的代码。在这边做一下记录,方便后续做一些更细化的理解拓展。
SystemUI
中的EventBus
该EventBus
用于不同类之间的消息传递,至于原理有空在看。这边说说使用吧,很简单。
注册与解注册:
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow(); EventBus.getDefault().register(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow(); EventBus.getDefault().unregister(this);
}
发送消息与接收消息,注意这边的方法名xxxEvent
要一致:
EventBus.getDefault().send(new xxxEvent(...));
public final void onBusEvent(xxxEvent event) {}
ActivityRecord、TaskRecord、ActivityStack
关系先上图,这是从博客看到的一张图。
首先一个ActivityRecord
对应着一个Activity
,而Activity可能对应着不同的ActivityRecord
(因为Activity可能被实例化多次)。其次一系列ActivityRecord
存在于TaskRecord
,而一系列TaskRecord
存在于ActivityStack
。ActivityStackSupervisor
是用来管理这些ActivityStack
的。
ActivityRecord
@frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java
//ams的引用
final ActivityManagerService service; // owner
//token用来和wms交互
final IApplicationToken.Stub appToken; // window manager token
final ActivityInfo info; // all about me
final ApplicationInfo appInfo; // information about activity's app
...
//Activity资源信息
CharSequence nonLocalizedLabel; // the label information from the package mgr.
int labelRes; // the label information from the package mgr.
int icon; // resource identifier of activity's icon.
int logo; // resource identifier of activity's logo.
int theme; // resource identifier of activity's theme.
int realTheme; // actual theme resource we will use, never 0.
int windowFlags; // custom window flags for preview window.
//Activity所在的TaskRecord
TaskRecord task; // the task this is in.
...
//Activity所在进程
ProcessRecord app; // if non-null, hosting application
int mActivityType
ActivityState state; // current state we are in
...
其中ActivityRecord
定义了该ActivityRecord
对应Activity的三种类型:
static final int APPLICATION_ACTIVITY_TYPE = 0;//普通应用类型
static final int HOME_ACTIVITY_TYPE = 1;//桌面类型
static final int RECENTS_ACTIVITY_TYPE = 2;//最近任务类型
ActivityRecord
是在startActivity
的时候创建的。
@frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java
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) {
...
ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage,
intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho,
requestCode, componentSpecified, voiceSession != null, mSupervisor, container,
options, sourceRecord);
...
}
总结一下,就是在Activity启动的时候同时创建了ActivityRecord
。同时指定了该ActivityRecord
的类型以及所在的TaskRecord
。
TaskRecord
@frameworks/base/services/core/java/com/android/server/am/TaskRecord.java
//TaskRecord的唯一标识
final int taskId;
...
//TaskRecord里所有的ActivityRecord信息
final ArrayList<ActivityRecord> mActivities;
//TaskRecord所在的ActivityStack
ActivityStack stack;
TaskRecord
也是在Activity启动的时候创建与绑定。
@frameworks/base/services/core/java/com/android/server/am/ActivityStarter.java
private void setTaskFromReuseOrCreateNewTask(TaskRecord taskToAffiliate) {
mTargetStack = computeStackFocus(mStartActivity, true, mLaunchBounds, mLaunchFlags,
mOptions);
if (mReuseTask == null) {
final TaskRecord task = mTargetStack.createTaskRecord(
mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),
mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
mNewTaskIntent != null ? mNewTaskIntent : mIntent,
mVoiceSession, mVoiceInteractor, !mLaunchTaskBehind /* toTop */);
mStartActivity.setTask(task, taskToAffiliate);
...
} else {
mStartActivity.setTask(mReuseTask, taskToAffiliate);
}
}
ActivityStack
@frameworks/base/services/core/java/com/android/server/am/ActivityStack.java
/**
* The back history of all previous (and possibly still
* running) activities. It contains #TaskRecord objects.
*/
private final ArrayList<TaskRecord> mTaskHistory = new ArrayList<>();
...
// Stack ID
final int mStackId;
...
/** Run all ActivityStacks through this */
final ActivityStackSupervisor mStackSupervisor;
一个重要的点是Stack ID有五种静态栈
0 HOME_STACK_ID
//Home应用以及recents app
所在的栈
1 FULLSCREEN_WORKSPACE_STACK_ID
//一般应用所在的栈
2 FREEFORM_WORKSPACE_STACK_ID
//类似桌面操作系统
3 DOCKED_STACK_ID
//分屏的应用所在的栈
4 PINNED_STACK_ID
//画中画栈
总的思路是从长按事件开始将参数一直传递到ActivityManager
。然后分成两部分:
先moveTask
操作,也就是获取当前的Task以及DOCKED_STACK_ID
,并将该Task移至分屏的应用所在的栈中以及调用windowManager
改变窗口。如果操作成功的话,启动RecentActivity
。期间通过EventBus
向DeviderView
发送信息,提示Divider
分隔线位置需要改变了。
首先从进入分屏的长按事件开始分析。监听事件在PhoneStatusBar
类中。
private View.OnLongClickListener mRecentsLongClickListener = new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
...
toggleSplitScreenMode(MetricsEvent.ACTION_WINDOW_DOCK_LONGPRESS,
MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
return true;
}
};
@Override
protected void toggleSplitScreenMode(int metricsDockAction, int metricsUndockAction) {
if (mRecents == null) {
return;
}
int dockSide = WindowManagerProxy.getInstance().getDockSide();
if (dockSide == WindowManager.DOCKED_INVALID) {
mRecents.dockTopTask(NavigationBarGestureHelper.DRAG_MODE_NONE,
ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, null, metricsDockAction);
} else {
EventBus.getDefault().send(new UndockingTaskEvent());
if (metricsUndockAction != -1) {
MetricsLogger.action(mContext, metricsUndockAction);
}
}
}
当dockSide
不等于WindowManager.DOCKED_INVALID
的话,说明当前处于分屏状态。那么就会给DividerView
发送一个UndockingTaskEvent
信息,通知DividerView
退出分屏。
当dockSide
等于WindowManager.DOCKED_INVALID
的时候,那么就会调用mRecents.dockTopTask
方法进入分屏状态。也就是调用Recents
的dockTopTask
方法:
@Override
public boolean dockTopTask(int dragMode, int stackCreateMode, Rect initialBounds,
int metricsDockAction) {
...
if (initialBounds == null) {
mContext.getSystemService(DisplayManager.class).getDisplay(Display.DEFAULT_DISPLAY)
.getRealSize(realSize);
initialBounds = new Rect(0, 0, realSize.x, realSize.y);//标注1
}
SystemServicesProxy ssp = Recents.getSystemServices();
//获取当前正在运行的Task
ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
boolean isRunningTaskInHomeStack = runningTask != null &&
SystemServicesProxy.isHomeStack(runningTask.stackId);//标注2
if (runningTask != null && !isRunningTaskInHomeStack && !screenPinningActive) {
logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode);
if (runningTask.isDockable) {//标注3
...
if (sSystemServicesProxy.isSystemUser(currentUser)) {
mImpl.dockTopTask(runningTask.id, dragMode, stackCreateMode, initialBounds);
} else {
...
callbacks.dockTopTask(runningTask.id, dragMode, stackCreateMode,initialBounds);
}
return true;
} else {
EventBus.getDefault().send(new ShowUserToastEvent(
R.string.recents_incompatible_app_message, Toast.LENGTH_SHORT));
return false;
}
} else {
return false;
}
}
标注1设置initialBounds
为分屏屏幕大小的初始值,这里默认值是全屏的尺寸。
标注2判断当前是不是桌面类应用,比如说Launcher就不让你进行分屏操作。
标注3是判断当前是否支持分屏。不支持的话,会通过EventBus
发送弹窗。如果支持的话则走下一步调用dockTopTask
方法,该方法位于中RecentsImpl
类中:
public void dockTopTask(int topTaskId, int dragMode,
int stackCreateMode, Rect initialBounds) {
SystemServicesProxy ssp = Recents.getSystemServices();
// Make sure we inform DividerView before we actually start the activity so we can change
// the resize mode already.
if (ssp.moveTaskToDockedStack(topTaskId, stackCreateMode, initialBounds)) {//标注1
EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
showRecents(
false /* triggeredFromAltTab */,
dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
false /* animate */,
true /* launchedWhileDockingTask*/,
false /* fromHome */,
DividerView.INVALID_RECENTS_GROW_TARGET);
}
}
到这里就开始有两个分支。先将当前的Activity移入分屏对应的栈中。如果分屏成功的话,同时通过Eventbus
通知DeviderView
进行分屏显示操作(这边还没确定DeviderView的位置),同时显示历史Activity列表界面。
moveTaskToDockedStack
操作ssp.moveTaskToDockedStack
,该方法位于SystemServicesProxy
类中:
public boolean moveTaskToDockedStack(int taskId, int createMode, Rect initialBounds) {
if (mIam == null) {
return false;
}
try {
return mIam.moveTaskToDockedStack(taskId, createMode, true /* onTop */,
false /* animate */, initialBounds, true /* moveHomeStackFront */ );
} catch (RemoteException e) {
e.printStackTrace();
}
return false;
}
由上可知是通过IActivityManager
代理通知ActivityManager
执行moveTaskToDockedStack
操作,那么下面看下ActivityManagerService
里面的moveTaskToDockedStack
是怎么运行的:
@frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public boolean moveTaskToDockedStack(int taskId, int createMode, boolean toTop, boolean animate,Rect initialBounds, boolean moveHomeStackFront) {
enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToDockedStack()");
synchronized (this) {
long ident = Binder.clearCallingIdentity();
try {
...
mWindowManager.setDockedStackCreateState(createMode, initialBounds);//标注1
final boolean moved = mStackSupervisor.moveTaskToStackLocked(//标注2
taskId, DOCKED_STACK_ID, toTop, !FORCE_FOCUS, "moveTaskToDockedStack",
animate, DEFER_RESUME);
...
return moved;
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
标注1 通知WindowManager
根据createMode
以及initialBounds
设置DockedStack
。
标注2 mStackSupervisor.moveTaskToStackLocked
执行真正的move操作,跟进去看下。
@frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java
boolean moveTaskToStackLocked(int taskId, int stackId, boolean toTop, boolean forceFocus,
String reason, boolean animate, boolean deferResume) {
//taskId是传进来要移动的,根据ID找出该task
final TaskRecord task = anyTaskForIdLocked(taskId);
...
try {
final ActivityStack stack = moveTaskToStackUncheckedLocked(//标注1
task, stackId, toTop, forceFocus, reason + " moveTaskToStack");
...
if (stackId == FULLSCREEN_WORKSPACE_STACK_ID && task.mBounds != null) {
kept = resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM,
!mightReplaceWindow, deferResume);
} else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
...
kept = resizeTaskLocked(task, bounds, RESIZE_MODE_FORCED, !mightReplaceWindow,
deferResume);
} else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
kept = resizeTaskLocked(task, stack.mBounds, RESIZE_MODE_SYSTEM,
!mightReplaceWindow, deferResume);
}
} finally {
mWindowManager.continueSurfaceLayout();
}
...
return (preferredLaunchStackId == stackId);
}
首先是moveTaskToStackUncheckedLocked
方法通过stackId
返回ActivityStack
对象stack。然后再调用resizeTaskLocked
方法,这是完成移动的核心方法。这边就不分析了,本文只是说一下大的流程。
OK,那回头看下标注1moveTaskToStackUncheckedLocked
方法:
ActivityStack moveTaskToStackUncheckedLocked(TaskRecord task, int stackId, boolean toTop, boolean forceFocus, String reason) {
...
//通过stackId获取ActivityStack
final ActivityStack stack = getStack(stackId, CREATE_IF_NEEDED, toTop);
task.mTemporarilyUnresizable = false;
//WindowManager做相应的移动
mWindowManager.moveTaskToStack(task.taskId, stack.mStackId, toTop);
//将要移动的taskrecord放入ActivityStack
stack.addTask(task, toTop, reason);
// If the task had focus before (or we're requested to move focus),
// move focus to the new stack by moving the stack to the front.
stack.moveToFrontAndResumeStateIfNeeded(
r, forceFocus || wasFocused || wasFront, wasResumed, reason);
return stack;
}
移动的时候显示窗口也是跟着变化。对应着ActivityRecord
,WMS
也有一个WindowState
通过ActivityRecord
的IApplicationToken
关联。而TaskRecord
与Task通过TaskID
对应,ActivityStack
与TaskStack
通过StackID
对应。
跟进WindowManager
的moveTaskToStack
方法:
frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java.java
public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
synchronized (mWindowMap) {
...
Task task = mTaskIdToTask.get(taskId);
...
TaskStack stack = mStackIdToStack.get(stackId);
...
task.moveTaskToStack(stack, toTop);
final DisplayContent displayContent = stack.getDisplayContent();
displayContent.layoutNeeded = true;
mWindowPlacerLocked.performSurfacePlacement();
}
}
可以看到mTaskIdToTask
根据taskId
获取Task对象,mStackIdToStack
根据stackId
获取TaskStack
对象。也就是说ActivityStackSupervisor
作为一个执行者不单单操作了AMS
同时也操作了WMS
。
Recents
列表界面显示在分屏操作成功之后,那么就调用showRecents
方法,也在RecentsImpl
类中代码如下:
public void showRecents(boolean triggeredFromAltTab, boolean draggingInRecents,
boolean animate, boolean launchedWhileDockingTask, boolean fromHome,
int growTarget) {
...
try {
//检查最顶端是不是home stack。
// Check if the top task is in the home stack, and start the recents activity
...
if (forceVisible || !ssp.isRecentsActivityVisible(isHomeStackVisible)) {
ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
startRecentsActivity(runningTask, isHomeStackVisible.value || fromHome, animate,
growTarget);
}
} catch (ActivityNotFoundException e) {
Log.e(TAG, "Failed to launch RecentsActivity", e);
}
}
重点是调用了startRecentsActivity
方法。OK,那么跟进startRecentsActivity
方法中看下:
protected void startRecentsActivity(ActivityManager.RunningTaskInfo runningTask,
boolean isHomeStackVisible, boolean animate, int growTarget) {
...
startRecentsActivity(opts);
mLastToggleTime = SystemClock.elapsedRealtime();
}
private void startRecentsActivity(ActivityOptions opts) {
Intent intent = new Intent();
intent.setClassName(RECENTS_PACKAGE, RECENTS_ACTIVITY);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_TASK_ON_HOME);
if (opts != null) {
mContext.startActivityAsUser(intent, opts.toBundle(), UserHandle.CURRENT);
} else {
mContext.startActivityAsUser(intent, UserHandle.CURRENT);
}
EventBus.getDefault().send(new RecentsActivityStartingEvent());
}
public final static String RECENTS_ACTIVITY = "com.android.systemui.recents.RecentsActivity";
上一个startRecentsActivity
方法经过一系列操作之后,调用startRecentsActivity
。而startRecentsActivity
则根据传进来的opts配置startActivity
。之后通过EventBus
将RecentsActivityStarting
信息通知DeviceView
或者其他需要类。可以看到分屏源码中大量使用EventBus
,他和广播一样方便却比广播方便。到这里代码分析完毕
其中RECENTS_ACTIVITY
的值为启动RecentsActivity
。在RecentsActivity
中的onstart()
回调函数中有个比较重要的方法。
@Override
protected void onStart() {
super.onStart();
...
// Notify of the next draw
mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
}
private final OnPreDrawListener mRecentsDrawnEventListener =
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mRecentsView.getViewTreeObserver().removeOnPreDrawListener(this);
EventBus.getDefault().post(new RecentsDrawnEvent());
return true;
}
};
也就是说在onStart()
中会注册OnPreDrawListener
监听,他会调用EventBus.getDefault().post(new RecentsDrawnEvent())
方法向DeviceView
通知开始ReventActivity
即将绘图了,DeviceView
开始确定位置和大小。