概述
在Android开发中,模拟辅助显示设备通常指的是通过Android开发者选项来设置的一种虚拟显示设备,它允许开发者在一个设备上模拟另一个设备的显示特性。这种功能对于测试应用程序在不同屏幕尺寸、分辨率和DPI(每英寸点数)下的表现非常有用。另一个作用是, 它可以通过特定的开发技巧和功能来充当副屏,实现多屏显示的效果。这种功能在开发测试、多任务处理以及特定应用场景(如车载系统)中非常有用。
使用
如何打开
- 打开设置
- 进入开发者选项 (方法自行查阅)
- 选择 模拟辅助显示设备
这个窗口会以浮动窗口的方式显示在上层, 支持的基本操作:
- 拖动到任意位置
- 缩放 (长按或多触摸)
开关的代码实现方式
参考系统设置的源代码:
布局文件: packages/apps/Settings/res/xml/development_prefs.xml
<ListPreference
android: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
@Override
public void registerLocked() {
super.registerLocked();
getHandler().post(new Runnable() {
@Override
public void run() {
getContext().getContentResolver().registerContentObserver(
Settings.Global.getUriFor(Settings.Global.OVERLAY_DISPLAY_DEVICES),
true, new ContentObserver(getHandler()) {
@Override
public 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.
@Override
public 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) {
@Override
public void onModeChangedLocked(int index) {
onActiveModeChangedLocked(index);
}
};
sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
}
}