读书笔记之SystemUI分屏浅析

前言

以前改过一个分屏的需求,跟了一下SystemUI的代码。在这边做一下记录,方便后续做一些更细化的理解拓展。

预备知识

1、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) {}

1、理清ActivityRecord、TaskRecord、ActivityStack关系

先上图,这是从博客看到的一张图。

img

首先一个ActivityRecord对应着一个Activity,而Activity可能对应着不同的ActivityRecord(因为Activity可能被实例化多次)。其次一系列ActivityRecord存在于TaskRecord,而一系列TaskRecord存在于ActivityStackActivityStackSupervisor是用来管理这些ActivityStack的。

2.1 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

2.2 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);
    }
}

2.3 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。期间通过EventBusDeviderView发送信息,提示Divider分隔线位置需要改变了。

1、开始分屏前的逻辑操作

首先从进入分屏的长按事件开始分析。监听事件在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方法进入分屏状态。也就是调用RecentsdockTopTask方法:

@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列表界面。

2、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;
    }

移动的时候显示窗口也是跟着变化。对应着ActivityRecordWMS也有一个WindowState通过ActivityRecordIApplicationToken关联。而TaskRecord与Task通过TaskID对应,ActivityStackTaskStack通过StackID对应。

读书笔记之SystemUI分屏浅析_第1张图片

跟进WindowManagermoveTaskToStack方法:

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

3、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。之后通过EventBusRecentsActivityStarting信息通知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开始确定位置和大小。

你可能感兴趣的:(架构设计)