Android Framework之多屏异显

实现方法

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: Intvar vertexBuffer // 顶点坐标缓存区
            : FloatBuffervar textureBuffer // 纹理坐标
            : FloatBufferprivate 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配置的更新在这里插入图片描述
【有需要的朋友,扫描下方二维码即可领取!!】👇👇
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值