开发者

Android使用DisplayManager创建虚拟屏流程及原理解析

目录
  • android VirtualDisplay创建流程及原理
    • 创建VirtualDisplay
    • VirtualDisplay原理
  • 如何上屏

    Android VirtualDisplay创建流程及原理

    Android DisplayManager提供了createVirtualDisplay接口,用于创建虚拟屏。虚拟屏可用于录屏(网上很多资料说这个功能),分屏幕(比如一块很长的屏幕,通过虚拟屏分出不同的区域)等等。

    创建VirtualDisplay

    DisplayManager中的函数原型如下。后两个Hide的API,只有平台的应用才可以使用。

    // 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 fandroidlags) {
    }
    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这个接口,实际上也是通过调用DisplayManager实现的功能。

    // 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);
            // 调用DisplayManager的接口
            return dm.createVirt编程客栈ualDisplay(this, virtualDisplayConfig, callback, handler);
        }

    创建VirtualDisplay时,需要传入Surface。**VirtualDisplay上要绘制的内容,实际是通过传入的Surface显示出来的。**比如在主屏(根据物理屏,分配逻辑Display)上创建了一个SurfaceView,通过把这个SurfaceView传给VirtualDisplay。那么VirtualDisplay的 内容,实际上是在主屏的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);
    	// 创建ImageReader,通过它得到一张Surface
    	mReader = ImageReader.newInstance(displayInfo.logicalWidth,
    			displayInfo.logicalHeight, PixelFormat.RGBA_8888, 2);
    	assertNotNull("ImageReader must not be null", mReader);
    	// 创建虚拟屏,传入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();
    }

    上面的例子中创建虚拟屏,返回Display(实际上是VirtualDislay)对象。有了Display对象,我们就可以将View绑定到这个虚拟的Display上了(绑定网上方法比较多可自行搜索)。关于Surface的创建,有很多种方法,比如通过SurfaceContron+Buffer这种方式也可以。

    VituralDisplay创建时,需要提供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 ,提供了其状态的回调。

        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原理

    关于VirtualDisplay的实现原理,主要从AndroidFramework角度进行分析。

    Android使用DisplayManager创建虚拟屏流程及原理解析

    // /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) {
    	// 走的这里,会调用到DisplayManagerGlobal中。
    	return mGlobal.createVirtualDisplay(mContext, projection, virtualDisplayConfig, callback,
    			handler);
    }

    DisplayManagerGlobal调用DMS(DisplayManagerService)服务创建虚拟屏,得到DMS返回的DisplayID后,通过DisplayID在Client端创建了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过来的调用,这个地方非空。
    	IMediaProjection projectionToken = projection != null ? projection.getProjection() : null;
    	int displayId;
    	try {
    		// 告知DMS创建虚拟屏,并返回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对象信息(也是调用DMS得到的)
    	Display display = getRealDisplay(displayId);
    	if (display == null) {
    		Log.wtf(TAG, "Could not obtain display info for newly created "
    				+ "virtual display: " + virthttp://www.devze.comualDisplayConfig.getName());
    		try {
    			// 创建失败,需要释放
    			mDm.releaseVirtualDisplay(callbackWrapper);
    		} catch (RemoteException ex) {
    			throw ex.rethrowFromSystemServer();
    		}
    		return null;
    	}
    	// 创建VirtualDisplay
    	return new VirtualDisplay(this, display, callbackWrapper,
    			virtualDisplayConfig.getSurface());
    }

    DisplayManagerService(DMS)中创建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 {
    		// 调用内部实现
    		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;
    		}
    		// 为虚拟屏创建Device(告知surfaceflinger创建Display)
    		DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked(
    				callback, projection, callingUid, packageName, surface, flags,
    				virtualDisplayConfig);
    		if (device == null) {
    			return -1;
    		}
    		// 发送添加Device通知,这里比较重要
    		mDisplayDeviceRepo.onDisplayDeviceEvent(device,
    				DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED);
    		// 检查Display是否创建成功
    		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开始调用SurfaceFlinger的接口,创建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,这个参数会告知SurfaceFlinger
    	boolean secure = (flags & VIRTUAL_DISPLAY_FLAG_SECURE) != 0;
    	IBinder appToken = callback.asBinder();
    	// 调用SurfaceFligner创建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,创建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); 
    // 这段代码,会调用到下面的函数中。
    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(就是上面创建的虚拟屏幕Device)放入到DMS的管理list
    		mDisplayDevices.add(device);
    		// 通知Device添加,会调用到LogicalDisplayMappe的handleDisplayDeviceAddedLocked中。
    		sendEventLocked(device, DISPLAY_DEVICjsE_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();
    }

    虚拟屏幕的创建,Client端通过Surface告知的DisplayID,创建VirtualDisplay对象。通过DisplayID,与DMS打交道。DMS服务端,通过SurfaceFlinger创建虚拟屏,拿到SurfaceFligner的DisplayToken,然后通过它创建VirtualDisplayDevice + LogicalDisplay来管理虚拟屏幕。

    Android使用DisplayManager创建虚拟屏流程及原理解析

    如何上屏

    创建虚拟屏幕的时候,会传入了一张Surface(比如绑定主屏的一张Buffer)。虚拟屏通过这张Surface拿到Surface对应的Buffer,将上屏内容绘制到这个Buffer上,然后提交到画面流水线上(SurfaceFlinger)。通过SurfaceFlinger将这个这个Buffer最终由SurfaceFlinger描画并显示到Surface所在那张Display上(根据VirtualDisplay位置去显示。)

    Android使用DisplayManager创建虚拟屏流程及原理解析

    以上就是Android使用VirtualDisplay的创建虚拟屏流程及原理解析的详细内容,更多关于Android VirtualDisplay的资料请关注编程客栈(www.devze.com)其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜