Android Framework(四)WMS-窗口显示流程——窗口创建与添加

news2024/11/17 3:06:27

文章目录

  • 流程概览
    • 涉及模块
    • 流程概览
  • 应用端——window创建:Activity::attach
    • 创建window流程
    • setWindowManager,getWindowManager
    • DecorView
  • 应用端——window的显示流程:Activity::onResume
    • ViewRootImpl::setView
      • mWindowSession 是什么
      • mWindow是什么
      • 跨进程通信小结
    • 流程小结
    • 关键类小结
  • 窗口添加——流程概览
    • WindowManagerService::addWindow方法概览
    • Token相关
    • WindowState的创建与挂载
    • WindowState挂载
    • 挂载的位置

流程概览

涉及模块

WMS 负责管理设备上所有的 Window ,所以应用想显示一个 Window 则要通过 WMS 来完成。 而 WMS 毕竟还是上层,窗口的内容要显示到屏幕上,还需要 SurfaceFlinger 来处理。 整个窗口的显示逻辑会涉及到下图中的三个模块:
在这里插入图片描述

1、应用端:控制内部窗口的添加以及UI绘制逻辑。
2、WMS模块:作为系统窗口管理模式,处理应用端的窗口显示逻辑。
3、SurfaceFlinger模块:WMS只能控制系统层窗口逻辑,真正显示内容还需要原生驱动层的SurfaceFlinger来完成。

应用端与WMS通信是通过匿名Binder —> Session完成。

WMS与SurfaceFlinger的通信通过匿名Binder ——> Clinent 完成。

流程概览

在这里插入图片描述
Google把窗口的显示分为了三个流程:

  1. addWindow流程:
    应用在走到onResume生命周期时,viewrootImpl会向WMS发送addWindow请求添加窗口,这一阶段,WMS的处理为:
    为应用端创建对应的windowstate并挂载。

  2. relayoutWindow流程:
    addWindow 流程后执行后,屏幕上就有新的 WindowState 添加了,WMS 也需要对屏幕上所有的窗口执行一遍 layout 来确定各个窗口所在的位置。
    而应用端想要绘制 UI 数据,则也需要知道自己的窗口大小,位置信息,并且还需要一个 Surface 来承载 UI 数据。所以这一阶段 WMS 的处理为:

    • 为窗口申请Surface并返回给应用端
    • 计算并返回窗口的大小,位置信息给应用端
  3. finishDrawingWindow流程:
    执行完上一流程后,应用端就可以执行View显示三部曲(测量、布局、绘制)来绘制UI了,绘制完成后需要将UI显示到屏幕上,这一步还需要WMS来通过SurfaceFlinger来显示这个Surface

应用端——window创建:Activity::attach

应用进程启动后,会执行 LaunchActivityItem 和 ResumeActivityItem 这2个事务,对应执行到 Activity 的 onCreate 和 onResume 生命周期,这其中肯定也涉及到了 Window 相关的操作。

LaunchActivityItem::execute
    ActivityThread::handleLaunchActivity
        ActivityThread::performLaunchActivity
            Instrumentation::newActivity      -- 创建Activity
            Activity::attach                  -- 创建Window
                Window::init
                Window::setWindowManager
            Instrumentation::callActivityOnCreate  
                Activity::performCreate
                    Activity::onCreate 

ResumeActivityItem::execute
    ActivityThread::handleResumeActivity
        ActivityThread::performResumeActivity   
            Activity::performResume   
                Instrumentation::callActivityOnResume
                    Activity::onResume        
        WindowManagerImpl::addView           -- 创建ViewRootImpl
            WindowManagerGlobal::addView   
                ViewRootImpl::setView        --WMS通信触发窗口的显示逻辑

这个调用链重点是:
1、创建Activity时在attach时,会创建对应的window,并setWindowManager,为activity绑定对应的窗口。注意,attach这个阶段activity还没执行到onCreate阶段,而是刚刚new出来,可见,activity与window的绑定是很早的,在activity刚刚new出来的attach阶段
2、在Activity的onResume阶段,执行 addView ,而这个 addView 后的逻辑才是触发 Window 显示流程,所以执行了 onResume 只是 Activity 可见,不代表 View 都显示了,这个时候都没触发 WMS 的绘制,如果后续的流程中出了问题,我们写在 XML 里的布局是不会显示在屏幕上的。window的显示在activity的onResume阶段

创建window流程

# ActivityThread
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            ......
            // 定义window
            Window window = null;
            ......
            // token传递的是ActivityRecord的token
            // 这里的 window 正常逻辑目前还为null
            activity.attach(...,r.token,, window, ...);
            ......
    }

# Activity
    // 定义Window对象
    private Window mWindow;

    final void attach(......) {
        ......
        // 重点* 1. 创建 PhoneWindow
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        // 一些设置
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        // 重点* 2. 将Activity作为回调设置给Window
        mWindow.setCallback(this);
        ......
        //  重点* 3. 设置token为 ActivityRecord
        mToken = token;
        ......
        // 重点* 4.1 set WindowManager
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

        ......
        // 重点* 4.2 get WindowManager
        mWindowManager = mWindow.getWindowManager();
        ......
    }

    public Window getWindow() {
        return mWindow;
    }
  1. 日常开发中,通过 Activity::getWindow 方法返回的其实是 PhoneWindow ,这是因为 Window 是个抽象类,而 PhoneWindow 是 Window 的唯一子类。
  2. 创建 PhoneWindow 后有一堆设置,这里需要注意 Window::setCallback 方法,是将 Activity 设置给了 Window,这里有什么用呢?
    这个是因为 Activity 也实现了 Window.Callback 接口,所以能传递 this ,这个接口主要是处理 Input 事件的。

Input 事件的传递流程都是先走到 Window,因为在 WMS 模块没有 Activity 的概念,只有 Window ,那么最后是怎么走到 Activity 呢?就是这里设置的 setCallback 。当然这个在当前分析的addWindow 流程没有关系,但是需要有点印象。

  1. 这块token可以理解为ActivityRecord
  2. setWindowManager 和 getWindowManager 这个两个方法写在这乍一看有点矛盾,在一个地方 set 又 get 感觉很多余。这是因为这里 set 和 get 返回的对象,其实不是同一个对象,下边会讲。

setWindowManager,getWindowManager

# Window
    // 应用Token
    private IBinder mAppToken;

    private WindowManager mWindowManager;

    // wm :WindowManager对象,注意下传进来的值
    // appToken :这个就是AMS中与当前Activity对应的ActivityRecord
    // appName :Activity全类名
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        // 将ActivityRecord设置给mAppToken
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        // 根据强转也能看出 mWindowManager 是 WindowManagerImpl 的类型
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }
    public WindowManager getWindowManager() {
        return mWindowManager;
    }

这里将传递进来的 wm 强转成 WindowManagerImpl 后调用其 createLocalWindowManager方法。
看的出来这个 wm 就是 WindowManagerImpl 类型。

这里的 WindowManager 其实就是一个接口,看命令就 Window 的管理者,FrameWork 有很多跨进程通信的类命名方式就是 BP 端叫 XXManager 然后对应的 BN 端就是 XXManagerService 。
但是! 这里的 WindowManager 不是这样的,他真的就是一个单纯的 java 语言里的接口,定义了一些约束,然后由 WindowManagerImpl 实现。

另外这里是通过 “getSystemService” 这份方式获取的,也容易给人误解他是一个 Service 。其实这个 WindowManager 和其实现类 WindowManagerImpl 与 WindowManagerService 几乎一点关系都没有。

WindowManager 又实现了 ViewManager 接口,这个接口中定义了对 View 的三个操作:addView ,removeView ,updateViewLayout 。

继续看流程,
WindowManagerImpl::createLocalWindowManager 该方法重新创建返回了一个 WindowManagerImpl 对象。 所以说setWindowManager 和 getWindowManager 的不是同一个对象, WindowManagerImpl::createLocalWindowManager 方法如下:

# WindowManagerImpl
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mContext, parentWindow, mWindowContextToken);
    }
    private WindowManagerImpl(Context context, Window parentWindow,
            @Nullable IBinder windowContextToken) {
        mContext = context;
        mParentWindow = parentWindow;
        mWindowContextToken = windowContextToken;
    }

这边注意的是将 Window 设置给了 mParentWindow。 相当于通过新创建的PhonWindow创建了一个 WindowManagerImpl ,作为其mWindowManager的对象。

到这里创建Window相关的就分析完了,创建的这个Window其实是 PhoneWindow 。

DecorView

每个 Window 都拥有一个 View。

# PhoneWindow.java
    // This is the top-level view of the window, containing the window decor.
    // 翻译:这是窗户的顶层视图,包含窗户装饰。
    private DecorView mDecor;

    private void installDecor() {
            mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            ......
        }......
    }
    // 返回 DecorView
    @Override
    public final @NonNull View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            // 为空就创建
            installDecor();
        }
        return mDecor;
    }

这个 mDecor 是 DecorView, 本质上也是一个 帧布局的View,是UI视图树的根 ,在 PhoneWindow::generateDecor 方法赋值,这个方法有多处会执行,最常见的就是我们在 Activity::onCreate 里执行 setContentView 的时候。

# DecorView.java
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    ......
}

也就是说 Window 通过 mDecor 的变量持有一个 View。

应用端——window的显示流程:Activity::onResume

这部分流程由应用端 ResumeActivityItem 开始事务触发,最终会执行到 Activity::onResume 生命周期,并且也会执行 ViewRootImpl::setView 方法,这个方法才是触发 窗口显示的真正逻辑。

# ActivityThread
    public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
            boolean isForward, String reason) {
        ......
        // 重点* 1. 触发onResume
        if (!performResumeActivity(r, finalStateRequest, reason)) {
            return;
        }
        ......
        // 拿到activity
        final Activity a = r.activity;
        ......
        if (r.window == null && !a.mFinished && willBeVisible) {
            // 将window 设置到 activityRecord中
            r.window = r.activity.getWindow();
            // 获取DecorView
            View decor = r.window.getDecorView();
            // 设置 View 不可见  
            decor.setVisibility(View.INVISIBLE);
            // 实际上是WindowManagerImpl
            ViewManager wm = a.getWindowManager();
            // 获取参数
            WindowManager.LayoutParams l = r.window.getAttributes();
            // DecorView设置给Activity
            a.mDecor = decor;
            // 重点* 2. 设置Activity的windowType,注意这个type,才是应用的窗口类型
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            ......
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    // 重点* 3. 执行addView,并设置mWindowAdded=true
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                   ......
                }
            }

        } else if (!willBeVisible) {
            ......
        }
        ......
        if (r.activity.mVisibleFromClient) {
            // 设置可见
            r.activity.makeVisible();
        }
        ......
    }

这段代码信息量还挺多的,相关注释已经加在代码上了,这里对3个重点单独提一下:

  • 1、performResumeActivity 会触发 onResume 。执行顺序在下面的 addView 之前,说明 onResume 只是 Activity 可见,而不是 UI 可见

  • 2、Activity 的 WindowType 为 TYPE_BASE_APPLICATION = 1, 还有个 TYPE_APPLICATION=2 目前已知是在创建 ActivityRecord 时使用

  • 3、通过 WindowManagerImpl::addView 触发后续逻辑

# WindowManagerImpl
    // 单例
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance()

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyTokens(params);
        // 这里的mParentWindow 就是PhoneWindow
        mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                mContext.getUserId());
    }

这个方法并没有啥复杂的,直接调到了 WindowManagerGlobal,不过这里也有2个需要注意的点:

  • 1、WindowManagerGlobal 是个单例,那就是说一个进程仅此一个。
  • 2、这里将mParentWindow传递了过去,上面分析的时候知道这个 mParentWindow 其实就是我们创建的 PhoneWindow。
# WindowManagerGlobal
    // 应用内 View 集合
    @UnsupportedAppUsage
    private final ArrayList<View> mViews = new ArrayList<View>();
    @UnsupportedAppUsage
    // 应用内 ViewRootImpl 集合
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    @UnsupportedAppUsage
     // 应用内 View 参数 集合
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
        ......
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            // 重点* 1. 调整 window参数,
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            ......
        }
        // 定义ViewRootImpl
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            ......
            // 如果一个窗口执行过 addView 的话,再执行就报错
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }
            ......
            IWindowSession windowlessSession = null;
            ......
            // 对于应用来说 windowlessSession 是为null的
            if (windowlessSession == null) {
                // 重点* 2. 创建ViewRootImpl
                root = new ViewRootImpl(view.getContext(), display);
            } else {
                root = new ViewRootImpl(view.getContext(), display,
                        windowlessSession);
            }
            // 设置参数到 decorView
            view.setLayoutParams(wparams);
            // 重点* 3. 添加到对应集合,看得出来在WindowManagerGlobal中这3个对象应该是要一一对应的
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            // do this last because it fires off messages to start doing things
            try {
                // 重点* 4. ViewRootImpl::setView
                root.setView(view, wparams, panelParentView, userId);
            } catch (RuntimeException e) {
                ......
            }
        }
    }

这段代码最重要的就是做了4件事:
1、Window::adjustLayoutParamsForSubWindow 调整参数,比如设置 token ,title,和硬件加速的标志位这3个比较重要的参数

2、ViewRootImpl 的创建 (这个类很重要)

3、WindowManagerGlobal 是进程单例维护了这个应用中多有的 DecorView ,而且看得出来定义了3个集合,且3个集合的元素是一一对应的

4、执行 ViewRootImpl::setView 方法,传递的 view 就是 PhoneWindow 持有的 DecorView 。 这里是应用端处理窗口的最重要一步,也是需要分析的主流程。

ViewRootImpl::setView

到这里要注意的,现在开始就没有对 Window 进行操作了,操作的是 DecorView 。

# ViewRootImpl
    // 与 WMS 通信的 binder
    final IWindowSession mWindowSession;
    // 对应的DecorView
    View mView;

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            // 当前第一次执行肯定为null
            if (mView == null) {
                // 重点* 将DecorView赋值给 mView
                mView = view;
                ......
                mAdded = true; // 表示已经add
                int res; // 定义稍后跨进程add返回的结果
                // 重点* 非常重要的方法,relayoutWindow和finishDrawingWindow都在这触发
                requestLayout();  
                InputChannel inputChannel = null; // 用于窗口接收input
                if ((mWindowAttributes.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                inputChannel = new InputChannel();
                }
                ......
                try {
                    ......
                    // 重点* 这里通过binder通信,调用WMS的 addWindow方法
                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
                            mTempControls);
                    ......
                }
                // 后续流程与addWindow主流程无关,但是也非常重要
                ......
                // 计算一次 Window的尺寸
                final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
                mWindowLayout.computeFrames(mWindowAttributes, state,
                        displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
                        UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
                        mInsetsController.getRequestedVisibilities(),
                        getAttachedWindowFrame(), 1f /* compactScale */, mTmpFrames);
                setFrame(mTmpFrames.frame);
                ......
                if (res < WindowManagerGlobal.ADD_OKAY) {
                    ......// 对WMS调用addWindow后的结果判断是什么错误
                }
                ......
                // DecorView::getParent 返回的是 ViewRootImpl 的原因
                view.assignParent(this); 
                ......
            }
        }
    }

这个方法是核心方法,处理了很多事,注释都加上了。为了有一个宏观的印象,这里将其触发的各个调用链整理如下:

ViewRootImpl::setView
    ViewRootImpl::requestLayout
        ViewRootImpl::scheduleTraversals             
            ViewRootImpl.TraversalRunnable::run          --- Vsync相关--scheduleTraversals
                ViewRootImpl::doTraversal
                  ViewRootImpl::performTraversals 
                     ViewRootImpl::relayoutWindow        --- relayoutWindow
                     ViewRootImpl::performMeasure        --- View绘制三部曲
                     ViewRootImpl::performLayout
                     ViewRootImpl::performDraw        
                     ViewRootImpl::createSyncIfNeeded    --- 绘制完成finishDrawing
    WindowSession.addToDisplayAsUser                     --- addWindow

这里要注意:虽然看顺序好像 addWindow 流程是在 relayoutWindow 执行前,但是因为 doTraversal 是异步的,所以还是先执行 addWindow 流程。
可以看到窗口显示的3个流程都在 ViewRootImpl::setView 里触发,在看着3个流程前,先看2个变量:

  • 1、mWindowSession 是什么?
  • 2、addToDisplayAsUser 放到第一个参数里的 mWindow 是什么?

mWindowSession 是什么

# ViewRootImpl
    final W mWindow;
    final IWindowSession mWindowSession;
    public ViewRootImpl(Context context, Display display) {
        this(context, display, WindowManagerGlobal.getWindowSession(),
                false /* useSfChoreographer */);
    }
    public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
        boolean useSfChoreographer) {
            mContext = context;
            mWindowSession = session;
            ......
            mWindow = new W(this);
            ......
        }

1、mWindowSession 是个 binder 对象,用于跨进程通信(与WMS)
2、mWindowSession 的赋值在 ViewRootImpl 构造方法
3、Activity 场景下分析,在 WindowManagerGlobal::addView 方法中构造 ViewRootImpl 是2个参数的构造方法,所以 mWindowSession 就是 WindowManagerGlobal::getWindowSession

接下来就需要看看 WindowManagerGlobal::getWindowSession 方法。

# WindowManagerGlobal

    private static IWindowSession sWindowSession;

    @UnsupportedAppUsage
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    // Emulate the legacy behavior.  The global instance of InputMethodManager
                    // was instantiated here.
                    // TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
                    InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
                    IWindowManager windowManager = getWindowManagerService();
                    // 赋值
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            // 返回
            return sWindowSession;
        }
    }

继续看 WindowManagerService::openSession 。

# WindowManagerService
    @Override
    public IWindowSession openSession(IWindowSessionCallback callback) {
        return new Session(this, callback);
    }
# Session

class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
    final WindowManagerService mService;
    ......
    @Override
    public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
        return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
                requestedVisibilities, outInputChannel, outInsetsState, outActiveControls);
    }
}

1、所以这里的 WindowManagerGlobal::getWindowSession 返回的就是一个 Session 对象。Session 继承 IWindowSession.Stub,并且内部持有 WMS 引用

2、应用端调用的 Session::addToDisplayAsUser 最终执行的是 WindowManagerService:: addWindow

3、 Session 是一个匿名 Binder

mWindow是什么

调用 Session::addToDisplayAsUser 方法传递了一个 mWindow ,咋一看还以为是把应用端的 Window 对象传递到 WMS 了,但是细想也不可能, WMS 模块的业务并不依赖应用端的 Window 对象,从上一小节的分析也看到了 mWindow 是 W 类型的变量,也是在 ViewRootImpl 的构造方法里赋值的,那这个W是什么呢?

# ViewRootImpl

        final W mWindow;
        final IWindowSession mWindowSession;

        public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
            boolean useSfChoreographer) {
                mContext = context;
                mWindowSession = session;
                ......
                mWindow = new W(this);
                ......
            }
        static class W extends IWindow.Stub {
            private final WeakReference<ViewRootImpl> mViewAncestor;
            private final IWindowSession mWindowSession;

            W(ViewRootImpl viewAncestor) {
                mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
                mWindowSession = viewAncestor.mWindowSession;
            }
            ......
        }

1、ViewRootImpl 下的 mWindow 是其内部类 W 的对象,这个 W 继承了 IWindow.Stub,那也是用于 binder 通信的

2、这个 W 是个匿名 Binder 作为 BN(binder native) 端,用于 WMS 调用应用端的相关方法

3、W 内部有一个 ViewRootImpl 弱引用。

跨进程通信小结

WMS 是实名 Binder Service 。从可行性上来说 ViewRootImpl 是完全可以直接通过 Binder 与 WMS 进行直接通信的,但是为什么要加这个 IWindowSession 呢?
这是因为 WMS 是核心服务,系统中有很多地方都需要与其通信,所以如果能减少直接与其 Binder 通信的频率也能提升系统效率。 所以 WMS 为每个 Activity(窗口)提供了一个匿名实现:IWindowSession ,处理一些“小请求”,减少各个进程直接与 WMS Binder 通信的频率。

这个和 SurfaceFlinger 的设计一致,SurfaceFlinger 也提供了一个匿名 Binder :Client 。

流程小结

应用端的流程在 ViewRootImpl::setView 方法中就结束了,内部会触发3大流程。
在这里插入图片描述
应用进程启动后,会执行 2 个事务,分别触发到 Activity 的 onCreate 和 onResume 2个常见的生命周期,所以这里分为了2个分支。
onCreate 分支:
(1)由 LaunchActivityItem 事务触发 handleLaunchActivity
(2)创建 Activity
(3)创建 Window,而 Window 是抽象类,PhoneWindow 唯一实现类。
(4)执行到 onCreate 生命周期
onResume 分支:
(1)由 ResumeActivityItem 事务触发 handleResumeActivity
(2)先触发 onResume 的执行流程
(3)执行 WindowManagerImpl::addView 实际上流程是交给了 WindowManagerGlobal::addView 处理
(4)创建核心类 ViewRootImpl
(5)执行关键函数 ViewRootImpl::setView ,这里会触发 WMS 三大流程

3大流程的逻辑还没看,但是可以先有个概念:

(1)addWindow 流程: ViewRootImpl 调用 Session::addToDisplayAsUser 触发 WindowManagerService::addWindow
(2)relayoutWindow 流程:ViewRootImpl 调用 Session::relayout 触发 WindowManagerService::relayoutWindow
(3)finishDrawingWindow 流程:ViewRootImpl 调用 Session::finishDrawing 触发 WindowManagerService::finishDrawingWindow

关键类小结

在这里插入图片描述

  • Activity :
    是通过反射创建的 ,通过成员变量 mWindow 持有一个 Window
    一个应用可以有多个 Activity ,而每个 Activity 的结构都是刚才分析的,这些 Activity 被存在 ActivityThread 下的 mActivities 的变量中

  • PhoneWindow,Window :Window 是个抽象类,唯一实现是 PhoneWindow ,通过成员变量 mDecor 持有一个 View

  • DecorView : 本质上也是一个 View 是开发常说的 “根View” ,Window 本身是没有内容的,真正 UI 载体就是这个 DecorView

  • ViewManager ,WindowManager : 前面2者都是接口类,WindowManagerImpl 实现其接口

  • WindowManagerImpl :WindowManagerImpl 表面上是管理 Window ,但是实际业务还是交给了 WindowManagerGlobal

  • WindowManagerGlobal :

    • 应用内全局单例,是真正的应用内 Window 的总管家,但是其实管理的是 Window 下的 DecorView
    • 当应用内有 Window 创建后,WindowManagerImpl 会调用
    • WindowManagerGlobal::addView 方法将 DecorView 传递过去
      WindowManagerGlobal 会为每个 DecorView 创建一个 ViewRootImpl 来管理这棵“View树”
    • 内部有3个集合,将 Window 的 DecorView 参数 LayoutParams 和新创建的 ViewRootImpl 一一添加到对应的集合中
  • ViewRootImpl :在 GUI 系统中是应用端的核心类,管理一个“View树”,负责这个“View树”的绘制等事务,同时还需要控制其与 WMS 通信

  • Session : WMS 为应用端的一个窗口单独开发的跨进程通信通道

  • W : 应用端的 Binder Service ,会传递到 WMS 中,这样 WMS 就可以与 应用端的一个窗口进行通信。

窗口添加——流程概览

首先从结果上对比下应用启动后窗口的区别:
在这里插入图片描述
红色部分就是启动应用后多出来的部分,在 DefaultTaskDisplayArea 节点下多出来这么一个层级:

Task
    ActivityRecord
        WindowState

在这里插入图片描述
其中 Task 和 ActivityRecord 是如何挂载上去的在【Activity启动流程】已经介绍了,当前要分析的 addWindow 流程最重要的目标就是分析窗口对应的 WindowState 是如何创建并且挂载到窗口树中的。
也就是这一变化:
在这里插入图片描述
这个流程逻辑相对简单,整个流程框图如下:
在这里插入图片描述

  • 1、应用端 Activity 执行到 onResume 说明 Activity 已经可见,下面就需要处理可见的内容

  • 2、应用端 Session 调用到 WindowManagerService::addWindow 方法

  • 3、WMS 处理 addWindow 流程也就做了2件事:

    • 创建出对应的 WindowState
    • 挂载到层级树中(挂载到对应的 WindowToken 下)

上边知道流程已经执行到 ViewRootImpl::setView 来触发 addWindow 流程,回忆一下应用端的调用:

# ViewRootImpl

final W mWindow;

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
                ......
                    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
                            mTempControls, attachedFrame, compatScale);
                ......
            }

这里有几个参数比较重要: mWindow : 用于 WMS 与应用端通信 mWindowAttributes : DecorView 的参数 getHostVisibility() :可见性 inputChannel:Input 通路。

看到你这些参数有个疑问:
明明是 addWindo 流程,但是到了 WindowManagerImpl 就变成了 addView 传递的也是 DecoreView ,再到和 WMS 通信的时候,参数里连 DecoreView 都不剩了,这怎么能叫 addWindow 流程呢?

WindowManagerService::addWindow方法概览

接上知道 Session::addToDisplayAsUser 方法调用的是 WindowManagerService::addWindow ,先看一下这个方法。

#  WindowManagerService
    // 保存应用端 ViewRootImpl 和 WindowState 的映射关系
    /** Mapping from an IWindow IBinder to the server's Window object. */
    final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();

    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
            ......
            // 1.1 权限检查
            int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
                    appOp);
            if (res != ADD_OKAY) {
                return res;
            }
            // 父窗口,应用 Activity 窗口逻辑是没有父窗口的
            WindowState parentWindow = null;
            ......
            // 拿到当前窗口类型
            final int type = attrs.type;
            ......
            synchronized (mGlobalLock) {
                    ......
                    // 1.2 如果窗口已经添加,直接return
                    if (mWindowMap.containsKey(client.asBinder())) {
                        // 日志
                        ProtoLog.w(WM_ERROR, "Window %s is already added", client);
                        return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                    }
                    ......
                    
                    ActivityRecord activity = null;
                    // 是否为 hasParent
                    final boolean hasParent = parentWindow != null;
                    // 2.1 拿到token
                    WindowToken token = displayContent.getWindowToken(
                            hasParent ? parentWindow.mAttrs.token : attrs.token);
                    // Activity 没有父窗口,这里也为null
                    final int rootType = hasParent ? parentWindow.mAttrs.type : type;
                    ......
                    if (token == null) {
                        ......
                        if (hasParent) {
                            // Use existing parent window token for child windows.
                            // 2.2子窗口用父窗口的 token
                            token = parentWindow.mToken;
                        } else if (...) {
                            ......
                        } else {
                            // 2.3 系统窗口会创建 token
                            final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                            token = new WindowToken.Builder(this, binder, type)
                                    .setDisplayContent(displayContent)
                                    .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
                                    .setRoundedCornerOverlay(isRoundedCornerOverlay)
                                    .build();
                        }
                    } else if (rootType >= FIRST_APPLICATION_WINDOW
                        && rootType <= LAST_APPLICATION_WINDOW) {
                        ......
                    } else if......// 忽略其他各种创建对 token的处理

                    // 3.1 创建 WindowState
                    final WindowState win = new WindowState(this, session, client, token, parentWindow,
                            appOp[0], attrs, viewVisibility, session.mUid, userId,
                            session.mCanAddInternalSystemWindow);
                    ......
                    final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
                    // 调整window的参数
                    displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
                    ......
                    // 1.3 验证Window是否可以添加,主要是验证权限
                    res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
                    if (res != ADD_OKAY) {
                        // 如果不满足则直接return
                        return res;
                    }   
                    final boolean openInputChannels = (outInputChannel != null
                            && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
                    if  (openInputChannels) {
                        // 4.1 Input 事件输入通道
                        win.openInputChannel(outInputChannel);
                    }
                    ......
                    
                    // 3.2 创建SurfaceSession
                    win.attach();
                    // 3.3 窗口存入mWindowMap
                    mWindowMap.put(client.asBinder(), win);
                    ......
                    // 3.4 窗口挂载
                    win.mToken.addWindow(win);
                    displayPolicy.addWindowLw(win, attrs);
                    ......
                    // 4.2 处理窗口焦点切换
                    boolean focusChanged = false;
                    if (win.canReceiveKeys()) {
                        focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                                false /*updateInputWindows*/);
                        if (focusChanged) {
                            imMayMove = false;
                        }
                    }
                    ......
                    // 调整父容器下的元素层级
                    win.getParent().assignChildLayers();
                    // 4.3 更新inut焦点
                    if (focusChanged) {
                        displayContent.getInputMonitor().setInputFocusLw(displayContent.mCurrentFocus,
                                false /*updateInputWindows*/);
                    }
                    // 4.4 更新input窗口
                    displayContent.getInputMonitor().updateInputWindowsLw(false /*force*/);
                    // 窗口添加log
                    ProtoLog.v(WM_DEBUG_ADD_REMOVE, "addWindow: New client %s"
                            + ": window=%s Callers=%s", client.asBinder(), win, Debug.getCallers(5));
                    ......
                }
                Binder.restoreCallingIdentity(origId);
                return res;
        }

这个方法就是 addWindow 流程的核心方法了,代码很多,保留了下面4个主要逻辑:

  • 1、检验处理:操作权限检验,判重校验以限制应用端的一个rootview只能执行一次。
  • 2、Token处理
    • token其实就是WindowToken类别(ActivityRecord的父类)
    • 获取token,如果是子窗口从父窗口拿,没有就从参数拿
    • 如果是系统窗口,就会根据窗口类型创建出一个WindowToken
  • 3、WindowState处理
    • new WindowState
    • 执行WindowState::attach,创建出SurfaceSession
    • 将新建的WindowState和W映射,存入mWindowMap中。
    • 窗口挂载
  • 4、Input和焦点处理

当前分析的 addWindow 主流程,所以分析2,3亮点,也就是 Token 和 WindowState 的处理逻辑。

Token相关

当前分析的场景,这个 Token 就是 Activity 启动流程中创建 ActivityRecord 时创建的 Token ,而 ActivityRecord 是 WindowToken 的子类。
在 【WindowContainer窗口树】介绍过,WindowState 的父节点大部分情况是 WindowToken ,而且在上一篇看到 dump 启动应前后的窗口树区别,明确知道 WindowState 是挂载到 ActivityRecord 下的,
在分析 WindowState 的创建和挂载前,需要先给它找到它的父节点: WindowToken 。这也是 WindowManagerService::addWindow 方法中比较靠前执行的逻辑。

根据上一小节的分析,当前场景的 Token 来自参数“attrs.token” 。这个参数是应用端传递过来的,上一篇在分析 WindowManagerGlobal::addView 方法的时候提到在Window::adjustLayoutParamsForSubWindow 方法对赋值 token 给参数,现在看一下这个方法。

# Window
    // 应用Token
    private IBinder mAppToken;

    void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        ......
        if (wp.token == null) {
            wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; // activity的window在这里设置token
        }
        ......
    }

mContainer 唯一赋值的地方在 Window::setContainer 方法,当前没调用,所以 wp.token 最终的值是为 mAppToken ,而mAppToken 的赋值在给 Window 设置 WindowManager 的时候赋值,也就是setWindowManager 方法,这里的 token 就是 ActivityRecord 的 token 。
下面这张图可以更直观的看到 Token 的传递:
在这里插入图片描述

  • 1、WindowToken 内部有个成本变量 token ,ActivityRecord 是其子类

  • 2、Activity 启动过程中会先创建 ActivityRecord ,在创建 ActivityRecord 的时候会创建一个匿名 Token 对象,并保存在变量 token 中

  • 3、随着启动流程的执行,会在 ActivityTaskSupervisor::realStartActivityLocked 方法里构建事务,这个时候 token 就被保存在 ClientTransaction 的成员变量 mActivityToken

  • 4、ClientTransaction 提供了一个 getActivityToken 方法返回 mActivityToken 。这个方法在具体的事务执行时,比如 LaunchActivityItem::execute 方法执行,会作为参数传递过去

  • 5、LaunchActivityItem::execute 方法会构建一个 ActivityClientRecord ,构建方法需要 Token 参数,这个时候 Token 就被保存在 ActivityClientRecord 的成员变量 token 中

  • 6、接下里就到了应用进程,应用进程执行 ActivityThread::performLaunchActivity 方法开始处理 Activity 启动流程,ActivityClientRecord 作为参数被传递了过来

  • 7、ActivityThread::performLaunchActivity 方法内部会执行 Activity::attach 方法,这个方法需要一个 Token 作为参数,传递的就是从 ActivityClientRecord 里取出的 token

  • 8、Activity::attach 方放会将 Token 赋值给成员变量 mToken

  • 9、Window 创建后会执行 Window::setWindowManager ,这个时候会将 mToken 作为参数传递进去,保存在 Window 的成员变量 mAppToken 中

  • 10、在执行 WindowManagerGlobal::addView 时会执行 Window::adjustLayoutParamsForSubWindow 调整参数,这个时候 Token 就被复制到 WindowManager.LayoutParams 下的 token 变量中

  • 11、执行 addWindow 流程时,WindowManager.LayoutParams 会被传递到 WMS ,这样 Token 也就被传递了过去

WindowState的创建与挂载

addWindow 流程中 WindowState 的创建与挂载是重点,在 WindowManagerService::addWindow 方法中,执行了 WindowState 的创建,代码如下:

# WindowManagerService

    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
            ......// WindowToken相关处理
            // 创建WindowState
            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], attrs, viewVisibility, session.mUid, userId,
                    session.mCanAddInternalSystemWindow);
            ......
        }

这里注意几个参数,然后直接看WindowState的构造方法

# WindowState

    final IWindow mClient;
    @NonNull WindowToken mToken;
    
    // The same object as mToken if this is an app window and null for non-app windows.
    // 与 mToken 相同的对象(如果这是应用程序窗口),而对于非应用程序窗口为null
    // 说人话就是应用窗口才有ActivityRecord
    ActivityRecord mActivityRecord;
    // 层级
    final int mBaseLayer;

    WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
            int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
            PowerManagerWrapper powerManagerWrapper) {
            ......
            mClient = c;
            ......
            // 保存token
            mToken = token;
            // 只有 ActivityRecord 重写了 asActivityRecord 其他默认返回努力了
            mActivityRecord = mToken.asActivityRecord();
            ......
            //子窗口处理
            if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW){
                ......
            }else {
                // Activity的窗口指为  2 * 10000 + 1000  = 21000
                mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
                        * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
                ......
            }
        }

创建 WindowState 有2个重要的参数 :client,和token。这个 client 代表着客户端也就是 ViewRootImpl 的内部类 W ,另一个参数就是上节的 Token 。

WindowState 以后会经常看到,不过当前只要知道在 WindowManagerService::addWindow 会创建出一个 WindowState 对象即可。

WindowState挂载

WindowState 创建好后自然是需要挂载到窗口树的,操作也很简单,直接添加到对应的 (ActivityRecord)WindowToken 下就好。

# WindowManagerService
    // ViewRootImpl和WindowState的map
    final HashMap<IBinder, WindowState> mWindowMap = new HashMap<>();

    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls) {
            ......
            // 窗口已经添加,直接return
            if (mWindowMap.containsKey(client.asBinder())) {
                // 打印log
                ProtoLog.w(WM_ERROR, "Window %s is already added", client);
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }
            ......// WindowToken相关处理
            ......// WindowState的创建
            // WindowState的挂载
            win.attach();
            // 1. 存进map
            mWindowMap.put(client.asBinder(), win);
            ......
            // 2. 挂载
            win.mToken.addWindow(win);
            ......
        }

  • 1、 在看挂载前先看一下 mWindowMap 这个数据结构,key 是一个 IBinder,value 是 WindowState ,这边将新创建的 WindowState 作为 value 添加到了 map 中,前面说过 client是应用端 ViewRootImpl 下的 “W”这个类,也就是说在 WMS 中应用端的这个 ViewRootImpl 和为其创建的 WindowState 已经被记录在 mWindowMap 中了。

在执行WMS::addWindow方法开始的时候就会尝试通过 clent 从 mWindowMap 获取值,如果获取到了说明已经执行过 addWindow 则进行 return 不执行后面逻辑。

  • 2、这里是窗口的挂载,“win.mToken” 这里的 mToken 刚刚看到是创建 WindowState 的时候传递的 token 也就是 ActivityRecord (WindowToken)。也就是说调用的是 ActivityRecord::addWindow 方法进行挂载的。
# ActivityRecord
    @Override
    void addWindow(WindowState w) {
        super.addWindow(w);
        checkKeyguardFlagsChanged();
    }

直接调用其父类方法,ActivityRecord 父类是 WindowToken,也就是说调用的是ActivityRecord::addWindow 方法进行挂载的。

# ActivityRecord
    @Override
    void addWindow(WindowState w) {
        super.addWindow(w);
        checkKeyguardFlagsChanged();
    }

直接调用其父类方法,ActivityRecord 父类是 WindowToken

# WindowToken
    void addWindow(final WindowState win) {
        // WindowState 挂载日志
        ProtoLog.d(WM_DEBUG_FOCUS,
                "addWindow: win=%s Callers=%s", win, Debug.getCallers(5));

        if (win.isChildWindow()) {
            // Child windows are added to their parent windows.
            // 子窗口的父类应该是WindowState所以不执行后续
            return;
        }
        // This token is created from WindowContext and the client requests to addView now, create a
        // surface for this token.
        // 真正添加进子容器
        if (!mChildren.contains(win)) {
            // 日志
            ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", win, this);
            // 挂载(添加进孩子容器),有一个比较方法
            addChild(win, mWindowComparator);
            // 记录有窗口边框
            mWmService.mWindowsChanged = true;
            // TODO: Should we also be setting layout needed here and other places?
        }
    }

执行完 WindowContainer::addChild 方法后 WindowState 已经被添加到层级树中了,挂在到对应的 ActivityRecord 下。
当然这里需要注意 WindowToken::addWindow 最终也是调用父类 WindowContainer::addChild 将 WindowState 添加到自己的孩子中,这里传递了一个mWindowComparator。

挂载的位置

WindowContainer::addChild 方法被定义在基类,也就是容器添加孩子时都会按一定规则添加,当然默认其实还是按顺序,但是有的时候也有特殊情况,所以这个方法提供了一个参数,使得可以在具体场景控制具体的添加逻辑。

# WindowContainer
    protected void addChild(E child, Comparator<E> comparator) {
        // 检查子元素是否已经被其他容器拥有,如果是,则抛出异常
        if (!child.mReparenting && child.getParent() != null) {
            throw new IllegalArgumentException("addChild: container=" + child.getName()
                    + " is already a child of container=" + child.getParent().getName()
                    + " can't add to container=" + getName());
        }
        // 初始化插入位置为-1,表示尚未找到合适的插入位置
        int positionToAdd = -1;
        // 如果有比较器则进行比较
        
        // 遍历当前容器中的所有子元素
        if (comparator != null) {
            final int count = mChildren.size();
            // 使用比较器比较待插入的子元素和当前容器中的子元素
            for (int i = 0; i < count; i++) {

                // 如果比较结果小于0,表示待插入元素应该位于当前元素之前
                if (comparator.compare(child, mChildren.get(i)) < 0) {
                    positionToAdd = i;
                    break;
                }
            }
        }
        // 没有比较器或者比较的结果还是-1 ,则添加到最后(大部分场景)
        if (positionToAdd == -1) {
            mChildren.add(child);
        } else {
            // 如果比较器计算出了准确位置,则按要求添加
            mChildren.add(positionToAdd, child);
        }

        // Set the parent after we've actually added a child in case a subclass depends on this.
        // 调用孩子容器设置当前容器为其父节点
        child.setParent(this);
    }

1、方法目的就是添加子元素到父容器中,但是可以根据 comparator 比较规则添加到正确的位置

2、比较方式很简单,拿当前需要添加的元素和容器内其他元素逐个比较,如果比较 comparator 返回值小于0,则添加到“被比较”的元素前面

3、有2种情况,是按顺序添加到容器末尾

  • 3.1 没有比较器。positionToAdd 为默认值 -1
  • 3.2 和每个元素比较的返回值都大于0,说明要添加其后面,这个时候 positionToAdd 还是为默认值 -1

4、setParent 调用孩子容器设置当前容器为其父节点,另外还会将 mSyncState 变量设置为 SYNC_STATE_WAITING_FOR_DRAW

当前场景,父容器 ActivityRecord 还是是空的,所以没什么意义。不过既然看到这里,就继续分析,根据分析,当前逻辑调用的比较器是 WindowToken下的 mWindowComparator 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2116788.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【数据库】MySQL聚合统计

目录 1.聚合函数 案例1&#xff1a; 统计班级共有多少同学 案例2&#xff1a;统计本次考试的数学成绩分数个数 案例3&#xff1a;统计数学成绩总分 案例4&#xff1a;统计平均总分 案例5&#xff1a;返回英语最高分 案例6&#xff1a;返回 > 70 分以上的数学最低分 2.分…

双指针思想

一.双指针思想 1.分类&#xff1a;同向双指针&#xff0c;反向双指针 2.优点&#xff1a;可以将两层循环嵌套的问题优化成一层循环 3.常见情况 <1>利用快慢双指针确定链表的中间节点&#xff0c;链表是否带环&#xff0c;带环链表的入环点在哪里 <2>一次循环解…

Android Fragment 学习备忘

1.fragment的动态添加与管理&#xff0c;fragment生命周期在后面小节&#xff1a;https://www.bilibili.com/video/BV1Ng411K7YP/?p37&share_sourcecopy_web&vd_source982a7a7c05972157e8972c41b546f9e4https://www.bilibili.com/video/BV1Ng411K7YP/?p37&share_…

安装Android Studio及第一个Android工程可能遇到的问题,gradle下载过慢、sync失败?

Android Studio版本众多&#xff0c;电脑操作系统、电脑型号、电脑硬件也是多种多样&#xff0c;幸运的半个小时内可以完成安装&#xff0c;碰到不兼容的电脑&#xff0c;一天甚至更长时间都无法安装成功。 Android安装及第一个Android工程分为4个步骤&#xff0c;为什么放到一…

9.8笔试记录

1.在c中哪些运算符不能重载? 在 C 中&#xff0c;有以下几个运算符不能被重载&#xff1a; . &#xff1a;成员访问运算符。例如obj.member中的.不能被重载。 :: &#xff1a;作用域解析运算符。用于指定命名空间、类等的作用域&#xff0c;不能被重载。 ?: &#xff1…

spring揭秘19-spring事务01-事务抽象

文章目录 【README】【1】事务基本元素【1.1】事务分类 【2】java事务管理【2.1】基于java的局部事务管理【2.2】基于java的分布式事务管理【2.2.1】基于JTA的分布式事务管理【2.2.2】基于JCA的分布式事务管理 【2.3】java事务管理的问题 【3】spring事务抽象概述【3.1】spring…

easyExcel-读取Excel

1、简单读取&#xff0c;没有合并单元格 2、复杂读取&#xff0c;合并单元格-方法一 1、简单读取&#xff0c;没有合并单元格 1.1、引入pom文件 <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.…

【单片机】详细解析完全重映射和部分重映射

1、重映射的作用 单片机中的每个引脚通常都有多个功能&#xff0c;不仅仅是作为普通的输入或输出&#xff0c;还可以与某些外设&#xff08;如定时器、串口、SPI、I2C等&#xff09;关联。默认情况下&#xff0c;这些外设功能通常固定绑定到特定的GPIO引脚。但是&#xff0c;在…

Matplotlib颜色透明度设置

matplotlib中的透明度设置都是通过alpha设置的,一般在能设置颜色的包括背景色、图表色、文字颜色都可以设置透明度 float类型,取值范围为[0.0,1.0],alpha取值越小越透明 import numpy as np import matplotlib.pyplot as pltx np.linspace(0, 2*np.pi, 100) y np.sin(x) y1…

Arch - 架构安全性_授权(Authorization)

文章目录 OverView授权&#xff08;Authorization&#xff09;RBAC&#xff1a; 概述RBAC&#xff1a;基于角色的访问控制RBAC&#xff1a;主要元素 OAuth2&#xff1a;面向第三方应用的认证授权协议业务场景OAuth2的工作流程OAuth2 四种不同的授权方式授权码模式&#xff08;A…

Android Studio 2024最新版Hello World

Android Studio 2024最新版Hello World 1. Android Studio 2024安装视频2. 创建项目Read Timed out 问题Android Studio Build Output 控制台中文乱码问题 3. 驱动管理 本文章介绍如何通过Android Studio 2024最新版创建项目&#xff0c; 并成功输出Hello World。 本次教程版本…

关于QT中使用网络编程QtNetwork的问题

在此处添加network模块 在链接器中添加附加库目录(QT对应的lib)在链接器-输入中添加对应的lib库(Qt5Network.lib) 这样,就可以使用对应的下面的库文件了,比如: #include <qnetworkaccessmanager.h>

参数高效微调(PEFT)综述

人工智能咨询培训老师叶梓 转载标明出处 大模型如BERT和GPT-3的参数数量庞大&#xff0c;动辄数十亿甚至数千亿级别&#xff0c;给进一步的微调和应用带来了巨大的挑战。针对这一问题&#xff0c;Vladislav Lialin、Vijeta Deshpande、Anna Rumshisky等研究者中提出了一系列参…

Leetcode面试经典150题-69.X的平方根

相当简单的题目&#xff0c;但是出现的概率还挺高的 解法都在代码里&#xff0c;不懂就留言或者私信 class Solution {public int mySqrt(int x) {/**0的平方根是0 */if(x 0) {return 0;}/**1~3的平方根是1 */if(x < 3) {return 1;}/**其他情况我们采用二分查找&#xff…

Python 调用手机摄像头

Python 调用手机摄像头 在手机上安装软件 这里以安卓手机作为演示&#xff0c;ISO也是差不多的 软件下载地址 注意&#xff1a;要想在电脑上查看手机摄像头拍摄的内容的在一个局域网里面(没有 WIFI 可以使用 热点 ) 安装完打开IP摄像头服务器 点击分享查看IP 查看局域网的I…

Android Studio下载Gradle失败问题解决

问题说明 使用 Android Studio 构建程序报错如下 Could not install Gradle distribution from https://services.gradle.org/distributions/gradle-7.5.1-bin.zip. Reason: java.net.SocketTimeoutException: Connect timed out问题解决 下载对应版本的压缩包 gradle-7.5.1…

香橙派转换模型以及在开发板上部署

新手小白记录一下自己使用香橙派模型转换以及在开发板上运行的过程&#xff0c;防止后面忘记。 使用的开发板&#xff1a;Orange Pi 5 Plus&#xff08;rk3588&#xff09; 官方的一些资料在&#xff08;主要参考用户手册&#xff09;&#xff1a;Orange Pi - Orangepihttp:/…

价格适中超微小间距P1.8全彩LED显示屏广泛应用于COB会议一体机

随着科技的不断进步与市场需求的日益多样化&#xff0c;价格适中且具备超微小间距P1.8技术的全彩LED显示屏&#xff0c;在COB&#xff08;Chip On Board&#xff09;会议一体机领域的应用正逐步深化&#xff0c;引领着现代会议展示的新风尚。这种显示屏不仅以其细腻的画质和卓越…

单片机DMA原理及应用详解(下篇)(附工程源码)

这篇文章详细介绍单片机的DMA原理和应用范例。希望我的分享能给你带来不一样的收获&#xff01; 关于DMA的原理&#xff0c;可以看上一篇文章&#xff1a; 单片机DMA原理及应用详解&#xff08;上篇&#xff09;&#xff08;附工程源码&#xff09;-CSDN博客 目录 一、STM32单片…

黑龙江IBM Storwize v3700存储故障维修

中国黑龙江省佳木斯IBM Storwize v3700存储控制器故障维修案例&#xff1a; 报修单位&#xff1a;黑龙江佳木斯某某医院 存储型号&#xff1a;联想或IBM v3700 存储故障&#xff1a;无法正常访问存储磁盘&#xff0c;无法进存储web管理界面&#xff0c;可以进存储服务助手界…