分屏源码解析(2):调用流程

前言:代码基于android8的分屏源码,它的分屏操作是从recent画面中长按一个任务,然后会将该任务挪到左边一半的屏幕显示出来并且可以交互,右边仍为recent画面,此时仍可以长按recent画面中的任务再打开一个任务,然后recent画面就消失。目前最多只能支持两个任务的分屏,现在希望通过研究两个任务分屏怎么实现的,去实现三个,或者四个任务的分屏。


    双屏怎么实现的:

    源码中的分屏模式,思路是通过双栈(一个DOCKED stack,一个RECENT stack或者FULL_SCREEN stack,取决于右半屏的应用是什么),把它们挪到前面,对应的task则挪到stack的前面,然后设置它们的bounds(栈的显示区域),从而限制了窗口的大小,最后绘制的时候就有两个应用显示在前台了。

  我们重点看startActivityFromRecent()里的逻辑,如图:


图中的函数调用关系已经清晰了,着重讲几个关键点:

    我们重写了一个自己的recent activity,名叫CustomRecentActivity,它的主体是一个RecyclerView,用GirdLayoutManager实现格子布局,我们要主要看在长按一个recent的时候,它是如何打开这个recent,并且分屏的,代码在这:

  @Override

  public boolean onLongClickItem(int position) {    //重写了onLongClickItem,长按某个recent的view的时候会调用这

     if (processEvent != null && mStack != null) {

         ArrayList<Task> list =  filterIgnoreTask(mStack.getStackTasks());

         if (list != null && list.size() > position) {

            return processEvent.onLongClickItem(list.get(position), mStack);  //CustomRecentActivity实现了这个接口

         }

     }

return false;

  }

  ----------------------------------------------------------------------------------

     @Override

   public boolean onLongClickItem(Task task, TaskStack stack) {

       if (isInMultiWindow) {   //如果进入了分屏,这个为true

           return false;

       }

       SystemServicesProxy ssp = Recents.getSystemServices();

       ssp.startTaskInDockedMode(task.key.id, ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT); //打开选中的recent

       mStack.removeTask(task, null, true /* fromDockGesture */);

       ssp.overridePendingAppTransitionMultiThumbFuture(null,

               null,

               true /* scaleUp */);

       startSelf();

        if(task.key.getComponent().getPackageName().equals("display.interactive.board2")){

            Intent mIntent = new Intent("com.hikvision.android.intent.action.multiwindow.showLeft");

            mIntent.setFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);

            Context mContext = CustomRecentsActivity.this;

            mContext.sendBroadcast(mIntent,"com.hikvision.receiver");

        }

        return true;

   }

   ---------------------------------------------------------------------------------------

       /** Docks a task to the side of the screen and starts it. */

   public boolean startTaskInDockedMode(int taskId, int createMode) {

       if (mIam == null) return false;

       try {

           final ActivityOptions options = ActivityOptions.makeBasic();

           options.setDockCreateMode(createMode);

           options.setLaunchStackId(DOCKED_STACK_ID);  //这个stackid为3

           mIam.startActivityFromRecents(taskId, options.toBundle());  //调用到AMS里的逻辑,请求打开对应recent的activity

           return true;

       } catch (Exception e) {

           Log.e(TAG, "Failed to dock task: " + taskId + " with createMode: " + createMode, e);

       }

       return false;

    }

      这三个函数就是处理输入事件,然后去向AMS请求打开对应的recent的activity,这里有一个点比较重要,就是DOCKED_STACK_ID,我们需要设置该stack id,AMS才会知道你要分屏,然后创建DOCKED stack。

    桌面是在id为0这个ActivityStack中管理着的,其他全屏的app的activity则在1这个ActivityStack中,至于分屏的app,自然是在3(DOCKED)这个ActivityStack中了。继续看AMS怎么处理请求的,它是转给了ActivityStackSupervisor中:

final int startActivityFromRecentsInner(int taskId, Bundle bOptions) {

   final TaskRecord task;

   final int callingUid;

   final String callingPackage;

   final Intent intent;

   final int userId;

   final ActivityOptions activityOptions = (bOptions != null)

           ? new ActivityOptions(bOptions) : null;

   final int launchStackId = (activityOptions != null)   //  DOCKED_STACK_ID = 3

           ? activityOptions.getLaunchStackId() : INVALID_STACK_ID;

   mWindowManager.deferSurfaceLayout();

   try {

       if (launchStackId == DOCKED_STACK_ID) {  

           mWindowManager.setDockedStackCreateState(

                   activityOptions.getDockCreateMode(), null /* initialBounds */);

           // Defer updating the stack in which recents is until the app transition is done, to

           // not run into issues where we still need to draw the task in recents but the

           // docked stack is already created.

           deferUpdateBounds(RECENTS_STACK_ID);

           mWindowManager.prepareAppTransition(TRANSIT_DOCK_TASK_FROM_RECENTS, false);

       }

       task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,

               launchStackId);                                                                 //step1

       // Since we don't have an actual source record here, we assume that the currently

       // focused activity was the source.

       final ActivityStack focusedStack = getFocusedStack();

       final ActivityRecord sourceRecord =

               focusedStack != null ? focusedStack.topActivity() : null;

       if (launchStackId != INVALID_STACK_ID) {

           if (task.getStackId() != launchStackId) {

               task.reparent(launchStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE,

                       DEFER_RESUME, "startActivityFromRecents");                              //step2

           }

       }

       // If the user must confirm credentials (e.g. when first launching a work app and the

       // Work Challenge is present) let startActivityInPackage handle the intercepting.

       if (!mService.mUserController.shouldConfirmCredentials(task.userId)

               && task.getRootActivity() != null) {

           mService.mActivityStarter.sendPowerHintForLaunchStartIfNeeded(true /* forceSend */);

           mActivityMetricsLogger.notifyActivityLaunching();

           mService.moveTaskToFrontLocked(task.taskId, 0, bOptions, true /* fromRecents */);

           mActivityMetricsLogger.notifyActivityLaunched(ActivityManager.START_TASK_TO_FRONT,

                   task.getTopActivity());

           // If we are launching the task in the docked stack, put it into resizing mode so

           // the window renders full-screen with the background filling the void. Also only

           // call this at the end to make sure that tasks exists on the window manager side.

           if (launchStackId == DOCKED_STACK_ID) {

               setResizingDuringAnimation(task);

           }

           mService.mActivityStarter.postStartActivityProcessing(task.getTopActivity(),

                   ActivityManager.START_TASK_TO_FRONT,

                   sourceRecord != null

                           ? sourceRecord.getTask().getStackId() : INVALID_STACK_ID,

                   sourceRecord, task.getStack());

           return ActivityManager.START_TASK_TO_FRONT;

       }

       callingUid = task.mCallingUid;

       callingPackage = task.mCallingPackage;

       intent = task.intent;

       intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY);

       userId = task.userId;

       int result = mService.startActivityInPackage(callingUid, callingPackage, intent, null,

               null, null, 0, 0, bOptions, userId, null, task, "startActivityFromRecents");  

       if (launchStackId == DOCKED_STACK_ID) {

           setResizingDuringAnimation(task);

       }

       return result;

   } finally {

       mWindowManager.continueSurfaceLayout();                                              //step3

   }

}


  我们再看这个函数里做的事情,我们对应分屏的case,如果是简单的在recent中打开一个应用,那么下面的不会发生。    主要分三步:

    Step1. 首先判断launch id是否为DOCKED stack的id,是则说明用户想分屏,然后在WMS中设置一些标志位。  接着,在DOCKED stack查找要分屏的task,第一次分屏这里都为null,所以系统会新建一个DOCKED stack,并且WMS中也会新建一个TaskStack(与ActivityStack对应)。

    Step2.  有了DOCKED stack后,要做一次reparent操作,即把要分屏的task从旧栈移除,添加到DOCKED stack中,然后调用resize,让task的bounds与栈的bounds保持一致,bounds的值会影响WindowState中的mFrame的值,该值会在窗口绘制的时候生效,限制显示的区域。 由于要分屏,窗口层级的大小也做相应的调整。

    Step3. 最后,就可以启动要分屏应用的Activity了,应用的task已经被挪到了栈顶,这个时候启动它的top activity,最后调用continueSurfaceLayout去触发WMS中的逻辑。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容