Android使用DisplayManager創(chuàng)建虛擬屏流程及原理解析
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) { }
補(bǔ)充一點,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角度進(jìn)行分析。
// /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位置去顯示。)
以上就是Android使用VirtualDisplay的創(chuàng)建虛擬屏流程及原理解析的詳細(xì)內(nèi)容,更多關(guān)于Android VirtualDisplay的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android Studio新建工程默認(rèn)在build.gradle中加入maven阿里源的問題
這篇文章主要介紹了Android Studio新建工程默認(rèn)在build.gradle中加入maven阿里源的問題,本文通過實例代碼相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-03-03Android百度地圖實現(xiàn)搜索和定位及自定義圖標(biāo)繪制并點擊時彈出泡泡
這篇文章主要介紹了Android百度地圖實現(xiàn)搜索和定位及自定義圖標(biāo)繪制并點擊時彈出泡泡的相關(guān)資料,需要的朋友可以參考下2016-01-01Android zxing如何識別反轉(zhuǎn)二維碼詳解
這篇文章主要給大家介紹了關(guān)于Android zxing如何識別反轉(zhuǎn)二維碼的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-09-09Android動態(tài)顯示當(dāng)前年月日時分秒系統(tǒng)時間(示例代碼)
這篇文章主要介紹了Android動態(tài)顯示當(dāng)前年月日時分秒系統(tǒng)時間的示例代碼,需要的朋友可以參考下2017-05-05Android開發(fā)之PopupWindow實現(xiàn)彈窗效果
這篇文章主要為大家詳細(xì)介紹了Android開發(fā)之PopupWindow實現(xiàn)彈窗效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-09-09實例講解Android應(yīng)用開發(fā)中TabHost的使用要點
這篇文章主要介紹了Android應(yīng)用開發(fā)中TabHost的使用要點,文中以實例講解了TabHost與Tab的布局方法,需要的朋友可以參考下2016-04-04