本章节通过获取Android系统的Display,使用Display+Presentation或Display+Activity实现分屏展示。最后通过MediaProjection录屏采集主屏幕数据,通过Display+Activity+OpenGL方式实现副屏同主屏的渲染。
系统提供了三种方式获取要Display显示屏幕:
1.MediaRouter获取Display
2.DisplayManager获取可支持Display[]数组
3.DisplayManager获取所有Display[]数组
MediaRouter官方文档
通过MediaRouter服务getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO)
方法获取到MediaRouter.RouteInfo对象,再通过MediaRouter.RouteInfo的getPresentationDisplay()
获取分屏Display。
fun getDisplayByMediaRouter(): Display? {
val mediaRouter = getSystemService(MEDIA_ROUTER_SERVICE) as MediaRouter
val localRouteInfo = mediaRouter.getSelectedRoute(MediaRouter.ROUTE_TYPE_LIVE_AUDIO)
return localRouteInfo?.presentationDisplay
}
通过DisplayManager的getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION);
获取所有分屏Display,然后获取对应分屏Display。
fun getDisplayByDisplayManager(): Display? {
val displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager
val pDisplays = displayManager.getDisplays(DisplayManager.DISPLAY_CATEGORY_PRESENTATION)
return if (pDisplays.size > 0) pDisplays[0] else null
}
通过DisplayManager的getDisplays()
获取所有屏幕Display的数组(该数组第一个是主屏Display),然后获取对应分屏Display。
fun getDisplayByDisplayManager2(): Display? {
val displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager
val displays = displayManager.displays
return if (displays.size > 1) displays[1] else null
}
上面我们已经通过三种方式获取到Display对象
,这里我们还需要Presentation来展示在对应分屏上,该方案需要悬浮窗权限
,因为Presentation实际上继承自Dialog
,所以展示在分屏上的其实是一个弹窗。下面我来是实现一下:
传入上面获取到的Display对象,通过Presentation实现分屏展示
private var presentation: MyPresentation? = null
/**
* 主屏back键/home键隐藏后,副屏仍可使用。
* 但是,再次打开主屏,副屏会失联,所以作如下设置
*
* @param display
*/
private fun showPresentation(display: Display?) {
if (display != null) {
presentation = null
presentation = MyPresentation(applicationContext, display)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
presentation!!.window!!.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY)
} else {
presentation!!.window!!.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT)
}
presentation!!.show()
} else {
Toast.makeText(this@MainActivity, "不支持分屏!", Toast.LENGTH_SHORT).show()
}
}
/**
* 隐藏Presentation
*/
private fun dismissPresentation() {
if (presentation != null) {
presentation!!.dismiss()
presentation = null
}
}
MyPresentation.kt
package com.yvan.display
import android.app.AlertDialog
import android.app.Presentation
import android.content.Context
import android.os.Bundle
import android.view.Display
import android.view.View
import android.widget.TextView
/**
* act和Presentation之间可以设置监听,进行互相通讯
*/
class MyPresentation(context: Context?, display: Display?) : Presentation(context, display) {
var textView: TextView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.ly_display)
textView = findViewById<TextView>(R.id.text)
textView!!.text = "副屏如果支持触摸,可以点击试试结果"
findViewById<View>(R.id.click).setOnClickListener {
AlertDialog.Builder(context)
.setTitle("Dialog")
.setMessage("Prsentation Click Test")
.setCancelable(false)
.setPositiveButton("OK") { dialog, which -> textView!!.text = "哎呦,不错哦" }.create().show()
}
findViewById<View>(R.id.dismiss).setOnClickListener { dismiss() }
}
}
ly_display.xml
<RelativeLayout
android:id="@+id/layout"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/click"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:text="在对应主屏上弹出对话框"/>
<Button
android:id="@+id/dismiss"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:text="使自己消失"/>
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="1111"
android:layout_centerInParent="true"
android:textSize="30sp"/>
RelativeLayout>
别忘了要在AndroidManifest.xml添加浮窗权限
,在使用前动态获取权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
/**
* 动态获取浮窗权限
*/
private fun checkPermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
//SYSTEM_ALERT_WINDOW权限申请
if (!Settings.canDrawOverlays(this)) {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
intent.data = Uri.parse("package:$packageName") //不加会显示所有可能的app
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivityForResult(intent, 1)
} else {
//TODO do something you need
}
}
}
上面的Presentation是弹窗,这里把分屏Display的id通过options.putInt("android.activity.launchDisplayId", displayId)
传入Activity,实现Activity展示在副屏上。
fun startShared() {
val options = Bundle() // 实例化Bundle
val displayManager = getSystemService(DISPLAY_SERVICE) as DisplayManager // 获取显示管理器 并 强转类型为 显示管理器
val displays = displayManager.displays // 从显示管理器中 获取 当前系统所有的显示屏幕
val intent = Intent(this, ScreenActivity::class.java) // 准备一个待 激活的Activity 也就是显示第二块屏幕的 目标Activity
if (displays != null && displays.size > 1) { // 判断屏幕大于1,意味着 有 副屏的存在,才能进入if
options.putInt("android.activity.launchDisplayId", displays[1].displayId) // 激活Activity前期的设置,设置为第二块屏幕的ID
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK or Intent.FLAG_ACTIVITY_NEW_TASK) // 这种第二块屏幕的显示,标准Flags的设置
startActivity(intent, options) // 激活 显示第二块屏幕的 目标Activity
}
}
上面副屏展示有Display+Presentation和Display+Activity两种方式,现在要实现将主屏内容渲染到副屏上,这里结合前面两个章节学习的OpenGL渲染到屏幕知识,可以通过以下方案实现:
主屏数据采集:MediaProjection录屏
副屏渲染:Display+Activity+OpenGL
这里是项目地址,别忘了点star喔!