实现方法
Presentation
- Presentation的使用有两种DisplayManger 和 MediaRouter
- 最小版本17,
- 本身是Dialog 无法创建Fragment 和PopWindow
- 主屏和副屏UI尺寸不一样
常见问题处理
-
副屏Toast 提示
//需要注意Toast的Context参数 Toast.makeText(getContext(),"提示语句",Toast.LENGTH_SHORT).show()
-
常驻不退出 要改成系统级别弹窗
<!--8.0之前--> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <!--8.0之后--> <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW"/> if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O) { window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY) } else{ window.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT) }
-
android 版本10 以上处理
在测试过程中 anroid 12 按照正常写法Presentation会崩
//也添加了各种权限符合要求,但是模拟器如下代码在android10 没问题,在android12有问题,可能是avd原因 val displayManager= getSystemService(Context.DISPLAY_SERVICE) as DisplayManager val displays= displayManager.displays if(displays.isNotEmpty()){ val testPresentation=TestPresentation(this@MainActivity,displays[1]) testPresentation.show() }
-
MediaRouter
官方虽然建议流媒转可以使用MediaRouter, 但是在模拟器有点问题,有androidX和V7版本
代码实现
注意事项
android 8 在后台使用服务,需要配合通知来使用
服务需要添加 foregroundServiceType
不然会报错
Android 8.0 有一项复杂功能;系统不允许后台应用创建后台服务。 因此,Android 8.0 引入了一种全新的方法,即 Context.startForegroundService(),以在前台启动新服务。
在系统创建服务后,应用有五秒的时间来调用该服务的 startForeground() 方法以显示新服务的用户可见通知。
如果应用在此时间限制内未调用 startForeground(),则系统将停止服务并声明此应用为 ANR。
<service android:name=".ScreenService" android:foregroundServiceType="mediaProjection"/>
报错
Permission Denial: startForeground from pid=18953, uid=10152 requires android.permission.FOREGROUND_SERVICE
//处理
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
代码
class MainActivity2 : AppCompatActivity() {
@RequiresApi(Build.VERSION_CODES.O)
companion object {
private const val TAG = "MainActivity2"
}
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val mediaProjectionManager =
application.getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager
startService(Intent(this@MainActivity2,PresentationService::class.java)) // 启动后台服务的
//需要在oncreat 里面调用 不然会报错
val result= registerForActivityResult(ActivityResultContracts.StartActivityForResult()){
val data2= mediaProjectionManager.getMediaProjection(it.resultCode,it.data!!)
val presentationDisplays = getSystemService(DISPLAY_SERVICE) as DisplayManager
if (presentationDisplays.displays.isNotEmpty()) {
val presentation: MyPresentation =
MyPresentation(this, presentationDisplays.displays[1])
presentation.setMediaProjection(data2)
presentation.show()
}
}
findViewById<View>(R.id.btn).setOnClickListener {
val mediaIntent= mediaProjectionManager.createScreenCaptureIntent()
result.launch(mediaIntent)
}
}
class MyPresentation:Presentation , TextureView.SurfaceTextureListener {
private var mediaProjection: MediaProjection? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.presentation)
val textureView= findViewById<TextureView>(R.id.textureView)
textureView.surfaceTextureListener = this
}
constructor(context: Context?,dis: Display?):super(context,dis)
public fun setMediaProjection(mediaProjection: MediaProjection){
this.mediaProjection = mediaProjection
}
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
// 创建一块虚拟屏幕
val virtualDisplay = mediaProjection!!.createVirtualDisplay(
"ScreenRecorder-display0",//名字随便写
width, height, 1,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,//显示的标志位,不同的标志位,截取不同的内容,具体看源码解释
Surface(surface), null, null
)
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {
}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
return true
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {
}
}
class PresentationService: Service() {
private val NOTIFICATION_CHANNEL_ID = "com.enjoy.mutidisplay.channel_id"
private val NOTIFICATION_CHANNEL_NAME = "com.enjoy.mutidisplay.channel_name"
private val NOTIFICATION_CHANNEL_DESC = "com.enjoy.mutidisplay.channel_desc"
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationIntent = Intent(this, PresentationService::class.java)
val pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
val notification = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.ic_launcher_foreground)) // 录屏通知的答图标等信息
.setSmallIcon(R.drawable.ic_launcher_foreground) // 录屏通知的小图标等信息
.setContentTitle("多屏服务")
.setContentText("正在录屏......")
.setContentIntent(pendingIntent)
.build()// 最后构建通知的所有信息 即可
val channel = NotificationChannel(
NOTIFICATION_CHANNEL_ID,
NOTIFICATION_CHANNEL_NAME,
NotificationManager.IMPORTANCE_DEFAULT
)
channel.description = NOTIFICATION_CHANNEL_DESC
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
startForeground(1, notification) // 开启通知 此 录屏服务 的 前台通知 正在录屏中...
}
}
新建Activity 在启动的页面开启录屏
需要依赖提前写好依赖的页面/包名/类型,需要依赖于flag
val option =ActivityOptions.makeBasic()
option.launchDisplayId = 1
//bundle 写法 不够优雅
//val bundle =Bundle()
//Intent.KEY_LAUNCH_DISPLAY_ID
// bundle.putInt("android.activity.launchDisplayId",1)
val intent =Intent()
intent.component = ComponentName("包","类")
//必须添加 否则无法推送附屏
intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent,option.toBundle())
//在第二个需要录屏的界面调用和PreSentaition 一样的代码
class ScreenActivity : Activity(), TextureView.SurfaceTextureListener {
private var textureView: TextureView? = null // TextureView的成员定于
private var screenServiceIntent: Intent? = null // 后台服务的Intent
private var mMediaProjectionManager: MediaProjectionManager? = null // 需要捕获屏幕内容的应用程序管理
private var width = INVALID_SIZE // 宽度
private var height = INVALID_SIZE // 高度
private var mediaProjection: MediaProjection? = null // 录制屏幕的核心
private var virtualDisplay: VirtualDisplay? = null // 虚拟显示屏幕
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.screen_secondary) //副屏显示的画面,都在TextureView中显示
textureView = findViewById(R.id.textureView) // TextureView的成员
textureView?.surfaceTextureListener = this // 绑定surfaceTextureListener接口事件行为
screenServiceIntent = Intent(this, ScreenService::class.java) // 后台服务的Intent构建
startService(screenServiceIntent) // 启动后台服务的
requestMediaProjection() // 调用此方法的目的,是为了,开启屏幕录制
}
// 开启屏幕录制 屏幕捕获
private fun requestMediaProjection() {
mMediaProjectionManager = applicationContext.getSystemService(MEDIA_PROJECTION_SERVICE) as MediaProjectionManager // 捕获屏幕内容的应用程序管理
val captureIntent = mMediaProjectionManager!!.createScreenCaptureIntent() // 得到 捕获屏幕的 系统Intent
startActivityForResult(captureIntent, REQUEST_MEDIA_PROJECTION) // 开始调用系统捕获屏幕
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) {
mediaProjection = mMediaProjectionManager ?.getMediaProjection(resultCode, data)
if (requestCode == REQUEST_MEDIA_PROJECTION && resultCode == RESULT_OK) { // 开始调用系统捕获屏幕 的 回馈 判断
// 创建虚拟的屏幕
virtualDisplay = mediaProjection ?.createVirtualDisplay(
"ScreenRecorder-display0", // 虚拟的屏幕名称 虚拟显示的名称,必须非空
width, height, 1, // 宽度,高度,虚拟显示在dpi中的密度 必须大于0。
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, // 标记的含义是 虚拟显示标志:创建一个公共显示
Surface(textureView!!.surfaceTexture), // 虚拟内容所显示的表面,说白了就是我们 最终要显示到 textureView控件中去
null, null // 用不到,也没有用过哦
)
}
}
// TODO 由于实现了 实现SurfaceTextureListener接口 的 方法具体回调实现 - start
override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
this.width = width
this.height = height
}
override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}
override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
virtualDisplay!!.release() // 释放虚拟屏幕
mediaProjection!!.stop() // 不要录制主屏幕数据啦
return true
}
override fun onSurfaceTextureUpdated(surface: SurfaceTexture) { }
// TODO 由于实现了 实现SurfaceTextureListener接口 的 方法具体回调实现 - end
// 通用常量区域
companion object {
private const val INVALID_SIZE = -1 // 宽度 与 高度 的 默认值
private const val REQUEST_MEDIA_PROJECTION = 100 // onActivityResult 反馈后的判断值
}
// 销毁时,停止后台服务
override fun onDestroy() {
super.onDestroy()
stopService(screenServiceIntent)
}
}
adb
adb shell am start -W -n 包名/类名 --dispaly 1
需要指定特定页面,需要指定disPlayId
adb shell am start -W -n com.nuonuo.aldltestapp/com.nuonuo.aldltestapp.MainActivity --display 1
Socket
基于WebSocket +MediaCodec 服务端编码,接收端解码
需要对于从MediaCodec可以创建出一个surface,从中获取 MediaProjectionManager 获取到的MediaProjection 生成的数据,进行编码 通过webSocket发送到对应的IP地址,在接收端进行解码
并利用Surface播放
优点 是可以跨进程通信
缺点 需要编解码 会卡顿 可能会掉线
// 创建一个目的surface来存放输入数据
Surface surface = mMediaCodec.createInputSurface();
// 获取屏幕流
mMediaProjection.createVirtualDisplay(
"screen",
VIDEO_WIDTH,
VIDEO_HEIGHT,
1,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC,
surface,
null,
null);
private void encodeData(ByteBuffer byteBuffer, MediaCodec.BufferInfo bufferInfo) {
// 偏移4 00 00 00 01为分隔符需要跳过
int offSet = 4;
if (byteBuffer.get(2) == 0x01) {
offSet = 3;
}
// 计算出当前帧的类型
int type = (byteBuffer.get(offSet) & 0x7E) >> 1;
if (type == TYPE_FRAME_VPS) {
// 保存vps sps pps信息
vps_pps_sps = new byte[bufferInfo.size];
byteBuffer.get(vps_pps_sps);
} else if (type == TYPE_FRAME_INTERVAL) {
// 将保存的vps sps pps添加到I帧前
final byte[] bytes = new byte[bufferInfo.size];
byteBuffer.get(bytes);
byte[] newBytes = new byte[vps_pps_sps.length + bytes.length];
System.arraycopy(vps_pps_sps, 0, newBytes, 0, vps_pps_sps.length);
System.arraycopy(bytes, 0, newBytes, vps_pps_sps.length, bytes.length);
// 将重新编码好的数据发送出去
mSocketManager.sendData(newBytes);
} else {
// B帧 P帧 直接发送
byte[] bytes = new byte[bufferInfo.size];
byteBuffer.get(bytes);
mSocketManager.sendData(bytes);
}
}
自定义openGl
不使用TextureView ,而自定义GLSurfaceView,需要自定义解析器和Filter
class MyGLSurfaceView (context: Context?, attrs: AttributeSet?) : GLSurfaceView(context, attrs) {
val renderer: MyGlRenderer // 定义成员变量:渲染器
// 布局一定会调用 两个参数的构造函数
init {
// 设置EGL版本
setEGLContextClientVersion(2)
// 设置渲染器
renderer = MyGlRenderer(this)
setRenderer(renderer)
// RENDERMODE_WHEN_DIRTY 按需渲染,有帧数据的时候,才会去渲染( 效率高,麻烦,后面需要手动调用一次才行)
// RENDERMODE_CONTINUOUSLY 每隔16毫秒,读取更新一次,(如果没有显示上一帧)
renderMode = RENDERMODE_WHEN_DIRTY
// requestRender();
}
// 自定义的MyGLSurfaceView,为了销毁 【渲染器】
override fun surfaceDestroyed(holder: SurfaceHolder) {
super.surfaceDestroyed(holder)
renderer.onSurfaceDestroyed()
}
}
// OpenGL的渲染器【注意:此内容会在NDK专题里面详细来讲解OpenGL系列课】
class MyGlRenderer(private val surfaceView: MyGLSurfaceView) : GLSurfaceView.Renderer, OnFrameAvailableListener {
private var surfaceTexName = 0 // OpenGL纹理对象名称(例如通过glGenTextures生成)
private var surfaceTexture: SurfaceTexture? = null // 定义成员 SurfaceTexture 用于显示的支持
private var screenFilter: ScreenFilter? = null // 在OpenGL中 对图像纹理图层 的 过滤器
var mtx = FloatArray(16) // OpenGL更新纹理的数量,是固定化 16
private var mediaProjection: MediaProjection? = null // 录制屏幕的核心
private var virtualDisplay: VirtualDisplay? = null // 虚拟显示屏幕
private var surface: Surface? = null // Surface为了服务于SurfaceTexture
private var width = INVALID_SIZE // 宽度
private var height = INVALID_SIZE // 高度
override fun onSurfaceCreated(gl: GL10, config: EGLConfig) {
// 创建一个OpenGL 纹理(图片)
val textures = IntArray(1)
GLES20.glGenTextures(textures.size, textures, 0)
surfaceTexName = textures[0]
surfaceTexture = SurfaceTexture(surfaceTexName)
// 注册 图像有效 回调
surfaceTexture!!.setOnFrameAvailableListener(this)
screenFilter = ScreenFilter(surfaceView.context)
}
override fun onSurfaceChanged(gl: GL10, width: Int, height: Int) {
this.width = width
this.height = height
createVirtualDisplay()
screenFilter!!.setSize(width, height)
}
// 创建虚拟的屏幕
private fun createVirtualDisplay() {
if (mediaProjection == null || width == INVALID_SIZE || height == INVALID_SIZE) {
return
}
surfaceTexture!!.setDefaultBufferSize(width, height)
surface = Surface(surfaceTexture)
virtualDisplay = mediaProjection!!.createVirtualDisplay(
"ScreenRecorder-display0", // 虚拟的屏幕名称 虚拟显示的名称,必须非空
width, height, 1, // 宽度,高度,虚拟显示在dpi中的密度 必须大于0。
DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC, // 此标记的含义是 虚拟显示标志:创建一个公共显示
surface, // 虚拟内容所显示的表面,说白了就是我们 最终要显示到 【Surface为了服务于SurfaceTexture】 中去
null, null // 用不到,也没有用过哦
)
}
/**
* 图像的处理与绘制
* @param gl
*/
override fun onDrawFrame(gl: GL10) {
// todo 更新纹理
surfaceTexture!!.updateTexImage()
surfaceTexture!!.getTransformMatrix(mtx)
screenFilter!!.setTransformMatrix(mtx)
screenFilter!!.onDraw(surfaceTexName)
}
// 被 自定义的MyGLSurfaceView,位了销毁 【渲染器】 哪里调用 的 对外开发API方法
fun onSurfaceDestroyed() {
surface!!.release()
virtualDisplay!!.release()
mediaProjection!!.stop()
width = INVALID_SIZE
height = INVALID_SIZE
}
// 此帧可以用时-回调此方法
override fun onFrameAvailable(surfaceTexture: SurfaceTexture) {
// 请求执行一次 onDrawFrame
surfaceView.requestRender()
}
// 在 ScreenActivity调用 设置 录制屏幕的核心 到 自定义的MyGLSurfaceView中去 的 对外开发API方法
fun setMediaProjection(mediaProjection: MediaProjection?) {
this.mediaProjection = mediaProjection
createVirtualDisplay()
}
// 通用常量区域
companion object {
private const val INVALID_SIZE = -1 // 宽度 与 高度 的 默认值
}
}
class ScreenFilter(context: Context?) {
private val vPosition: Int
private val vCoord: Int
private val vTexture: Int
private val vMatrix: Int
private val program: Int
var vertexBuffer // 顶点坐标缓存区
: FloatBuffer
var textureBuffer // 纹理坐标
: FloatBuffer
private var mWidth = 0
private var mHeight = 0
private var mtx: FloatArray? = null
init {
// 准备数据
vertexBuffer = ByteBuffer.allocateDirect(4 * 4 * 2).order(ByteOrder.nativeOrder()).asFloatBuffer()
val VERTEX = floatArrayOf(
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
)
vertexBuffer.clear()
vertexBuffer.put(VERTEX)
textureBuffer = ByteBuffer.allocateDirect(4 * 4 * 2).order(ByteOrder.nativeOrder()).asFloatBuffer()
val TEXTURE = floatArrayOf(
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f
)
textureBuffer.clear()
textureBuffer.put(TEXTURE)
val vertexSharder = OpenGLUtils.readAssetTextFile(context!!, "vshader.vsh")
val fragSharder = OpenGLUtils.readAssetTextFile(context, "fshader.fsh")
// 着色器程序准备好
program = OpenGLUtils.loadProgram(vertexSharder, fragSharder)
// 获取程序中的变量 索引
vPosition = GLES20.glGetAttribLocation(program, "vPosition")
vCoord = GLES20.glGetAttribLocation(program, "vCoord")
vTexture = GLES20.glGetUniformLocation(program, "vTexture")
vMatrix = GLES20.glGetUniformLocation(program, "vMatrix")
}
// 设置更新 宽度 与 高度
fun setSize(width: Int, height: Int) {
mWidth = width
mHeight = height
}
fun onDraw(texture: Int) {
// 设置绘制区域
GLES20.glViewport(0, 0, mWidth, mHeight)
GLES20.glUseProgram(program)
vertexBuffer.position(0)
// 4、归一化 normalized [-1,1] . 把[2,2]转换为[-1,1]
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 0, vertexBuffer)
// CPU传数据到GPU,默认情况下着色器无法读取到这个数据。 需要我们启用一下才可以读取
GLES20.glEnableVertexAttribArray(vPosition)
textureBuffer.position(0)
// 4、归一化 normalized [-1,1] . 把[2,2]转换为[-1,1]
GLES20.glVertexAttribPointer(vCoord, 2, GLES20.GL_FLOAT, false, 0, textureBuffer)
// CPU传数据到GPU,默认情况下着色器无法读取到这个数据。 需要我们启用一下才可以读取
GLES20.glEnableVertexAttribArray(vCoord)
// 相当于激活一个用来显示图片的画框
GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texture)
// 0: 图层ID GL_TEXTURE0
// GL_TEXTURE1 , 1
GLES20.glUniform1i(vTexture, 0)
GLES20.glUniformMatrix4fv(vMatrix, 1, false, mtx, 0)
// 通知画画,
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
}
fun setTransformMatrix(mtx: FloatArray) {
this.mtx = mtx
}
}
object OpenGLUtils {
private const val TAG = "OpenGLUtils"
val VERTEX = floatArrayOf(
-1.0f, -1.0f,
1.0f, -1.0f,
-1.0f, 1.0f,
1.0f, 1.0f
)
val TEXURE = floatArrayOf(
0.0f, 0.0f,
1.0f, 0.0f,
0.0f, 1.0f,
1.0f, 1.0f
)
fun glGenTextures(textures: IntArray) {
GLES20.glGenTextures(textures.size, textures, 0)
for (i in textures.indices) {
// 绑定纹理,后续配置纹理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textures[i])
/**
* 必须:设置纹理过滤参数设置
*/
/*设置纹理缩放过滤*/
// GL_NEAREST: 使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色
// GL_LINEAR: 使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色
// 后者速度较慢,但视觉效果好
GLES20.glTexParameteri(
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR
) // 放大过滤
GLES20.glTexParameteri(
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR
) //缩小过滤
/**
* 可选:设置纹理环绕方向
*/
//纹理坐标的范围是0-1。超出这一范围的坐标将被OpenGL根据GL_TEXTURE_WRAP参数的值进行处理
// GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T 分别为x,y方向。
// GL_REPEAT:平铺
// GL_MIRRORED_REPEAT: 纹理坐标是奇数时使用镜像平铺
// GL_CLAMP_TO_EDGE: 坐标超出部分被截取成0、1,边缘拉伸
// GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
// GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
}
}
fun readAssetTextFile(context: Context, filename: String?): String {
var br: BufferedReader? = null
var line: String?
val sb = StringBuilder()
try {
val `is` = context.assets.open(filename!!)
br = BufferedReader(InputStreamReader(`is`))
while (br.readLine().also { line = it } != null) {
sb.append(line)
sb.append("\n")
}
} catch (e: Exception) {
e.printStackTrace()
} finally {
if (br != null) {
try {
br.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
return sb.toString()
}
fun loadProgram(vSource: String?, fSource: String?): Int {
/**
* 顶点着色器
*/
val vShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER)
// 加载着色器代码
GLES20.glShaderSource(vShader, vSource)
// 编译(配置)
GLES20.glCompileShader(vShader)
// 查看配置 是否成功
val status = IntArray(1)
GLES20.glGetShaderiv(vShader, GLES20.GL_COMPILE_STATUS, status, 0)
check(status[0] == GLES20.GL_TRUE) {
// 失败
"load vertex shader:" + GLES20.glGetShaderInfoLog(vShader)
}
/**
* 片元着色器
* 流程和上面一样
*/
val fShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER)
// 加载着色器代码
GLES20.glShaderSource(fShader, fSource)
// 编译(配置)
GLES20.glCompileShader(fShader)
// 查看配置 是否成功
GLES20.glGetShaderiv(fShader, GLES20.GL_COMPILE_STATUS, status, 0)
if (status[0] != GLES20.GL_TRUE) {
// 失败
throw IllegalStateException("load fragment shader:" + GLES20.glGetShaderInfoLog(vShader))
}
/**
* 创建着色器程序
*/
val program = GLES20.glCreateProgram()
// 绑定顶点和片元
GLES20.glAttachShader(program, vShader)
GLES20.glAttachShader(program, fShader)
// 链接着色器程序
GLES20.glLinkProgram(program)
// 获得状态
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, status, 0)
if (status[0] != GLES20.GL_TRUE) {
throw IllegalStateException("link program:" + GLES20.glGetProgramInfoLog(program))
}
GLES20.glDeleteShader(vShader)
GLES20.glDeleteShader(fShader)
return program
}
fun copyAssets2SdCard(context: Context, src: String?, dst: String?) {
try {
val file = File(dst)
if (!file.exists()) {
val `is` = context.assets.open(src!!)
val fos = FileOutputStream(file)
var len: Int
val buffer = ByteArray(2048)
while (`is`.read(buffer).also { len = it } != -1) {
fos.write(buffer, 0, len)
}
`is`.close()
fos.close()
}
} catch (e: IOException) {
e.printStackTrace()
}
}
}
总结
一般音视频可以用MeidaRouter
投屏可以用Presentation
跨进程可以用socket那种形式
知识点
- ActivityThread
startActivityNow()
->startActivityNow()
->createBaseContextForActivity(r)
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
//根据token获取对应的disPlayId
final int displayId = ActivityClient.getInstance().getDisplayId(r.token);
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
if (pkgName != null && !pkgName.isEmpty()
&& r.packageInfo.mPackageName.contains(pkgName)) {
for (int id : dm.getDisplayIds()) {
if (id != DEFAULT_DISPLAY) {
Display display =
dm.getCompatibleDisplay(id, appContext.getResources());
appContext = (ContextImpl) appContext.createDisplayContext(display);
break;
}
}
}
return appContext;
}
-
为什么不直接从APP送显,而选择Context
因为context可以塞进去很多数据,不然传很多数据过去 很麻烦
-
其中一个屏幕黑屏了怎么处理?
这里涉及到wms对多屏的管理和图片的合成流程,需要区分生成渲染部分出了问题 还是合成部分出了问题。
需要将图片dump出来,具体部分需要看资料了。在SurfaceFlinger的init 方法中 初始化了DisPlays。
Framework学习方法
由于许多Android开发者日常工作主要集中在业务层面,大量时间用于编写基础代码、应用现成框架,导致对底层技术如Framework、Handler源码、Binder机制等了解不足,仅停留在表面认知阶段。
最后为了帮助大家学习Framework,特此准备了一份超级详细的Android Framework内核源码知识体系图解,以及配套的《Android Framework源码开发解析》学习笔记,能更好的帮助理解Android Framework领域的核心技术,从而提升自身的竞争力。
【有需要的朋友,扫描下方二维码即可领取!!】👇👇
《Android Framework源码开发揭秘》
第一章 系统启动流程分析
- 第一节 Android启动概括
- 第二节 init.rc解析
- 第三节 Zygote
- 第四节 面试题
第二章 跨进程通信IPC解析
- 第一节 Service还可以这么理解
- 第二节 Binder基础
- 第三节 Binder应用
- 第四节 AIDL应用(上)
- 第五节 AIDL应用(下)
- 第六节 Messenger原理及应用
- 第七节 服务端回调
- 第八节 获取服务(IBinder)
- 第九节 Binder面试题全解析
第三章 Handler源码解析
- 第一节 源码分析
- 第二节 难点问题
- 第三节 Handler常问面试题
第四章 AMS源码解析
- 第一节 引言
- 第二节 Android架构
- 第三节 通信方式
- 第四节 系统启动系列
- 第五节 AMS
- 第六节 AMS面试题解析
第五章 WMS源码解析
- 第一节 WMS与activity启动流程
- 第二节 WMS绘制原理
- 第三节 WMS角色与实例化过程
- 第四节 WMS工作原理
第六章 Surface源码解析
- 第一节 创建流程及软硬件绘制
- 第二节 双缓冲及Surface View解析
- 第三节 Android图形系统综述
第七章 基于Android12.0的SurfaceFlinger源码解析
- 第一节 应用建立和SurfaceFlinger的沟通桥梁
- 第二节 SurfaceFlinger的启动和消息队列处理机制
- 第三节 SurfaceFlinger之VSyns(上)
- 第四节 SurfaceFlinger之VSyns(中)
- 第五节 SurfaceFlinger之VSyns(下)
第八章 PKMS源码解析
- 第一节 PKMS调用方式
- 第二节 PKMS启动过程分析
- 第三节 APK的扫描
- 第四节 APK的安装
- 第五节 PKMS之权限扫描
- 第六节 静默安装
- 第七节 requestPermissions源码流程解析
- 第八节 PKMS面试题
第九章 InputManagerService源码解析
- 第一节 Android Input输入事件处理流程(1)
- 第二节 Android Input输入事件处理流程(2)
- 第三节 Android Input输入事件处理流程(3)
第十章 DisplayManagerService源码解析
- 第一节 DisplayManagerService启动
- 第二节 DisplayAdepter和DisplayDevice的创建
- 第三节 DMS部分亮灭屏流程
- 第四节 亮度调节
- 第五节 Proximity Sensor灭屏原理
- 第六节 Logical Display和Physical Display配置的更新