PipMenuView原理讲解-车载车机手机安卓framework系统开发

先来了解一下的PipMenuView的是啥:

b站免费视频教程讲解:
https://www.bilibili.com/video/BV1wj411o7A9/
在这里插入图片描述

正常的情况下pip显示
在这里插入图片描述
触摸小窗pip后:
在这里插入图片描述
是否发现又多了3个按钮,但是这个窗口到底是啥呢?
这个想要知道这个画面是谁,发现dumpsys还看不出具体的窗口,最后还是通过SurfaceFlinger图层发现它的名字的:
在这里插入图片描述
知道了名字后就可以开始入手寻找。

先来看看对应的PipMenuView的创建调用堆栈

05-24 15:29:53.270   753   753 I PipDemo : attachPipMenuView
05-24 15:29:53.270   753   753 I PipDemo : java.lang.Exception
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.wm.shell.pip.phone.PhonePipMenuController.attachPipMenuView(PhonePipMenuController.java:187)
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.wm.shell.pip.phone.PhonePipMenuController.attach(PhonePipMenuController.java:164)
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.wm.shell.pip.PipTaskOrganizer.onTaskAppeared(PipTaskOrganizer.java:627)
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.wm.shell.ShellTaskOrganizer.updateTaskListenerIfNeeded(ShellTaskOrganizer.java:555)
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.wm.shell.ShellTaskOrganizer.onTaskInfoChanged(ShellTaskOrganizer.java:465)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.window.TaskOrganizer$1.lambda$onTaskInfoChanged$6$android-window-TaskOrganizer$1(TaskOrganizer.java:316)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.window.TaskOrganizer$1$$ExternalSyntheticLambda3.run(Unknown Source:4)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.os.Handler.handleCallback(Handler.java:942)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.os.Handler.dispatchMessage(Handler.java:99)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.os.Looper.loopOnce(Looper.java:201)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.os.Looper.loop(Looper.java:288)
05-24 15:29:53.270   753   753 I PipDemo : 	at android.app.ActivityThread.main(ActivityThread.java:7897)
05-24 15:29:53.270   753   753 I PipDemo : 	at java.lang.reflect.Method.invoke(Native Method)
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
05-24 15:29:53.270   753   753 I PipDemo : 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:937)

再看看对应的window添加代码:
frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java

void attachPipMenuView() {
    
    
        // In case detach was not called (e.g. PIP unexpectedly closed)
        if (mPipMenuView != null) {
    
    
            detachPipMenuView();
        }
        mPipMenuView = new PipMenuView(mContext, this, mMainExecutor, mMainHandler,
                mSplitScreenController, mPipUiEventLogger);
                //注意这里调用是mSystemWindows的addView,和正常的WindowGlobal不一样哈
        mSystemWindows.addView(mPipMenuView,
                getPipMenuLayoutParams(MENU_WINDOW_TITLE, 0 /* width */, 0 /* height */),
                0, SHELL_ROOT_LAYER_PIP);
        setShellRootAccessibilityWindow();
        // Make sure the initial actions are set
        updateMenuActions();
    }
    
    frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java
     /**
     * Adds a view to system-ui window management.
     */
    public void addView(View view, WindowManager.LayoutParams attrs, int displayId,
            @WindowManager.ShellRootLayer int shellRootLayer) {
    
    
        PerDisplay pd = mPerDisplay.get(displayId);
        if (pd == null) {
    
    
            pd = new PerDisplay(displayId);
            mPerDisplay.put(displayId, pd);
        }
        pd.addView(view, attrs, shellRootLayer);
    }

这里又发现是调用了PerDisplay的addView

private class PerDisplay {
    
    
        final int mDisplayId;
        private final SparseArray<SysUiWindowManager> mWwms = new SparseArray<>();

        PerDisplay(int displayId) {
    
    
            mDisplayId = displayId;
        }

        public void addView(View view, WindowManager.LayoutParams attrs,
                @WindowManager.ShellRootLayer int shellRootLayer) {
    
    
            SysUiWindowManager wwm = addRoot(shellRootLayer);//根据shellRootLayer为SHELL_ROOT_LAYER_PIP创建出对应的SysUiWindowManager,SysUiWindowManager继承WindowlessWindowManager名字即可以看出这个东西其实没有windowstate的,WindowlessWindowManager实现了IWindowSession,这里session就和以前的session接口实现完全不一样,以前都是一般直接调用wms的接口,这里全部进行了重写,这里也就说明的为啥一定要通过session而不是直接调用wms接口了,就是为了这种动态扩展
           
            final Display display = mDisplayController.getDisplay(mDisplayId);
            //构造对应的SurfaceControlViewHost,会构造出对应的Viewrootimpl
            SurfaceControlViewHost viewRoot =
                    new SurfaceControlViewHost(
                            view.getContext(), display, wwm, true /* useSfChoreographer */);
            attrs.flags |= FLAG_HARDWARE_ACCELERATED;
            viewRoot.setView(view, attrs);//这里调用了SurfaceControlViewHost的setView,其实也最后会调用ViewRootImpl的setView
            mViewRoots.put(view, viewRoot);
            setShellRootAccessibilityWindow(shellRootLayer, view);
        }
  SysUiWindowManager addRoot(@WindowManager.ShellRootLayer int shellRootLayer) {
    
    
            SysUiWindowManager wwm = mWwms.get(shellRootLayer);
            if (wwm != null) {
    
    
                return wwm;
            }
            SurfaceControl rootSurface = null;
            ContainerWindow win = new ContainerWindow();//这里直接new的一个IWindow的binder对象进行添加到wms
            try {
    
    
                //获取一个SurfaceControl来放置pipmenuview,注意这里和不一样,以前都是wms帮忙创建对应的SurfaceControl,所以这里就是为啥dumpsys window没办法看到这里的PipMenuView的窗口
                rootSurface = mWmService.addShellRoot(mDisplayId, win, shellRootLayer);
            } catch (RemoteException e) {
    
    
            }
            //通过上面的rootSurface等构造出对应的SysUiWindowManager
            wwm = new SysUiWindowManager(mDisplayId, displayContext, rootSurface, win);
            mWwms.put(shellRootLayer, wwm);
            return wwm;
        }

看看wms端的addShellRoot:

//frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
    @Override
    public SurfaceControl addShellRoot(int displayId, IWindow client,
            @WindowManager.ShellRootLayer int shellRootLayer) {
    
    //最后目的就是获取一个可以挂载pipmenuview的SurfaceControl
      
        try {
    
    
            synchronized (mGlobalLock) {
    
    
                final DisplayContent dc = mRoot.getDisplayContent(displayId);
                if (dc == null) {
    
    
                    return null;
                }
                return dc.addShellRoot(client, shellRootLayer);
            }
        }
    }
    //frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
       SurfaceControl addShellRoot(@NonNull IWindow client,
            @WindowManager.ShellRootLayer int shellRootLayer) {
    
    
        ShellRoot root = mShellRoots.get(shellRootLayer);
       //省略
        root = new ShellRoot(client, this, shellRootLayer);//创建一个ShellRoot
        SurfaceControl rootLeash = root.getSurfaceControl();//获取一个root的SurfaceControl
         //省略
        mShellRoots.put(shellRootLayer, root);
        SurfaceControl out = new SurfaceControl(rootLeash, "DisplayContent.addShellRoot");
        return out;//把rootLeash的备份返回
    }
    //frameworks/base/services/core/java/com/android/server/wm/ShellRoot.java
ShellRoot(@NonNull IWindow client, @NonNull DisplayContent dc,
            @WindowManager.ShellRootLayer final int shellRootLayer) {
    
    
     //省略
        mClient = client;
        switch (shellRootLayer) {
    
    
            case SHELL_ROOT_LAYER_DIVIDER:
                mWindowType = TYPE_DOCK_DIVIDER;
                break;
            case SHELL_ROOT_LAYER_PIP:
                mWindowType = TYPE_APPLICATION_OVERLAY;//给出对应的window type
                break;
            default:
                throw new IllegalArgumentException(shellRootLayer
                        + " is not an acceptable shell root layer.");
        }
        //这里看到熟悉的创建了对应的windowtoken,因为有TYPE_APPLICATION_OVERLAY可以挂到层级结构树上面
        mToken = new WindowToken.Builder(dc.mWmService, client.asBinder(), mWindowType)
                .setDisplayContent(dc)
                .setPersistOnEmpty(true)
                .setOwnerCanManageAppTokens(true)
                .build();
         //这里以windowtoken为父亲创建了一个图层Shell Root Leash
        mSurfaceControl = mToken.makeChildSurface(null)
                .setContainerLayer()
                .setName("Shell Root Leash " + dc.getDisplayId())
                .setCallsite("ShellRoot")
                .build();
        mToken.getPendingTransaction().show(mSurfaceControl);
    }
    //frameworks/base/core/java/android/view/SurfaceControlViewHost.java
 /** @hide */
    public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
            @NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) {
    
    
        mWm = wwm;
        mViewRoot = new ViewRootImpl(c, d, mWm, useSfChoreographer);//创建出对应的ViewRootImpl
        addConfigCallback(c, d);//添加相关configcallback

        WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);//注意在调用是addWindowlessRoot哈
//省略
    }

上面代码已经清楚到了ViewRootImpl已经把对应的PipMenuView设置到了ViewRootImpl,但是好像并没有看到PipMenuView这个图层有添加到SurfaceFlinger图层,其实核心还是在ViewRootImpl

PipMenuView图层的添加到SurfaceFlinger图层的过程

以前ViewRootImpl的setView是不是有个

 res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
                            mTempControls);

注意这里的mWindowSession已经是我们的WindowlessWindowManager它对这里有进行对应的重写:


 public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
    
    
        return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibilities,
                outInputChannel, outInsetsState, outActiveControls);
    }
    @Override
    public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
    
    
            //注意这里就是创建了一个画面图层名字就是PipMenuView
        final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession)
                .setFormat(attrs.format)
                .setBLASTLayer()
                .setName(attrs.getTitle().toString())
                .setCallsite("WindowlessWindowManager.addToDisplay");
        attachToParentSurface(window, b);//把PipMenuView图层挂到前面说的rootSurface
        final SurfaceControl sc = b.build();
  //省略
    }
      protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
    
    
        b.setParent(mRootSurface);
    }

上面就已经讲解清楚了PipMenuView这个画面是怎么一回事,为啥dumpsys window看不到,但是surfaceflinger结构树可以看到
在这里插入图片描述

时序图:
在这里插入图片描述

pip展示MenuView时候app自定义相关 button原理:

app层是可以控制的:

 if (mPictureInPictureParamsBuilder == null) {
    
    
                mPictureInPictureParamsBuilder = new PictureInPictureParams.Builder();
            }
            RemoteAction action = new RemoteAction(Icon.createWithResource(this,R.mipmap.pasue),"hello","world", PendingIntent.getActivity(
                    this, 1,
                    new Intent(),
                    PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
            ));
            List<RemoteAction> list =new ArrayList<>();
            list.add(action);
            list.add(action);
            list.add(action);
            mPictureInPictureParamsBuilder.setActions(list);
            if (videoView != null) {
    
    

                // Calculate the aspect ratio of the PiP screen. 计算video的纵横比
                int mVideoWith = videoView.getWidth();
                int mVideoHeight = videoView.getHeight();
                Log.i("lsm","enterPiPMode mVideoWith = " + mVideoWith + " mVideoHeight = " + mVideoHeight);
                if (mVideoWith != 0 && mVideoHeight != 0) {
    
    
                    //设置param宽高比,根据宽高比例调整初始参数
                    Rational aspectRatio = new Rational(mVideoWith, mVideoHeight);
                    mPictureInPictureParamsBuilder.setAspectRatio(aspectRatio);
                }

            }

展示出来的效果如下:
在这里插入图片描述

可以看到多了3个图标,这个就是google留给app自己可以定制的接口部分
这里最主要采用的方案就是RemoteView方案,因为PipMenuView属于systemui进程
这里几个ActionView属于各个app进程自己定制的。

在这里插入图片描述

remoteview也可以响应相关的点击事件,不过都是通过pendingintent方式
具体可以参考千里马课程demo

猜你喜欢

转载自blog.csdn.net/learnframework/article/details/130855898