当前位置: 首页 > news >正文

Android 12系统源码_屏幕设备(二)DisplayAdapter和DisplayDevice的创建

前言

在Android 12系统源码_屏幕设备(一)DisplayManagerService的启动这篇文章中我们具体分析了DisplayManagerService 的启动流程,本篇文章我们将在这个的基础上具体来分析下设备屏幕适配器的创建过程。

一、注册屏幕适配器

系统是在DisplayManagerService 的onStart方法中调用registerDefaultDisplayAdapters进行了默认屏幕适配器的注册,在systemReady方法中调用registerAdditionalDisplayAdapters进行额外屏幕适配器的注册。

frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java

public final class DisplayManagerService extends SystemService {private static final int MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS = 1;//注册物理屏幕适配器、虚拟屏幕适配器private static final int MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS = 2;//注册其他屏幕适配器@Overridepublic void onStart() {...代码省略...// 在android.display线程中创建默认DisplayAdapter,并进行注册mHandler.sendEmptyMessage(MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS);...代码省略...}public void systemReady(boolean safeMode, boolean onlyCore) {...代码省略...  //注册除了物理屏幕适配器、虚拟屏幕适配器以外的其他屏幕适配器mHandler.sendEmptyMessage(MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS);...代码省略...}private final class DisplayManagerHandler extends Handler {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case MSG_REGISTER_DEFAULT_DISPLAY_ADAPTERS://注册默认的屏幕适配器registerDefaultDisplayAdapters();break;case MSG_REGISTER_ADDITIONAL_DISPLAY_ADAPTERS://注册额外的屏幕设备适配器registerAdditionalDisplayAdapters();break;...代码省略...    }}}//注册默认的屏幕适配器private void registerDefaultDisplayAdapters() {synchronized (mSyncRoot) {//注册内置物理屏幕适配器registerDisplayAdapterLocked(new LocalDisplayAdapter(mSyncRoot, mContext, mHandler, mDisplayDeviceRepo));//注册虚拟屏幕适配器mVirtualDisplayAdapter = mInjector.getVirtualDisplayAdapter(mSyncRoot, mContext,mHandler, mDisplayDeviceRepo);if (mVirtualDisplayAdapter != null) {registerDisplayAdapterLocked(mVirtualDisplayAdapter);}}}//注册额外的屏幕适配器对象private void registerAdditionalDisplayAdapters() {synchronized (mSyncRoot) {if (shouldRegisterNonEssentialDisplayAdaptersLocked()) {registerOverlayDisplayAdapterLocked();//注册模拟辅助设备屏幕适配器registerWifiDisplayAdapterLocked();//注册WIFI屏幕适配器}}}}

二、注册默认屏幕适配器

注册内置物理屏幕适配器和虚拟屏幕适配器调用的都是registerDisplayAdapterLocked方法。

public final class DisplayManagerService extends SystemService {//当前已经注册的屏幕适配器集合private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();//虚拟屏幕适配器private VirtualDisplayAdapter mVirtualDisplayAdapter;//注册默认的屏幕适配器private void registerDefaultDisplayAdapters() {synchronized (mSyncRoot) {//注册内置物理屏幕适配器registerDisplayAdapterLocked(new LocalDisplayAdapter(mSyncRoot, mContext, mHandler, mDisplayDeviceRepo));//注册虚拟屏幕适配器mVirtualDisplayAdapter = mInjector.getVirtualDisplayAdapter(mSyncRoot, mContext,mHandler, mDisplayDeviceRepo);if (mVirtualDisplayAdapter != null) {registerDisplayAdapterLocked(mVirtualDisplayAdapter);}}}private void registerDisplayAdapterLocked(DisplayAdapter adapter) {mDisplayAdapters.add(adapter);//将适配器对象添加到mDisplayAdapters集合中adapter.registerLocked();//进行适配器注册操作}}

此方法先是创建内置物理屏幕适配器LocalDisplayAdapter对象实例,然后获取虚拟屏幕适配器VirtualDisplayAdapter对象实例。进一步调用registerDisplayAdapterLocked方法将其添加到屏幕适配器集合mDisplayAdapters中,然后还有调用每个适配器的registerLocked方法。

2.1 内置物理屏幕适配器

注册内置物理屏幕适配器先是创建LocalDisplayAdapter对象,然后调用该对象的registerLocked方法。

frameworks/base/services/core/java/com/android/server/display/LocalDisplayAdapter.java

final class LocalDisplayAdapter extends DisplayAdapter {public LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,Context context, Handler handler, Listener listener) {this(syncRoot, context, handler, listener, new Injector());}@VisibleForTestingLocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,Context context, Handler handler, Listener listener, Injector injector) {super(syncRoot, context, handler, listener, TAG);//父类构造方法mInjector = injector;mSurfaceControlProxy = mInjector.getSurfaceControlProxy();}@Overridepublic void registerLocked() {super.registerLocked();mInjector.setDisplayEventListenerLocked(getHandler().getLooper(),new LocalDisplayEventListener());// 从SurfaceControl中获取物理屏幕idfor (long physicalDisplayId : mSurfaceControlProxy.getPhysicalDisplayIds()) {//尝试连接物理屏幕tryConnectDisplayLocked(physicalDisplayId);}}private void tryConnectDisplayLocked(long physicalDisplayId) {// 根据id获取当前物理屏幕的令牌final IBinder displayToken = mSurfaceControlProxy.getPhysicalDisplayToken(physicalDisplayId);if (displayToken != null) {// 根据token获取当前物理屏幕的配置项SurfaceControl.StaticDisplayInfo staticInfo =  mSurfaceControlProxy.getStaticDisplayInfo(displayToken);if (staticInfo == null) {Slog.w(TAG, "No valid static info found for display device " + physicalDisplayId);return;}SurfaceControl.DynamicDisplayInfo dynamicInfo = mSurfaceControlProxy.getDynamicDisplayInfo(displayToken);if (dynamicInfo == null) {Slog.w(TAG, "No valid dynamic info found for display device " + physicalDisplayId);return;}if (dynamicInfo.supportedDisplayModes == null) {// There are no valid modes for this device, so we can't use itSlog.w(TAG, "No valid modes found for display device " + physicalDisplayId);return;}if (dynamicInfo.activeDisplayModeId < 0) {// There is no active mode, and for now we don't have the// policy to set one.Slog.w(TAG, "No valid active mode found for display device " + physicalDisplayId);return;}if (dynamicInfo.activeColorMode < 0) {// We failed to get the active color mode. We don't bail out here since on the next// configuration pass we'll go ahead and set it to whatever it was set to last (or// COLOR_MODE_NATIVE if this is the first configuration).Slog.w(TAG, "No valid active color mode for display device " + physicalDisplayId);dynamicInfo.activeColorMode = Display.COLOR_MODE_INVALID;}SurfaceControl.DesiredDisplayModeSpecs modeSpecs =mSurfaceControlProxy.getDesiredDisplayModeSpecs(displayToken);// 根据id从mDevices数组中获取对应的LocalDisplayDeviceLocalDisplayDevice device = mDevices.get(physicalDisplayId);if (device == null) {//是否是默认屏幕final boolean isDefaultDisplay = mDevices.size() == 0;// 创建LocalDisplayDevicedevice = new LocalDisplayDevice(displayToken, physicalDisplayId, staticInfo,dynamicInfo, modeSpecs, isDefaultDisplay);mDevices.put(physicalDisplayId, device);//通知DMS更新DisplayDevice事件,新增屏幕设备sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);} else if (device.updateDisplayPropertiesLocked(staticInfo, dynamicInfo,modeSpecs)) {//通知DMS更新DisplayDevice事件,屏幕设备属性发生变化sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);}} else {// The display is no longer available. Ignore the attempt to add it.// If it was connected but has already been disconnected, we'll get a// disconnect event that will remove it from mDevices.}}
}

2.1.1 构造方法

LocalDisplayAdapter的构造方法很简单,主要属性的赋值都在其父类DisplayAdapter中。

final class LocalDisplayAdapter extends DisplayAdapter {public LocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,Context context, Handler handler, Listener listener) {this(syncRoot, context, handler, listener, new Injector());}@VisibleForTestingLocalDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,Context context, Handler handler, Listener listener, Injector injector) {super(syncRoot, context, handler, listener, TAG);//父类构造方法mInjector = injector;mSurfaceControlProxy = mInjector.getSurfaceControlProxy();}
}

frameworks/base/services/core/java/com/android/server/display/DisplayAdapter.java

abstract class DisplayAdapter {private final DisplayManagerService.SyncRoot mSyncRoot;private final Context mContext;private final Handler mHandler;private final Listener mListener;private final String mName;// Called with SyncRoot lock held.public DisplayAdapter(DisplayManagerService.SyncRoot syncRoot,Context context, Handler handler, Listener listener, String name) {//DMS模块全局同步锁mSyncRoot = syncRoot;mContext = context;//android.display线程的HandlermHandler = handler;//DislayApdater.Listener对象,用于回调DMSmListener = listener;mName = name;}public void registerLocked() {}
}

2.1.2 创建物理屏对象

创建好物理屏幕适配器对象后,会执行该对象的registerLocked()方法。

final class LocalDisplayAdapter extends DisplayAdapter {@Overridepublic void registerLocked() {super.registerLocked();mInjector.setDisplayEventListenerLocked(getHandler().getLooper(), new LocalDisplayEventListener());// 从SurfaceControl中获取物理屏幕idfor (long physicalDisplayId : mSurfaceControlProxy.getPhysicalDisplayIds()) {//尝试连接物理屏幕tryConnectDisplayLocked(physicalDisplayId);}}
}

registerLocked方法首先通过SurfaceControl获得所有的物理显示屏幕id,然后依次执行tryConnectDisplayLocked()方法,根据id创建对应的物理显屏幕和DMS进行连接。

final class LocalDisplayAdapter extends DisplayAdapter {private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();private void tryConnectDisplayLocked(long physicalDisplayId) {// 根据id获取当前物理屏幕的令牌final IBinder displayToken = mSurfaceControlProxy.getPhysicalDisplayToken(physicalDisplayId);if (displayToken != null) {// 根据token获取当前物理屏幕的配置项SurfaceControl.StaticDisplayInfo staticInfo =  mSurfaceControlProxy.getStaticDisplayInfo(displayToken);if (staticInfo == null) {Slog.w(TAG, "No valid static info found for display device " + physicalDisplayId);return;}SurfaceControl.DynamicDisplayInfo dynamicInfo = mSurfaceControlProxy.getDynamicDisplayInfo(displayToken);if (dynamicInfo == null) {Slog.w(TAG, "No valid dynamic info found for display device " + physicalDisplayId);return;}if (dynamicInfo.supportedDisplayModes == null) {// There are no valid modes for this device, so we can't use itSlog.w(TAG, "No valid modes found for display device " + physicalDisplayId);return;}if (dynamicInfo.activeDisplayModeId < 0) {// There is no active mode, and for now we don't have the// policy to set one.Slog.w(TAG, "No valid active mode found for display device " + physicalDisplayId);return;}if (dynamicInfo.activeColorMode < 0) {// We failed to get the active color mode. We don't bail out here since on the next// configuration pass we'll go ahead and set it to whatever it was set to last (or// COLOR_MODE_NATIVE if this is the first configuration).Slog.w(TAG, "No valid active color mode for display device " + physicalDisplayId);dynamicInfo.activeColorMode = Display.COLOR_MODE_INVALID;}SurfaceControl.DesiredDisplayModeSpecs modeSpecs =mSurfaceControlProxy.getDesiredDisplayModeSpecs(displayToken);// 根据id从mDevices数组中获取对应的LocalDisplayDeviceLocalDisplayDevice device = mDevices.get(physicalDisplayId);if (device == null) {//是否是默认屏幕final boolean isDefaultDisplay = mDevices.size() == 0;// 创建LocalDisplayDevicedevice = new LocalDisplayDevice(displayToken, physicalDisplayId, staticInfo,dynamicInfo, modeSpecs, isDefaultDisplay);mDevices.put(physicalDisplayId, device);//通知DMS更新DisplayDevice事件,新增屏幕设备sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);} else if (device.updateDisplayPropertiesLocked(staticInfo, dynamicInfo,modeSpecs)) {//通知DMS更新DisplayDevice事件,屏幕设备属性发生变化sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);}} else {// The display is no longer available. Ignore the attempt to add it.// If it was connected but has already been disconnected, we'll get a// disconnect event that will remove it from mDevices.}}
}

这个方法先是从SurfaceControler中获取多个物理显示相关的配置属性,然后根据物理设备id从mDevices数组中获取对应的LocalDisplayDevice,如果不存在则会进行创建,物理屏幕设备对象LocalDisplayDevice是LocalDisplayAdapter的静态内部类。

final class LocalDisplayAdapter extends DisplayAdapter {//物理屏幕设备对象private final class LocalDisplayDevice extends DisplayDevice {private final long mPhysicalDisplayId;private final SparseArray<DisplayModeRecord> mSupportedModes = new SparseArray<>();private final ArrayList<Integer> mSupportedColorModes = new ArrayList<>();private final boolean mIsDefaultDisplay;//是否是默认屏幕private final BacklightAdapter mBacklightAdapter;//背光适配器private DisplayDeviceInfo mInfo;//屏幕设备信息private boolean mHavePendingChanges;private int mState = Display.STATE_UNKNOWN;// This is only set in the runnable returned from requestDisplayStateLocked.private float mBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;private float mSdrBrightnessState = PowerManager.BRIGHTNESS_INVALID_FLOAT;private int mDefaultModeId;private int mDefaultModeGroup;private int mActiveModeId;private DisplayModeDirector.DesiredDisplayModeSpecs mDisplayModeSpecs =new DisplayModeDirector.DesiredDisplayModeSpecs();private boolean mDisplayModeSpecsInvalid;private int mActiveColorMode;private Display.HdrCapabilities mHdrCapabilities;private boolean mAllmSupported;private boolean mGameContentTypeSupported;private boolean mAllmRequested;private boolean mGameContentTypeRequested;private boolean mSidekickActive;private SidekickInternal mSidekickInternal;private SurfaceControl.StaticDisplayInfo mStaticDisplayInfo;// The supported display modes according in SurfaceFlingerprivate SurfaceControl.DisplayMode[] mSfDisplayModes;// The active display mode in SurfaceFlingerprivate SurfaceControl.DisplayMode mActiveSfDisplayMode;private DisplayDeviceConfig mDisplayDeviceConfig;private DisplayEventReceiver.FrameRateOverride[] mFrameRateOverrides =new DisplayEventReceiver.FrameRateOverride[0];LocalDisplayDevice(IBinder displayToken, long physicalDisplayId,SurfaceControl.StaticDisplayInfo staticDisplayInfo,SurfaceControl.DynamicDisplayInfo dynamicInfo,SurfaceControl.DesiredDisplayModeSpecs modeSpecs, boolean isDefaultDisplay) {// 设置mDisplayAdapter、mDisplayToken、mUniqueId三个属性super(LocalDisplayAdapter.this, displayToken, UNIQUE_ID_PREFIX + physicalDisplayId,getContext());//物理设备屏幕idmPhysicalDisplayId = physicalDisplayId;// 是否是默认屏mIsDefaultDisplay = isDefaultDisplay;// 更新物理屏配置,物理屏配置、色彩模式、HDR模式updateDisplayPropertiesLocked(staticDisplayInfo, dynamicInfo, modeSpecs);mSidekickInternal = LocalServices.getService(SidekickInternal.class);//背光适配器mBacklightAdapter = new BacklightAdapter(displayToken, isDefaultDisplay,mSurfaceControlProxy);mDisplayDeviceConfig = null;}}
}

在创建物理屏幕设备对象或者更新物理屏幕设备对象属性后,都会调用sendDisplayDeviceEventLocked方法,通知DMS屏幕设备状态发生变化。

2.2、注册虚拟屏幕适配器

public class VirtualDisplayAdapter extends DisplayAdapter {public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,Context context, Handler handler, Listener listener) {this(syncRoot, context, handler, listener,(String name, boolean secure) -> SurfaceControl.createDisplay(name, secure));}@VisibleForTestingVirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,Context context, Handler handler, Listener listener,SurfaceControlDisplayFactory surfaceControlDisplayFactory) {super(syncRoot, context, handler, listener, TAG);mHandler = handler;mSurfaceControlDisplayFactory = surfaceControlDisplayFactory;}}

VirtualDisplayAdapter的构造方法同样很简单,主要属性的赋值同样是依赖于其父类DisplayAdapter,值得一提的是该类没有重新父类的registerLocked方法,意味着registerLocked方法是空实现。

三、 通知DMS屏幕设备状态发生变化

前面注册内置物理屏幕适配器的最后有提到sendDisplayDeviceEventLocked方法,来看下该方法。

abstract class DisplayAdapter {private final Listener mListener;public interface Listener {void onDisplayDeviceEvent(DisplayDevice device, int event);void onTraversalRequested();}/*** 发送一个屏幕设备事件给屏幕设备适配器的监听者*/protected final void sendDisplayDeviceEventLocked(final DisplayDevice device, final int event) {mHandler.post(() -> mListener.onDisplayDeviceEvent(device, event));}
}    

3.1 DisplayDeviceRepository阶段

DisplayDeviceRepository这个类实现了DisplayAdapter.Listener的回调方法。

frameworks/base/services/core/java/com/android/server/display/DisplayDeviceRepository.java

class DisplayDeviceRepository implements DisplayAdapter.Listener {private final List<DisplayDevice> mDisplayDevices = new ArrayList<>();@Overridepublic void onDisplayDeviceEvent(DisplayDevice device, int event) {switch (event) {case DISPLAY_DEVICE_EVENT_ADDED://新增屏幕设备handleDisplayDeviceAdded(device);break;case DISPLAY_DEVICE_EVENT_CHANGED://屏幕设备属性发生变化handleDisplayDeviceChanged(device);break;case DISPLAY_DEVICE_EVENT_REMOVED://屏幕设备被移除handleDisplayDeviceRemoved(device);break;}}private void handleDisplayDeviceAdded(DisplayDevice device) {synchronized (mSyncRoot) {DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();if (mDisplayDevices.contains(device)) {Slog.w(TAG, "Attempted to add already added display device: " + info);return;}Slog.i(TAG, "Display device added: " + info);device.mDebugLastLoggedDeviceInfo = info;mDisplayDevices.add(device);//调用sendEventLocked方法发送消息事件sendEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);}}private void handleDisplayDeviceChanged(DisplayDevice device) {synchronized (mSyncRoot) {final DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();if (!mDisplayDevices.contains(device)) {Slog.w(TAG, "Attempted to change non-existent display device: " + info);return;}int diff = device.mDebugLastLoggedDeviceInfo.diff(info);if (diff == DisplayDeviceInfo.DIFF_STATE) {Slog.i(TAG, "Display device changed state: \"" + info.name+ "\", " + Display.stateToString(info.state));} else if (diff != 0) {Slog.i(TAG, "Display device changed: " + info);}if ((diff & DisplayDeviceInfo.DIFF_COLOR_MODE) != 0) {try {mPersistentDataStore.setColorMode(device, info.colorMode);} finally {mPersistentDataStore.saveIfNeeded();}}device.mDebugLastLoggedDeviceInfo = info;device.applyPendingDisplayDeviceInfoChangesLocked();//调用sendEventLocked方法发送消息事件sendEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);}}private void handleDisplayDeviceRemoved(DisplayDevice device) {synchronized (mSyncRoot) {DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked();if (!mDisplayDevices.remove(device)) {Slog.w(TAG, "Attempted to remove non-existent display device: " + info);return;}Slog.i(TAG, "Display device removed: " + info);device.mDebugLastLoggedDeviceInfo = info;//调用sendEventLocked方法发送消息事件sendEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED);}}}

DisplayDeviceRepository的onDisplayDeviceEvent方法会根据收到的事件类型分别做处理:

  • 新增屏幕设备,调用handleDisplayDeviceAdded方法,
  • 屏幕设备属性发生变化,调用handleDisplayDeviceChanged方法
  • 屏幕设备被移除,调用handleDisplayDeviceRemoved方法。

以上三种场景最终都会进一步触发sendEventLocked方法。

class DisplayDeviceRepository implements DisplayAdapter.Listener {//屏幕设备状态发生变化事件监听者private final List<Listener> mListeners = new ArrayList<>();private void sendEventLocked(DisplayDevice device, int event) {final int size = mListeners.size();for (int i = 0; i < size; i++) {//进一步触发回调对象的onDisplayDeviceEventLocked方法mListeners.get(i).onDisplayDeviceEventLocked(device, event);}}/*** Listens to {@link DisplayDevice} events from {@link DisplayDeviceRepository}.*/public interface Listener {void onDisplayDeviceEventLocked(DisplayDevice device, int event);// TODO: multi-display - Try to remove the need for requestTraversal...it feels like// a shoe-horned method for a shoe-horned feature.void onTraversalRequested();};
}    

LogicalDisplayMapper实现了DisplayDeviceRepository.Listener这个回调。

3.2 LogicalDisplayMapper阶段

LogicalDisplayMapper是系统逻辑屏幕设备对象的管理者。

frameworks/base/services/core/java/com/android/server/display/LogicalDisplayMapper.java

class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,@NonNull Handler handler) {mSyncRoot = syncRoot;mPowerManager = context.getSystemService(PowerManager.class);mHandler = new LogicalDisplayMapperHandler(handler.getLooper());mDisplayDeviceRepo = repo;mListener = listener;mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false);mSupportsConcurrentInternalDisplays = context.getResources().getBoolean(com.android.internal.R.bool.config_supportsConcurrentInternalDisplays);mDeviceStateOnWhichToWakeUp = context.getResources().getInteger(com.android.internal.R.integer.config_deviceStateOnWhichToWakeUp);mDisplayDeviceRepo.addListener(this);mDeviceStateToLayoutMap = new DeviceStateToLayoutMap();}@Overridepublic void onDisplayDeviceEventLocked(DisplayDevice device, int event) {switch (event) {case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_ADDED://新增屏幕设备if (DEBUG) {Slog.d(TAG, "Display device added: " + device.getDisplayDeviceInfoLocked());}handleDisplayDeviceAddedLocked(device);//break;case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_CHANGED://屏幕设备属性发生变化if (DEBUG) {Slog.d(TAG, "Display device changed: " + device.getDisplayDeviceInfoLocked());}finishStateTransitionLocked(false /*force*/);updateLogicalDisplaysLocked();//更新逻辑屏幕设备的属性break;case DisplayDeviceRepository.DISPLAY_DEVICE_EVENT_REMOVED://移除屏幕设备if (DEBUG) {Slog.d(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked());}updateLogicalDisplaysLocked();//更新逻辑屏幕设备的属性break;}}
}

LogicalDisplayMapper的onDisplayDeviceEventLocked方法也会根据收到的事件类型做分流处理:

  • 新增屏幕设备,调用handleDisplayDeviceAddedLocked方法,
  • 屏幕设备被移除或者屏幕设备属性发生变化,调用updateLogicalDisplaysLocked方法

3.2.1 新增逻辑屏

class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {private void handleDisplayDeviceAddedLocked(DisplayDevice device) {// 获取DisplayDeviceInfo对象DisplayDeviceInfo deviceInfo = device.getDisplayDeviceInfoLocked();// Internal Displays need to have additional initialization.// This initializes a default dynamic display layout for INTERNAL// devices, which is used as a fallback in case no static layout definitions// exist or cannot be loaded.if (deviceInfo.type == Display.TYPE_INTERNAL) {initializeInternalDisplayDeviceLocked(device);}// 创建逻辑屏对象LogicalDisplay display = createNewLogicalDisplayLocked(device, Layout.assignDisplayIdLocked(false /*isDefault*/));applyLayoutLocked();updateLogicalDisplaysLocked();}//创建逻辑屏幕private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {final int layerStack = assignLayerStackLocked(displayId);final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);display.updateLocked(mDisplayDeviceRepo);mLogicalDisplays.put(displayId, display);setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_ENABLED);return display;}
}

3.2.2 更新逻辑屏

class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {private void updateLogicalDisplaysLocked() {// Go through all the displays and figure out if they need to be updated.// Loops in reverse so that displays can be removed during the loop without affecting the// rest of the loop.for (int i = mLogicalDisplays.size() - 1; i >= 0; i--) {final int displayId = mLogicalDisplays.keyAt(i);LogicalDisplay display = mLogicalDisplays.valueAt(i);mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked());display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo);display.updateLocked(mDisplayDeviceRepo);final DisplayInfo newDisplayInfo = display.getDisplayInfoLocked();final int updateState = mUpdatedLogicalDisplays.get(displayId, UPDATE_STATE_NEW);final boolean wasPreviouslyUpdated = updateState != UPDATE_STATE_NEW;// The display is no longer valid and needs to be removed.if (!display.isValidLocked()) {mUpdatedLogicalDisplays.delete(displayId);// Remove from groupfinal DisplayGroup displayGroup = getDisplayGroupLocked(getDisplayGroupIdFromDisplayIdLocked(displayId));if (displayGroup != null) {displayGroup.removeDisplayLocked(display);}if (wasPreviouslyUpdated) {// The display isn't actually removed from our internal data structures until// after the notification is sent; see {@link #sendUpdatesForDisplaysLocked}.Slog.i(TAG, "Removing display: " + displayId);mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_REMOVED);} else {// This display never left this class, safe to remove without notificationmLogicalDisplays.removeAt(i);}continue;// The display is new.} else if (!wasPreviouslyUpdated) {Slog.i(TAG, "Adding new display: " + displayId + ": " + newDisplayInfo);assignDisplayGroupLocked(display);mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_ADDED);// Underlying displays device has changed to a different one.} else if (!TextUtils.equals(mTempDisplayInfo.uniqueId, newDisplayInfo.uniqueId)) {// FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in caseassignDisplayGroupLocked(display);mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_SWAPPED);// Something about the display device has changed.} else if (!mTempDisplayInfo.equals(newDisplayInfo)) {// FLAG_OWN_DISPLAY_GROUP could have changed, recalculate just in caseassignDisplayGroupLocked(display);mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);// The display is involved in a display layout transition} else if (updateState == UPDATE_STATE_TRANSITION) {mLogicalDisplaysToUpdate.put(displayId,LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION);// Display frame rate overrides changed.} else if (!display.getPendingFrameRateOverrideUids().isEmpty()) {mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);// Non-override display values changed.} else {// While application shouldn't know nor care about the non-overridden info, we// still need to let WindowManager know so it can update its own internal state for// things like display cutouts.display.getNonOverrideDisplayInfoLocked(mTempDisplayInfo);if (!mTempNonOverrideDisplayInfo.equals(mTempDisplayInfo)) {mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);}}mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_UPDATED);}// Go through the groups and do the same thing. We do this after displays since group// information can change in the previous loop.// Loops in reverse so that groups can be removed during the loop without affecting the// rest of the loop.for (int i = mDisplayGroups.size() - 1; i >= 0; i--) {final int groupId = mDisplayGroups.keyAt(i);final DisplayGroup group = mDisplayGroups.valueAt(i);final boolean wasPreviouslyUpdated = mUpdatedDisplayGroups.indexOfKey(groupId) > -1;final int changeCount = group.getChangeCountLocked();if (group.isEmptyLocked()) {mUpdatedDisplayGroups.delete(groupId);if (wasPreviouslyUpdated) {mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_REMOVED);}continue;} else if (!wasPreviouslyUpdated) {mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_ADDED);} else if (mUpdatedDisplayGroups.get(groupId) != changeCount) {mDisplayGroupsToUpdate.put(groupId, DISPLAY_GROUP_EVENT_CHANGED);}mUpdatedDisplayGroups.put(groupId, changeCount);}// Send the display and display group updates in order by message type. This is important// to ensure that addition and removal notifications happen in the right order.sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION);sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_ADDED);sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_REMOVED);sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_CHANGED);sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_FRAME_RATE_OVERRIDES_CHANGED);sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_SWAPPED);sendUpdatesForDisplaysLocked(LOGICAL_DISPLAY_EVENT_ADDED);sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_CHANGED);sendUpdatesForGroupsLocked(DISPLAY_GROUP_EVENT_REMOVED);mLogicalDisplaysToUpdate.clear();mDisplayGroupsToUpdate.clear();}
}

四、注册额外屏幕适配器

前面我们有提到,DMS有在systemReady方法中调用registerAdditionalDisplayAdapters方法来进行额外屏幕适配器的注册。

public final class DisplayManagerService extends SystemService {//当前已经注册的屏幕适配器集合private final ArrayList<DisplayAdapter> mDisplayAdapters = new ArrayList<DisplayAdapter>();//The Wifi display adapter, or null if not registered.private WifiDisplayAdapter mWifiDisplayAdapter;//注册额外的屏幕适配器对象private void registerAdditionalDisplayAdapters() {synchronized (mSyncRoot) {if (shouldRegisterNonEssentialDisplayAdaptersLocked()) {registerOverlayDisplayAdapterLocked();//注册模拟辅助设备屏幕适配器registerWifiDisplayAdapterLocked();//注册WIFI屏幕适配器}}}//注册模拟辅助屏幕设备被适配器private void registerOverlayDisplayAdapterLocked() {registerDisplayAdapterLocked(new OverlayDisplayAdapter(mSyncRoot, mContext, mHandler, mDisplayDeviceRepo, mUiHandler));}//注册Wifi屏幕设备适配器private void registerWifiDisplayAdapterLocked() {if (mContext.getResources().getBoolean(com.android.internal.R.bool.config_enableWifiDisplay)|| SystemProperties.getInt(FORCE_WIFI_DISPLAY_ENABLE, -1) == 1) {mWifiDisplayAdapter = new WifiDisplayAdapter(mSyncRoot, mContext, mHandler, mDisplayDeviceRepo,mPersistentDataStore);registerDisplayAdapterLocked(mWifiDisplayAdapter);}}private void registerDisplayAdapterLocked(DisplayAdapter adapter) {mDisplayAdapters.add(adapter);//将适配器对象添加到mDisplayAdapters集合中adapter.registerLocked();//进行适配器注册操作}}

4.1 模拟辅助设备适配器

注册模拟辅助设备适配器需要先创建OverlayDisplayAdapter对象,然后调用该对象的registerLocked方法。

frameworks/base/services/core/java/com/android/server/display/OverlayDisplayAdapter.java

final class OverlayDisplayAdapter extends DisplayAdapter {public OverlayDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,Context context, Handler handler, Listener listener, Handler uiHandler) {super(syncRoot, context, handler, listener, TAG);mUiHandler = uiHandler;}@Overridepublic void registerLocked() {super.registerLocked();getHandler().post(new Runnable() {@Overridepublic void run() {//监听global数据库overlay_display_devices字段的变化getContext().getContentResolver().registerContentObserver(Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),true, new ContentObserver(getHandler()) {@Overridepublic void onChange(boolean selfChange) {updateOverlayDisplayDevices();}});//更新当前模拟辅助设备updateOverlayDisplayDevices();}});}}

OverlayDisplayAdapter的registerLocked方法仅仅是对global数据库overlay_display_devices字段的变化进行了监听,初次以及后续该字段变化的时候都会调用updateOverlayDisplayDevices方法,关于overlay_display_devices这个字段的变化逻辑,我们在Android 12系统源码_多屏幕(二)模拟辅助设备功能开关实现原理这篇文章有详细分析过。

final class OverlayDisplayAdapter extends DisplayAdapter {private final ArrayList<OverlayDisplayHandle> mOverlays = new ArrayList<OverlayDisplayHandle>();//更新模拟辅助屏幕设备private void updateOverlayDisplayDevices() {synchronized (getSyncRoot()) {updateOverlayDisplayDevicesLocked();}}private void updateOverlayDisplayDevicesLocked() {//获取当前overlay_display_devices的属性值,例如【1920x1080/320】String value = Settings.Global.getString(getContext().getContentResolver(),Settings.Global.OVERLAY_DISPLAY_DEVICES);//如果为空直接返回if (value == null) {value = "";}//如果没有发生变化直接返回if (value.equals(mCurrentOverlaySetting)) {return;}mCurrentOverlaySetting = value;//清除目前已经存在的所有模拟辅助显示设备if (!mOverlays.isEmpty()) {Slog.i(TAG, "Dismissing all overlay display devices.");for (OverlayDisplayHandle overlay : mOverlays) {overlay.dismissLocked();}mOverlays.clear();}//对overlay_display_devices字段的内容进行解析int count = 0;for (String part : value.split(DISPLAY_SPLITTER)) {Matcher displayMatcher = DISPLAY_PATTERN.matcher(part);if (displayMatcher.matches()) {if (count >= 4) {Slog.w(TAG, "Too many overlay display devices specified: " + value);break;}String modeString = displayMatcher.group(1);String flagString = displayMatcher.group(2);//将字符串转化为OverlayMode集合ArrayList<OverlayMode> modes = new ArrayList<>();for (String mode : modeString.split(MODE_SPLITTER)) {Matcher modeMatcher = MODE_PATTERN.matcher(mode);if (modeMatcher.matches()) {try {int width = Integer.parseInt(modeMatcher.group(1), 10);int height = Integer.parseInt(modeMatcher.group(2), 10);int densityDpi = Integer.parseInt(modeMatcher.group(3), 10);if (width >= MIN_WIDTH && width <= MAX_WIDTH&& height >= MIN_HEIGHT && height <= MAX_HEIGHT&& densityDpi >= DisplayMetrics.DENSITY_LOW&& densityDpi <= DisplayMetrics.DENSITY_XXXHIGH) {modes.add(new OverlayMode(width, height, densityDpi));continue;} else {Slog.w(TAG, "Ignoring out-of-range overlay display mode: " + mode);}} catch (NumberFormatException ex) {}} else if (mode.isEmpty()) {continue;}}//解析OverlayMode集合if (!modes.isEmpty()) {int number = ++count;String name = getContext().getResources().getString(com.android.internal.R.string.display_manager_overlay_display_name,number);int gravity = chooseOverlayGravity(number);OverlayFlags flags = OverlayFlags.parseFlags(flagString);Slog.i(TAG, "Showing overlay display device #" + number+ ": name=" + name + ", modes=" + Arrays.toString(modes.toArray())+ ", flags=" + flags);//为其创建OverlayDisplayHandle对象,并将该对象添加到mOverlays集合中mOverlays.add(new OverlayDisplayHandle(name, modes, gravity, flags, number));continue;}}Slog.w(TAG, "Malformed overlay display devices setting: " + value);}}private final class OverlayDisplayHandle implements OverlayDisplayWindow.Listener {private static final int DEFAULT_MODE_INDEX = 0;private final String mName;private final List<OverlayMode> mModes;private final int mGravity;private final OverlayFlags mFlags;private final int mNumber;private OverlayDisplayWindow mWindow;private OverlayDisplayDevice mDevice;private int mActiveMode;OverlayDisplayHandle(String name,List<OverlayMode> modes,int gravity,OverlayFlags flags,int number) {mName = name;mModes = modes;mGravity = gravity;mFlags = flags;mNumber = number;mActiveMode = 0;showLocked();//显示模拟辅助屏幕设备}private void showLocked() {//保证mShowRunnable是运行在UI线程中的mUiHandler.post(mShowRunnable);}public void dismissLocked() {//移除显示模拟辅助屏幕设备的RunnablemUiHandler.removeCallbacks(mShowRunnable);//执行销毁模拟辅助屏幕设备的RunnablemUiHandler.post(mDismissRunnable);}private void onActiveModeChangedLocked(int index) {mUiHandler.removeCallbacks(mResizeRunnable);mActiveMode = index;if (mWindow != null) {mUiHandler.post(mResizeRunnable);}}// Called on the UI thread.@Overridepublic void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate,long presentationDeadlineNanos, int state) {synchronized (getSyncRoot()) {IBinder displayToken = SurfaceControl.createDisplay(mName, mFlags.mSecure);mDevice = new OverlayDisplayDevice(displayToken, mName, mModes, mActiveMode,DEFAULT_MODE_INDEX, refreshRate, presentationDeadlineNanos,mFlags, state, surfaceTexture, mNumber) {@Overridepublic void onModeChangedLocked(int index) {onActiveModeChangedLocked(index);}};sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);}}// Called on the UI thread.@Overridepublic void onWindowDestroyed() {synchronized (getSyncRoot()) {if (mDevice != null) {mDevice.destroyLocked();sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);}}}// Called on the UI thread.@Overridepublic void onStateChanged(int state) {synchronized (getSyncRoot()) {if (mDevice != null) {mDevice.setStateLocked(state);sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_CHANGED);}}}public void dumpLocked(PrintWriter pw) {pw.println("  " + mName + ":");pw.println("    mModes=" + Arrays.toString(mModes.toArray()));pw.println("    mActiveMode=" + mActiveMode);pw.println("    mGravity=" + mGravity);pw.println("    mFlags=" + mFlags);pw.println("    mNumber=" + mNumber);// Try to dump the window state.if (mWindow != null) {final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");ipw.increaseIndent();DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, "", 200);}}// Runs on the UI thread. 显示窗口private final Runnable mShowRunnable = new Runnable() {@Overridepublic void run() {OverlayMode mode = mModes.get(mActiveMode);OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(),mName, mode.mWidth, mode.mHeight, mode.mDensityDpi, mGravity,mFlags.mSecure, OverlayDisplayHandle.this);window.show();synchronized (getSyncRoot()) {mWindow = window;}}};// Runs on the UI thread. 关闭窗口private final Runnable mDismissRunnable = new Runnable() {@Overridepublic void run() {OverlayDisplayWindow window;synchronized (getSyncRoot()) {window = mWindow;mWindow = null;}if (window != null) {window.dismiss();}}};// Runs on the UI thread. 缩放窗口private final Runnable mResizeRunnable = new Runnable() {@Overridepublic void run() {OverlayMode mode;OverlayDisplayWindow window;synchronized (getSyncRoot()) {if (mWindow == null) {return;}mode = mModes.get(mActiveMode);window = mWindow;}window.resize(mode.mWidth, mode.mHeight, mode.mDensityDpi);}};}private static final class OverlayMode {final int mWidth;//宽度final int mHeight;//高度final int mDensityDpi;//像素密度OverlayMode(int width, int height, int densityDpi) {mWidth = width;mHeight = height;mDensityDpi = densityDpi;}}
}

4.2 WIIFI屏幕设备适配器

注册WIFI设备适配器需要先创建WifiDisplayAdapter对象,然后调用该对象的registerLocked方法。

frameworks/base/services/core/java/com/android/server/display/WifiDisplayAdapter.java

final class WifiDisplayAdapter extends DisplayAdapter {private static final String ACTION_DISCONNECT = "android.server.display.wfd.DISCONNECT";private WifiDisplayController mDisplayController;public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,Context context, Handler handler, Listener listener,PersistentDataStore persistentDataStore) {super(syncRoot, context, handler, listener, TAG);if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT)) {throw new RuntimeException("WiFi display was requested, "+ "but there is no WiFi Direct feature");}mHandler = new WifiDisplayHandler(handler.getLooper());mPersistentDataStore = persistentDataStore;mSupportsProtectedBuffers = context.getResources().getBoolean(com.android.internal.R.bool.config_wifiDisplaySupportsProtectedBuffers);}@Overridepublic void registerLocked() {super.registerLocked();updateRememberedDisplaysLocked();getHandler().post(new Runnable() {@Overridepublic void run() {//创建WIFI屏幕设备控制器mDisplayController = new WifiDisplayController(getContext(), getHandler(), mWifiDisplayListener);//注册广播接受者getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,new IntentFilter(ACTION_DISCONNECT), null, mHandler);}});}private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {@Overridepublic void onReceive(Context context, Intent intent) {if (intent.getAction().equals(ACTION_DISCONNECT)) {synchronized (getSyncRoot()) {//请求断开连接requestDisconnectLocked();}}}};public void requestDisconnectLocked() {getHandler().post(new Runnable() {@Overridepublic void run() {if (mDisplayController != null) {//请求断开连接mDisplayController.requestDisconnect();}}});}
}

WifiDisplayAdapter的registerLocked方法先是创建WifiDisplayController对象,然后注册广播接受者,监听android.server.display.wfd.DISCONNECT广播事件,当收到该广播的时候执行requestDisconnectLocked方法,该方法最终是调用WifiDisplayController的requestDisconnect方法。

五、总结

方法调用时序图

相关文章:

Android 12系统源码_屏幕设备(二)DisplayAdapter和DisplayDevice的创建

前言 在Android 12系统源码_屏幕设备&#xff08;一&#xff09;DisplayManagerService的启动这篇文章中我们具体分析了DisplayManagerService 的启动流程&#xff0c;本篇文章我们将在这个的基础上具体来分析下设备屏幕适配器的创建过程。 一、注册屏幕适配器 系统是在Disp…...

常用Mysql命令

前言 本文列举了一些常见的mysql操作 正文 一、连接和登录 MySQL 1. 使用命令行登录 MySQL 注意&#xff1a;需要将mysql的bin目录导入到环境变量中 mysql -u 用户名 -p示例&#xff1a; mysql -u root -p执行上述命令后&#xff0c;系统会提示输入密码&#xff0c;输入…...

IDEA Debug工具

一、Debug工具栏 自定义debug工具栏&#xff1a;先把debug程序运行起来->右击->配置 常用的工具&#xff1a; 二、DeBug常用图标详解 三、DeBug实践操作 常规Debug&#xff1a;略。 Stream Chain&#xff1a;处理流式语句 Reset Frame&#xff1a;重置方法入栈 …...

ARM64的汇编资源

最近在写一本ARM64的教材&#xff0c;所以在晚上查找了一下相关资源&#xff0c;都是免费开源的&#xff0c;不包括盗版书籍。 Exploring AArch64 assembler Roger Ferrer Ibez的博客文章&#xff0c;写在2016-2017年&#xff0c;内容简单充实&#xff0c;适合入门。 《ARM6…...

实验室安全分级分类管理系统在高校中的具体应用

盛元广通高校实验室安全分级分类管理系统的构建&#xff0c;旨在通过科学合理的管理手段&#xff0c;提高实验室的安全水平&#xff0c;保障师生的人身安全&#xff0c;防止实验事故的发生。这一系统通常包括实验室安全等级评估、分类管理、风险控制、安全教育与培训、应急响应…...

使用 prerenderRoutes 进行预渲染路由

title: 使用 prerenderRoutes 进行预渲染路由 date: 2024/8/20 updated: 2024/8/20 author: cmdragon excerpt: prerenderRoutes 函数是 Nuxt 3 中一个强大的工具,它能够帮助开发者优化页面加载速度和改善用户体验。通过使用 prerenderRoutes,你能够灵活地指定需要预渲染的…...

【深度解析】WRF-LES与PALM微尺度气象大涡模拟

查看原文>>>【深度解析】WRF-LES与PALM微尺度气象大涡模拟 针对微尺度气象的复杂性&#xff0c;大涡模拟&#xff08;LES&#xff09;提供了一种无可比拟的解决方案。微尺度气象学涉及对小范围内的大气过程进行精确模拟&#xff0c;这些过程往往与天气模式、地形影响和…...

redis事件机制

redis服务器是一个由事件驱动(死循环)的程序&#xff0c;它总共就干两件事&#xff1a; 文件事件&#xff1a;利用I/O复用机制&#xff0c;监听Socket等文件描述符发生的事件&#xff0c;如网络请求时间事件&#xff1a;定时触发的事件&#xff0c;负责完成redis内部定时任务&…...

【C++】模拟实现vector

可以把vector看作升级版的数组&#xff0c;可采用下标进行访问&#xff0c;非常高效&#xff0c;大小可动态改变&#xff0c;会自动扩容&#xff0c;数据存储在堆空间上。 VECROR 成员变量、函数及模板总览构造函数和析构函数无参构造函数构造n个元素大小的空间并初始化通过某个…...

【CAN-IDPS】汽车网关信息安全要求以及实验方法

《汽车网关信息安全技术要求及试验方法》是中国的一项国家标准,编号为GB/T 40857-2021,于2021年10月11日发布,并从2022年5月1日起开始实施 。这项标准由全国汽车标准化技术委员会(TC114)归口,智能网联汽车分会(TC114SC34)执行,主管部门为工业和信息化部。 该标准主要…...

EASE-Grid是啥东西?

EASE-Grid&#xff08;Equal-Area Scalable Earth Grid&#xff0c;等面积可扩展地球网格&#xff09;是NASA设计的网格系统&#xff0c;主要用于存储和处理全球范围内的地球科学数据。可以被理解为一种特殊的投影方式&#xff0c;使得在全球范围内进行数据分析和可视化时&…...

前端用户管理模块方法及api分析

用户管理 方法及对应api 搜索 searchSysUser / GetSysUserListByPage 重置 resetData 添加用户 addShow &#xff1a;点击按钮后出现对话框&#xff0c;含有提交 submit / SaveSysUser、取消按钮 修改 editSysUser / UpdateSysUser 删除 deleteById / DeleteSysUser 分配角色…...

microsoft edge怎么关闭安全搜索

microsoft edge浏览器为用户提供了安全搜索功能&#xff0c;旨在帮助用户过滤掉搜索结果中出现的不当信息。然而&#xff0c;有些用户可能觉得安全搜索功能限制了他们的浏览体验或工作需求。下面就给大家带来关闭microsoft edge安全搜索的相关内容&#xff0c;一起来看看吧。&a…...

Qt | QSQLite内存数据库增删改查

点击上方"蓝字"关注我们 01、演示 参数随便设置 查询 修改 右键菜单是重点 手动提交,点击Submit All...

【论文阅读】SegNeXt:重新思考卷积注意力设计

《SegNeXt: Rethinking Convolutional Attention Design for Semantic Segmentation》 原文&#xff1a;https://github.com/Visual-Attention-Network/SegNeXt/blob/main/resources/paper.pdf 源码&#xff1a;https://github.com/Visual-Attention-Network/SegNeXt 1、简介 …...

【C++】String类:标准库介绍

目录 一.预备知识 1.auto关键字 2.范围for 3.迭代器 二.标准库里的string 1.string类的基本介绍 2.构造函数 ​编辑 3.访问及遍历操作 3.1 operator [] 3.2 基于范围for 3.3 使用迭代器 4.迭代器 5.容量操作 5.1 size和length 5.2 capacity 5.3 reserve和resiz…...

MS523非接触式读卡器 IC

MS523 是一款应用于 13.56MHz 非接触式通信中的高集成 度读写卡芯片&#xff0c;它集成了在 13.56MHz 下所有类型的被动非接 触式通信方式和协议&#xff0c;支持 ISO14443A/B 的多层应用。 主要特点  高度集成的解调和解码模拟电路  采用少量外部器件&#…...

仓颉编程语言入门 -- Socket 编程与HTTP 编程概述

仓颉的 Socket 编程概述 在网络通信的广阔天地中&#xff0c;仓颉的Socket编程如同一座桥梁&#xff0c;连接着不同的计算设备&#xff0c;实现了基于传输层协议的数据传输。无论是追求稳定可靠的TCP&#xff0c;还是偏好轻量级、无连接的UDP&#xff0c;Socket都扮演着不可或…...

Oracle基本SQL操作-用户角色权限管理

一、用户权限管理 -- 创建锁定用户&#xff0c;此时用户不可用 create USER zhucl IDENTIFIED BY 123456 account lock; 会提示用户被锁定&#xff1a; -- 删除用户 drop user zhucl;-- 重新创建用户&#xff0c;不锁定 create user zhucl IDENTIFIED BY 123456 account unlo…...

Qt-信号和槽(8)

目录 信号的概念 Qt中的信号三要素 connect函数 connect的原型 connect的使用 信号函数和槽函数 参数匹配 close关闭槽函数 运行结果 第一个问题&#xff1a;怎么知道 手册使用 第二个问题&#xff0c;为什么可以直接传递函数指针 自定义槽函数 第一种自定义槽函…...

80.游戏的分辨率修改思路与分析

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a;易道云信息技术研究院 上一个内容&#xff1a;79.游戏分析工具闪屏问题优化与数据被修改高亮 GAMEHACKER2.exe 工具下载地址&#xff…...

MaxKB(二):Ubuntu24.04搭建maxkb开发环境

接上文&#xff1a;windows10搭建maxkb开发环境&#xff08;劝退指南&#xff09; 上文在windows10环境搭建maxkb开发环境遇到各种坑&#xff0c;后面就转战ubuntu平台&#xff0c;果然比较顺利的完成开发环境搭建。当然遇到相关的问题还是可以参考上文《windows10搭建maxkb开发…...

c#实现数据导出为PDF的方式

PdfSharp vs iTextSharp: C#中PDF导出功能比较 PdfSharp 优点 轻量级&#xff1a;适合简单的PDF生成任务易于学习&#xff1a;API相对简单&#xff0c;学习曲线较缓开源&#xff1a;提供开源版本&#xff0c;可自由使用和修改纯C#实现&#xff1a;不依赖外部库或COM组件支持…...

【联想电脑】:使用拓展坞后转接HDMI,无法识别显示屏

项目场景&#xff1a; 作为一个嵌入式软件开发者&#xff0c;有两个外接屏幕&#xff0c;不足为奇。 但是在今天的使用电脑过程中&#xff0c;出现了接了一个拓展坞上面有HDMI接口&#xff0c;但是HDMI接口接上外接显示屏的时候电脑无法识别到&#xff0c;导致只有电脑直连的HD…...

Verilog刷题笔记53

题目&#xff1a; Fsm serialdata See also: Serial receiver Now that you have a finite state machine that can identify when bytes are correctly received in a serial bitstream, add a datapath that will output the correctly-received data byte. out_byte needs …...

GoFly快速开发后台框架-后端接口请求返回403提示码就跨域问题/请求端域名拦截问题

问题&#xff1a; 大家在本地开发或者部署后请求后端时返回403&#xff0c;只有一个问题就是存在请求端跨域问题。 解决办法&#xff1a; 解决这个问题很简单&#xff0c;跨域的就解决跨域就好了。 我们官方给大家统一解决办法是&#xff1a; 到后端配置文件resource/conf…...

设备实时数据采集:开启制造业智能化、自动化的新篇章

传统制造业在进行生产过程中&#xff0c;会涉及到设备实时数据采集需求&#xff0c;这些数据对于监控生产流程、优化生产效率、保证产品质量以及降低成本等方面至关重要。以下是一些常见的数据采集需求&#xff1a; 1.生产数据&#xff1a;包括生产数量、生产批次、生产速度等&…...

【python与java的区别-03(集合、字典)】

一、Set python: 集合&#xff08;set&#xff09;是一个无序的不重复元素序列。 集合中的元素不会重复&#xff0c;并且可以进行交集、并集、差集等常见的集合操作。 可以使用大括号 { } 创建集合&#xff0c;元素之间用逗号 , 分隔&#xff0c; 或者也可以使用 set() 函数…...

Java继承

目录 一、继承概念 二、语法格式 三、类型及特性 四、关键字 4.1. super 4.2. super与this指针 4.3 final 关键字 一、继承概念 继承(inheritance)机制&#xff1a;是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特 性 的基础上进…...

Kafka集群搭建的两种方式

目录 1. 依赖Zookeeper搭建集群 1. 下载Kafka二进制文件 2. 更改kafka配置 3. 启动Zookeeper集群和Kafka集群 4. 验证集群 1.创建主题 2. 检查主题是否存在 3. 创建生产者生产数据 4. 创建消费者消费数据 5. 检查Zookeeper中Kafka集群的元数据 2. 不依赖Zookeeper搭…...