分屏源码解析(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中的逻辑。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,589评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,615评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,933评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,976评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,999评论 6 393
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,775评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,474评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,359评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,854评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,007评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,146评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,826评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,484评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,029评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,153评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,420评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,107评论 2 356

推荐阅读更多精彩内容