Android VirtualDisplay創(chuàng)建流程及原理
- Android DisplayManager提供了createVirtualDisplay接口,用于創(chuàng)建虛擬屏。虛擬屏可用于錄屏(網(wǎng)上很多資料說這個功能),分屏幕(比如一塊很長的屏幕,通過虛擬屏分出不同的區(qū)域)等等。
創(chuàng)建VirtualDisplay
- DisplayManager中的函數(shù)原型如下。后兩個Hide的API,只有平臺的應(yīng)用才可以使用。
// frameworks/base/core/java/android/hardware/display/DisplayManager.java
public VirtualDisplay createVirtualDisplay(@NonNull String name,
int width, int height, int densityDpi, @Nullable Surface surface, int flags) {
}
public VirtualDisplay createVirtualDisplay(@NonNull String name,
int width, int height, int densityDpi, @Nullable Surface surface, int flags,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
}
/** @hide */
public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,
@NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface,
int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler,
@Nullable String uniqueId) {
}
/** @hide */
public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,
@NonNull VirtualDisplayConfig virtualDisplayConfig,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
}
- 補充一點,MediaProjection中也提供了 createVirtualDisplay這個接口,實際上也是通過調(diào)用DisplayManager實現(xiàn)的功能。
// frameworks/base/media/java/android/media/projection/MediaProjection.java
public VirtualDisplay createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
DisplayManager dm = mContext.getSystemService(DisplayManager.class);
// 調(diào)用DisplayManager的接口
return dm.createVirtualDisplay(this, virtualDisplayConfig, callback, handler);
}
- 創(chuàng)建VirtualDisplay時,需要傳入Surface。**VirtualDisplay上要繪制的內(nèi)容,實際是通過傳入的Surface顯示出來的。**比如在主屏(根據(jù)物理屏,分配邏輯Display)上創(chuàng)建了一個SurfaceView,通過把這個SurfaceView傳給VirtualDisplay。那么VirtualDisplay的 內(nèi)容,實際上是在主屏的SurfaceView上顯示的。下面是一段Android原生的例子。
// frameworks/base/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java
private Display createVirtualDisplay() {
final String displayName = "NavVirtualDisplay";
final DisplayInfo displayInfo = new DisplayInfo();
mContext.getDisplay().getDisplayInfo(displayInfo);
final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
// 創(chuàng)建ImageReader,通過它得到一張Surface
mReader = ImageReader.newInstance(displayInfo.logicalWidth,
displayInfo.logicalHeight, PixelFormat.RGBA_8888, 2);
assertNotNull("ImageReader must not be null", mReader);
// 創(chuàng)建虛擬屏,傳入Surface。
mVirtualDisplay = displayManager.createVirtualDisplay(displayName, displayInfo.logicalWidth,
displayInfo.logicalHeight, displayInfo.logicalDensityDpi, mReader.getSurface(),
0 /*flags*/);
assertNotNull("virtual display must not be null", mVirtualDisplay);
waitForDisplayReady(mVirtualDisplay.getDisplay().getDisplayId());
return mVirtualDisplay.getDisplay();
}
- 上面的例子中創(chuàng)建虛擬屏,返回Display(實際上是VirtualDislay)對象。有了Display對象,我們就可以將View綁定到這個虛擬的Display上了(綁定網(wǎng)上方法比較多可自行搜索)。關(guān)于Surface的創(chuàng)建,有很多種方法,比如通過SurfaceContron+Buffer這種方式也可以。
- VituralDisplay創(chuàng)建時,需要提供flag。其值定義如下,可通過 “或”將flag組合。
public static final int VIRTUAL_DISPLAY_FLAG_PUBLIC = 1 << 0;
public static final int VIRTUAL_DISPLAY_FLAG_PRESENTATION = 1 << 1;
public static final int VIRTUAL_DISPLAY_FLAG_SECURE = 1 << 2;
public static final int VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY = 1 << 3;
public static final int VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR = 1 << 4;
public static final int VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 5;
public static final int VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH = 1 << 6;
public static final int VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT = 1 << 7;
public static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8;
public static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9;
public static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10;
public static final int VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP = 1 << 11;
- DisplayManager公開的接口中,有VirtualDisplay.Callback ,提供了其狀態(tài)的回調(diào)。
public static abstract class Callback {
/**
* Called when the virtual display video projection has been
* paused by the system or when the surface has been detached
* by the application by calling setSurface(null).
* The surface will not receive any more buffers while paused.
*/
public void onPaused() { }
/**
* Called when the virtual display video projection has been
* resumed after having been paused.
*/
public void onResumed() { }
/**
* Called when the virtual display video projection has been
* stopped by the system. It will no longer receive frames
* and it will never be resumed. It is still the responsibility
* of the application to release() the virtual display.
*/
public void onStopped() { }
}
VirtualDisplay原理
- 關(guān)于VirtualDisplay的實現(xiàn)原理,主要從AndroidFramework角度進行分析。
// /frameworks/base/core/java/android/hardware/display/DisplayManager.java
public VirtualDisplay createVirtualDisplay(@NonNull String name,
int width, int height, int densityDpi, @Nullable Surface surface, int flags) {
return createVirtualDisplay(name, width, height, densityDpi, surface, flags, null, null);
}
public VirtualDisplay createVirtualDisplay(@NonNull String name,
int width, int height, int densityDpi, @Nullable Surface surface, int flags,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
height, densityDpi);
builder.setFlags(flags);
if (surface != null) {
builder.setSurface(surface);
}
return createVirtualDisplay(null /* projection */, builder.build(), callback, handler);
}
// TODO : Remove this hidden API after remove all callers. (Refer to MultiDisplayService)
/** @hide */
public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,
@NonNull String name, int width, int height, int densityDpi, @Nullable Surface surface,
int flags, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler,
@Nullable String uniqueId) {
final VirtualDisplayConfig.Builder builder = new VirtualDisplayConfig.Builder(name, width,
height, densityDpi);
builder.setFlags(flags);
if (uniqueId != null) {
builder.setUniqueId(uniqueId);
}
if (surface != null) {
builder.setSurface(surface);
}
return createVirtualDisplay(projection, builder.build(), callback, handler);
}
/** @hide */
public VirtualDisplay createVirtualDisplay(@Nullable MediaProjection projection,
@NonNull VirtualDisplayConfig virtualDisplayConfig,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
// 走的這里,會調(diào)用到DisplayManagerGlobal中。
return mGlobal.createVirtualDisplay(mContext, projection, virtualDisplayConfig, callback,
handler);
}
- DisplayManagerGlobal調(diào)用DMS(DisplayManagerService)服務(wù)創(chuàng)建虛擬屏,得到DMS返回的DisplayID后,通過DisplayID在Client端創(chuàng)建了VirtualDisplay對象。
// /frameworks/base/core/java/android/hardware/display/DisplayManager.java
public VirtualDisplay createVirtualDisplay(@NonNull Context context, MediaProjection projection,
@NonNull VirtualDisplayConfig virtualDisplayConfig, VirtualDisplay.Callback callback,
Handler handler) {
VirtualDisplayCallback callbackWrapper = new VirtualDisplayCallback(callback, handler);
// 從MediaProjection過來的調(diào)用,這個地方非空。
IMediaProjection projectionToken = projection != null ? projection.getProjection() : null;
int displayId;
try {
// 告知DMS創(chuàng)建虛擬屏,并返回DisplayID
displayId = mDm.createVirtualDisplay(virtualDisplayConfig, callbackWrapper,
projectionToken, context.getPackageName());
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
if (displayId < 0) {
Log.e(TAG, "Could not create virtual display: " + virtualDisplayConfig.getName());
return null;
}
// 通過DisplayID,取得Display對象信息(也是調(diào)用DMS得到的)
Display display = getRealDisplay(displayId);
if (display == null) {
Log.wtf(TAG, "Could not obtain display info for newly created "
+ "virtual display: " + virtualDisplayConfig.getName());
try {
// 創(chuàng)建失敗,需要釋放
mDm.releaseVirtualDisplay(callbackWrapper);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
return null;
}
// 創(chuàng)建VirtualDisplay
return new VirtualDisplay(this, display, callbackWrapper,
virtualDisplayConfig.getSurface());
}
- DisplayManagerService(DMS)中創(chuàng)建DisplayDevice并添加到Device列表中管理
// /frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
@Override // Binder call
public int createVirtualDisplay(VirtualDisplayConfig virtualDisplayConfig,
IVirtualDisplayCallback callback, IMediaProjection projection, String packageName) {
// 檢查uid與包名,是否相符。
final int callingUid = Binder.getCallingUid();
if (!validatePackageName(callingUid, packageName)) {
throw new SecurityException("packageName must match the calling uid");
}
if (callback == null) {
throw new IllegalArgumentException("appToken must not be null");
}
if (virtualDisplayConfig == null) {
throw new IllegalArgumentException("virtualDisplayConfig must not be null");
}
// 拿到client端傳過來的surface對象
final Surface surface = virtualDisplayConfig.getSurface();
int flags = virtualDisplayConfig.getFlags();
if (surface != null && surface.isSingleBuffered()) {
throw new IllegalArgumentException("Surface can't be single-buffered");
}
// 下面開始針對Flag,做一些邏輯判斷。
if ((flags & VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) {
flags |= VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
// Public displays can't be allowed to show content when locked.
if ((flags & VIRTUAL_DISPLAY_FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD) != 0) {
throw new IllegalArgumentException(
"Public display must not be marked as SHOW_WHEN_LOCKED_INSECURE");
}
}
if ((flags & VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY) != 0) {
flags &= ~VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR;
}
if ((flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
flags &= ~VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP;
}
if (projection != null) {
try {
if (!getProjectionService().isValidMediaProjection(projection)) {
throw new SecurityException("Invalid media projection");
}
flags = projection.applyVirtualDisplayFlags(flags);
} catch (RemoteException e) {
throw new SecurityException("unable to validate media projection or flags");
}
}
if (callingUid != Process.SYSTEM_UID &&
(flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
if (!canProjectVideo(projection)) {
throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
+ "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
+ "MediaProjection token in order to create a screen sharing virtual "
+ "display.");
}
}
if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0) {
if (!canProjectSecureVideo(projection)) {
throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT "
+ "or an appropriate MediaProjection token to create a "
+ "secure virtual display.");
}
}
if (callingUid != Process.SYSTEM_UID && (flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) != 0) {
if (!checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) {
EventLog.writeEvent(0x534e4554, "162627132", callingUid,
"Attempt to create a trusted display without holding permission!");
throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "
+ "create a trusted virtual display.");
}
}
if (callingUid != Process.SYSTEM_UID
&& (flags & VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP) != 0) {
if (!checkCallingPermission(ADD_TRUSTED_DISPLAY, "createVirtualDisplay()")) {
throw new SecurityException("Requires ADD_TRUSTED_DISPLAY permission to "
+ "create a virtual display which is not in the default DisplayGroup.");
}
}
if ((flags & VIRTUAL_DISPLAY_FLAG_TRUSTED) == 0) {
flags &= ~VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS;
}
// Sometimes users can have sensitive information in system decoration windows. An app
// could create a virtual display with system decorations support and read the user info
// from the surface.
// We should only allow adding flag VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
// to trusted virtual displays.
final int trustedDisplayWithSysDecorFlag =
(VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
| VIRTUAL_DISPLAY_FLAG_TRUSTED);
if ((flags & trustedDisplayWithSysDecorFlag)
== VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS
&& !checkCallingPermission(INTERNAL_SYSTEM_WINDOW, "createVirtualDisplay()")) {
throw new SecurityException("Requires INTERNAL_SYSTEM_WINDOW permission");
}
final long token = Binder.clearCallingIdentity();
try {
// 調(diào)用內(nèi)部實現(xiàn)
return createVirtualDisplayInternal(callback, projection, callingUid, packageName,
surface, flags, virtualDisplayConfig);
} finally {
Binder.restoreCallingIdentity(token);
}
}
// /frameworks/base/services/core/java/com/android/server/display/DisplayManagerService.java
private int createVirtualDisplayInternal(IVirtualDisplayCallback callback,
IMediaProjection projection, int callingUid, String packageName, Surface surface,
int flags, VirtualDisplayConfig virtualDisplayConfig) {
synchronized (mSyncRoot) {
if (mVirtualDisplayAdapter == null) {
Slog.w(TAG, "Rejecting request to create private virtual display "
+ "because the virtual display adapter is not available.");
return -1;
}
// 為虛擬屏創(chuàng)建Device(告知surfaceflinger創(chuàng)建Display)
DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked(
callback, projection, callingUid, packageName, surface, flags,
virtualDisplayConfig);
if (device == null) {
return -1;
}
// 發(fā)送添加Device通知,這里比較重要
mDisplayDeviceRepo.onDisplayDeviceEvent(device,
DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
// 檢查Display是否創(chuàng)建成功
final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device);
if (display != null) {
return display.getDisplayIdLocked();
}
// Something weird happened and the logical display was not created.
Slog.w(TAG, "Rejecting request to create virtual display "
+ "because the logical display was not created.");
mVirtualDisplayAdapter.releaseVirtualDisplayLocked(callback.asBinder());
mDisplayDeviceRepo.onDisplayDeviceEvent(device,
DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED);
}
return -1;
}
- 接下來DMS開始調(diào)用SurfaceFlinger的接口,創(chuàng)建Display。并將Display放入自身的List中管理。
// /frameworks/base/services/core/java/com/android/server/display/VirtualDisplayAdapter.java
public DisplayDevice createVirtualDisplayLocked(IVirtualDisplayCallback callback,
IMediaProjection projection, int ownerUid, String ownerPackageName, Surface surface,
int flags, VirtualDisplayConfig virtualDisplayConfig) {
String name = virtualDisplayConfig.getName();
// VIRTUAL_DISPLAY_FLAG_SECURE 的用途,是判斷是否為安全的Display,這個參數(shù)會告知SurfaceFlinger
boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
IBinder appToken = callback.asBinder();
// 調(diào)用SurfaceFligner創(chuàng)建Display(Display的type是virtual)
IBinder displayToken = mSurfaceControlDisplayFactory.createDisplay(name, secure);
final String baseUniqueId =
UNIQUE_ID_PREFIX + ownerPackageName + "," + ownerUid + "," + name + ",";
final int uniqueIndex = getNextUniqueIndex(baseUniqueId);
String uniqueId = virtualDisplayConfig.getUniqueId();
if (uniqueId == null) {
uniqueId = baseUniqueId + uniqueIndex;
} else {
uniqueId = UNIQUE_ID_PREFIX + ownerPackageName + ":" + uniqueId;
}
// 通過SurfaceFligner返回的displayToken,創(chuàng)建Device對象
VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken,
ownerUid, ownerPackageName, surface, flags, new Callback(callback, mHandler),
uniqueId, uniqueIndex, virtualDisplayConfig);
// 放到虛擬屏的List中管理。
mVirtualDisplayDevices.put(appToken, device);
try {
if (projection != null) {
projection.registerCallback(new MediaProjectionCallback(appToken));
}
appToken.linkToDeath(device, 0);
} catch (RemoteException ex) {
mVirtualDisplayDevices.remove(appToken);
device.destroyLocked(false);
return null;
}
// Return the display device without actually sending the event indicating
// that it was added. The caller will handle it.
return device;
}
// /frameworks/base/services/core/java/com/android/server/display/DisplayDeviceRepository.java
// mDisplayDeviceRepo.onDisplayDeviceEvent(device,
// DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
// 這段代碼,會調(diào)用到下面的函數(shù)中。
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;
// 需要是將Device(就是上面創(chuàng)建的虛擬屏幕Device)放入到DMS的管理list
mDisplayDevices.add(device);
// 通知Device添加,會調(diào)用到LogicalDisplayMappe的handleDisplayDeviceAddedLocked中。
sendEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
}
}
// /frameworks/base/services/core/java/com/android/server/display/LogicalDisplayMapper.java
private void handleDisplayDeviceAddedLocked(DisplayDevice device) {
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);
}
// Create a logical display for the new display device
LogicalDisplay display = createNewLogicalDisplayLocked(
device, Layout.assignDisplayIdLocked(false /*isDefault*/));
// 刷新布局和display配置
applyLayoutLocked();
updateLogicalDisplaysLocked();
}
- 虛擬屏幕的創(chuàng)建,Client端通過Surface告知的DisplayID,創(chuàng)建VirtualDisplay對象。通過DisplayID,與DMS打交道。DMS服務(wù)端,通過SurfaceFlinger創(chuàng)建虛擬屏,拿到SurfaceFligner的DisplayToken,然后通過它創(chuàng)建VirtualDisplayDevice + LogicalDisplay來管理虛擬屏幕。
如何上屏?
- 創(chuàng)建虛擬屏幕的時候,會傳入了一張Surface(比如綁定主屏的一張Buffer)。虛擬屏通過這張Surface拿到Surface對應(yīng)的Buffer,將上屏內(nèi)容繪制到這個Buffer上,然后提交到畫面流水線上(SurfaceFlinger)。通過SurfaceFlinger將這個這個Buffer最終由SurfaceFlinger描畫并顯示到Surface所在那張Display上(根據(jù)VirtualDisplay位置去顯示。)
文章來源地址http://www.zghlxwxcb.cn/news/detail-763489.html
文章來源:http://www.zghlxwxcb.cn/news/detail-763489.html
到了這里,關(guān)于【Android】VirtualDisplay創(chuàng)建流程及原理的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!