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方法。

五、总结

方法调用时序图

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

机器学习预处理

一、数据读取 数据的读取方式有多种&#xff0c;最终我们可以转化为numpy和pandas形式储存&#xff0c;方便后续的模型建立。 1.1 读取库的安装 需要用到的三个库 pip install pandas pip install numpy pip install openpyxl 1.2 库的使用 import pandas as pd ​ #### 1…

【故障处理】- ping不通的原因

PING不通是一个非常常见的网络问题&#xff0c;它可能由多种原因引起。如链路故障、ARP学习失败等 以一个Ping不通的尝试示例&#xff0c;介绍Ping不通故障的定位思路。如下图&#xff1a; PC3 Ping不通PC4 PC>ping 20.1.1.20Ping 20.1.1.20: 32 data bytes, Press Ctrl_C…

亚马逊铺货ERP国内采集,图片编辑文本翻译一键拉伸,自...

亚马逊全功能 ERP 铺货采集&#xff0c;自动生成 SKU。 说说国内平台采集的商品如何通过 ERP 自己做链接上传发布到亚马逊平台&#xff01; 1. 首先进入 ERP 插件&#xff0c;直接点击 1688 平台采集自己想做的产品类型。各位按照自身的需求选择搜索的 JK&#xff0c;选择想采…

【GH】【EXCEL】P1: Write DATA SET from GH into EXCEL

文章目录 WriteFast WriteGH data material :GH process and components instructionFast Write DataFast Write Data & Clear DataFast Write to Cell EXCEL written results Write by ColumnGH data material :Compile ColumnGH process and components instructionWrite…

Redis RDB三两事

rdb&#xff1a;将数据库的快照以二进制格式保存在文件中&#xff0c;redis重启后直接加载数据。可以通过save和bgsave命令生成rdb。当然我们可以在生成rdb文件时指定规则&#xff0c;例如 save 60 1000 如果60秒内不少于1000个key发生了改动&#xff0c;则生成一个新的rdb文件…

如何在C++ QT 程序中集成cef3开源浏览器组件去显示网页?

目录 1、问题描述 2、为什么选择cef3浏览器组件 3、cef3组件的介绍与下载 4、将cef3组件封装成sdk 5、如何使用cef3组件加载web页面 5.1、了解CefApp与CefClient 5.2、初始化与消息循环 5.3、如何创建浏览器 5.4、重载CefClient类 6、在qt客户端集成cef组件 7、最后…

什么是主机加固?主机加固的几种有效方法

在数字化时代&#xff0c;数据的价值不言而喻&#xff0c;但随之而来的安全威胁也日益严峻。从勒索病毒到内部泄露&#xff0c;企业的数据安全面临着前所未有的挑战。为了应对这些挑战&#xff0c;一种全新的主机加固解决方案应运而生。 MCK主机加固解决方案&#xff0c;采用先…

Using Azure openAI key rotation automation

题意&#xff1a;使用 Azure OpenAI 密钥轮换自动化 问题背景&#xff1a; We are planning to do the Azure OpenAI key rotation automatically. How can we achieve this? Do we have terraform resource for this. 我们计划自动执行 Azure OpenAI 密钥轮换。我们如何实现…

运维小技能:通过调整JVM的默认内存配置来解决内存溢出(‌OutOfMemoryError)‌或栈溢出(‌StackOverflowError)‌等错误

文章目录 引言I 调整JVM的默认堆内存配置1.1 java命令启动jar包时配置JVM 的内存参数1.2 基于Tomcat服务器部署的java应用,配置JVM 的内存参数II 案例: Linux 操作系统设置tomcat的 JVM 的内存参数查找Tomcat位置: 快速定位服务状态和部署位置具体配置步骤扩展: 监测Nginx访…

Mysql查询日志

Mysql查询日志 Mysql查询日志默认是关闭状态的。 mysql> show variables like %general_log%; --------------------------------------- | Variable_name | Value | --------------------------------------- | general_log | OFF …

Python数分实战

学习视频&#xff1a;【课程3.0】Python基础与分析实战_哔哩哔哩_bilibili 由于学习过python进行数据分析&#xff0c;所以就简单记录一下&#xff0c;最主要学习的还是视频最后的两个项目&#xff0c;进行实战 之前想不明白明明有很智能的软件做数据分析&#xff0c;为什么还要…

C++票据查验、票据ocr、文字识别

现在&#xff0c;80、90后的人们逐渐过渡为职场上的主力人员&#xff0c;在工作中当然也会碰到各种各样的问题。比如&#xff0c;当你的老板给你一个艰难的任务时&#xff0c;肯定是不能直接拒绝的。那么我们该怎么做呢&#xff1f;翔云建议您先认真考虑老板说的任务的难度&…

倍福ADS通信教程

介绍 TwinCAT3 TwinCAT3是Beckhoff推出的一款基于PC的控制器软件&#xff0c;简单理解是一套集成开发环境&#xff0c;里边有各种分析工具以及通信中间件&#xff1b;开发者可以很方便的用它来进行IPC和PLC之间的通信连接 ADS 倍福ADS&#xff08;‌Automation Device Spec…

WebRTC音视频开发读书笔记(六)

数据通道不仅可以发送文本消息, 还可以发送图片、二进制文件,将其类型binaryType属性设置成arraybuffer类型即可. 九\、文件传输 1、文件传输流程 &#xff08;1&#xff09;使用表单file打开本地文件 &#xff08;2&#xff09;使用FileReader读取文件的二进制数据 &#…

零基础学习Redis(5) -- redis单线程模型介绍

前面我们提到过&#xff0c;redis是单线程的&#xff0c;这期我们详细介绍一下redis的单线程模型 1. redis单线程模型 redis只使用一个线程处理所有的请求&#xff0c;并不是redis服务器进程内部只有一个线程&#xff0c;其实也存在多个线程&#xff0c;只不过多个线程是在处…

SparkSQL遵循ANSI标准

ANSI简介 ANSI Compliance通常指的是遵循美国国家标准学会&#xff08;American National Standards Institute, ANSI&#xff09;制定的标准。在计算机科学和技术领域&#xff0c;这通常涉及到数据库管理系统&#xff08;DBMS&#xff09;对于SQL语言的支持程度。 ANSI为SQL…

基于vue框架的爱学习分享平台ud317(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,学科分类,交流答疑,论坛交流,学习资料 开题报告内容 基于Vue框架的爱学习分享平台 开题报告 一、项目背景与意义 随着互联网技术的飞速发展&#xff0c;知识的获取与传播方式正经历着前所未有的变革。在线教育平台逐渐成为满足…

如何理解:进程控制

文章目录 前言&#xff1a;进程创建&#xff1a;进程终止&#xff1a;如何终止进程&#xff1f;进程等待非阻塞等待&#xff1a; 总结&#xff1a; 前言&#xff1a; ​ 对于前面的地址空间的学习&#xff0c;我们现在了解到原来所谓变量的地址其实是虚拟地址&#xff0c;该虚…

【数学建模备赛】Ep05:斯皮尔曼spearman相关系数

文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、斯皮尔曼spearman相关系数&#xff1a;☀️☀️☀️1. 回顾皮尔逊相关系数2. 斯皮尔曼spearman相关系数3. 斯皮尔曼相关系数公式4. 另外一种斯皮尔曼相关系数定义5. matlab的用法5. matlab的用法 三、对斯皮尔曼相…

MySQL(二)——CRUD

文章目录 CRUD新增全列插入指定列插入插入查询结果 查询全列查询指定列查询查询字段为表达式表达式不包含字段表达式包含一个字段表达式包含多个字段 补充&#xff1a;别名去重查询排序条件查询 补充&#xff1a;运算符区间查询模糊查询NULL的查询 分页查询聚合查询聚合函数 分…