目录
- 概述
- 使用
- 开关的代码实现方式
- 系统部分的处理:
- 参考
概述
在Android开发中,模拟辅助显示设备通常指的是通过Android开发者选项来设置的一种虚拟显示设备,它允许开发者在一个设备上模拟另一个设备的显示特性。这种功能对于测试应用程序在不同屏幕尺寸、分辨率和DPI(每英寸点数)下的表现非常有用。另一个作用是, 它可以通过特定的开发技巧和功能来充当副屏,实现多屏显示的效果。这种功能在开发测试、多任务处理以及特定应用场景(如车载系统)中非常有用。
使用
如何打开
- 打开设置
- 进入开发者选项 (方法自行查阅)
- 选择 模拟辅助显示设备
这个窗口会以浮动窗口的方式显示在上层, 支持的基本操作:
- 拖动到任意位置
- 缩放 (长按或多触摸)
开关的代码实现方式
参考系统设置的源代码:
布局文件: packages/apps/Settings/res/xml/development_prefs.xml
<ListPreferenceandroid:key="overlay_display_devices"android:title="@string/overlay_display_devices_title"android:entries="@array/overlay_display_devices_entries"android:entryValues="@array/overlay_display_devices_values" />
列表和对应的值: frameworks/base/packages/SettingsLib/res/values/arrays.xml
<!-- Titles for overlay display devices preference. [CHAR LIMIT=35] --><string-array name="overlay_display_devices_entries"><item>None</item><item>480p</item><item>480p (secure)</item><item>720p</item><item>720p (secure)</item><item>1080p</item><item>1080p (secure)</item><item>4K</item><item>4K (secure)</item><item>4K (upscaled)</item><item>4K (upscaled, secure)</item><item>720p, 1080p (dual screen)</item></string-array><!-- Values for overlay display devices preference. --><string-array name="overlay_display_devices_values" translatable="false" ><item></item><item>720x480/142</item><item>720x480/142,secure</item><item>1280x720/213</item><item>1280x720/213,secure</item><item>1920x1080/320</item><item>1920x1080/320,secure</item><item>3840x2160/320</item><item>3840x2160/320,secure</item><item>1920x1080/320|3840x2160/640</item><item>1920x1080/320|3840x2160/640,secure</item><item>1280x720/213;1920x1080/320</item></string-array>
packages/apps/Settings/src/com/android/settings/development/DevelopmentSettings.java
private void updateOverlayDisplayDevicesOptions() {String value = Settings.Global.getString(getActivity().getContentResolver(),Settings.Global.OVERLAY_DISPLAY_DEVICES);if (value == null) {value = "";}CharSequence[] values = mOverlayDisplayDevices.getEntryValues();for (int i = 0; i < values.length; i++) {if (value.contentEquals(values[i])) {mOverlayDisplayDevices.setValueIndex(i);mOverlayDisplayDevices.setSummary(mOverlayDisplayDevices.getEntries()[i]);return;}}mOverlayDisplayDevices.setValueIndex(0);mOverlayDisplayDevices.setSummary(mOverlayDisplayDevices.getEntries()[0]);}
查看和设置当前配置的值:
# 查看
settings get global overlay_display_devices
720x480/142# 配置
settings put global overlay_display_devices 1920x1080/320
在应用获得权限的前提下, 可以尝试使用代码来获取和设置:
//获取String overlay_display_devices = Settings.Global.getString(getContentResolver(), "overlay_display_devices");
//设置Settings.Global.putString(getContentResolver(), "overlay_display_devices", "");
系统部分的处理:
frameworks/base/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@Overridepublic void registerLocked() {super.registerLocked();getHandler().post(new Runnable() {@Overridepublic void run() {getContext().getContentResolver().registerContentObserver(Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),true, new ContentObserver(getHandler()) {@Overridepublic void onChange(boolean selfChange) {updateOverlayDisplayDevices();}});updateOverlayDisplayDevices();}});}
private void updateOverlayDisplayDevices() {synchronized (getSyncRoot()) {updateOverlayDisplayDevicesLocked();}}private void updateOverlayDisplayDevicesLocked() {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();}int count = 0;for (String part : value.split(";")) {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);ArrayList<OverlayMode> modes = new ArrayList<>();for (String mode : modeString.split("\\|")) {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;}}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);boolean secure = flagString != null && flagString.contains(",secure");Slog.i(TAG, "Showing overlay display device #" + number+ ": name=" + name + ", modes=" + Arrays.toString(modes.toArray()));mOverlays.add(new OverlayDisplayHandle(name, modes, gravity, secure, number));continue;}}Slog.w(TAG, "Malformed overlay display devices setting: " + value);}}
创建OverlayDisplayDevice, 并显示
/*** Functions as a handle for overlay display devices which are created and* destroyed asynchronously.** Guarded by the {@link DisplayManagerService.SyncRoot} lock.*/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 boolean mSecure;private final int mNumber;private OverlayDisplayWindow mWindow;private OverlayDisplayDevice mDevice;private int mActiveMode;public OverlayDisplayHandle(String name, List<OverlayMode> modes, int gravity,boolean secure, int number) {mName = name;mModes = modes;mGravity = gravity;mSecure = secure;mNumber = number;mActiveMode = 0;showLocked();}private void showLocked() {mUiHandler.post(mShowRunnable);}public void dismissLocked() {mUiHandler.removeCallbacks(mShowRunnable);mUiHandler.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, mSecure);mDevice = new OverlayDisplayDevice(displayToken, mName, mModes, mActiveMode,DEFAULT_MODE_INDEX, refreshRate, presentationDeadlineNanos,mSecure, state, surfaceTexture, mNumber) {@Overridepublic void onModeChangedLocked(int index) {onActiveModeChangedLocked(index);}};sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);}}
参考
Android双屏异显(Presentation)与后台动态配置副屏内容
Android双屏异显以及原理分析