Android Surface的跨进程绘制,如何绘制xml布局给Surface,全网独一份

news2024/11/15 19:56:44

工作中遇到了这样一个需求

需求:需要将一个自定义View或者自定义布局通过跨进程方式传递给第二个应用来展示,第一个应用负责布局的渲染,第二个应用不需要关心第一个应用的业务和实现,仅提供SurfaceView占位及展示

方案:调研后决定跨进程通过aidl来实现,数据则通过Surface来传递,由于aidl仅支持基本数据类型或者实现了Parcelable接口的类,Surface满足以上条件,并且xml布局可以通过inflate方式转换为bitmap,并且xml的易用性和可控性更高,同时Surface可以渲染xml转为的Bitmap,故选择Surface作为数据传递方式

技术方案已经确定,开干!

1.aidl开启

开启aidl的第一步,首先要在app目录下的build.gradle添加aidl的配置,添加完后别忘了还需要重新同步下项目

buildFeatures {
aidl = true
}

2.创建aidl文件

在AndroidStudio中在项目中右键可以看到创建aidl类型的文件,我们创建一个为SurfaceViewManager的aidl文件

创建后它会默认为我们生成一个专门的aidl目录

 接下来,我们直接开始编辑所需要的方法

// SurfaceViewManager.aidl
package cn.itbox.auto.driver;

import android.view.Surface;
import android.view.MotionEvent;

// Declare any non-default types here with import statements

interface SurfaceViewManager {
    // SurfaceView onCreate时调用
    void surfaceCreated(in Surface surface);
    // SurfaceView onChange时调用
    void surfaceChanged(in Surface surface);
    // SurfaceView onDestroy时调用
    void surfaceDestroyed(in Surface surface);
    // 发送SurfaceView的Touch事件
    void sendTouchEvent(in MotionEvent event);
}

编写完后需要主动Make一下项目,这样才能为我们生成对应的java文件

3.Surface管理类和布局渲染 

我们接下来就可以直接去实现这个接口对应的Stub,我们创建一个SurfaceManager去管理Surface的绘制过程(具体业务需要和自己的需求结合来实现),我这里是实现了两种xml布局的渲染,以及进度条刷新及展示

package cn.itbox.auto.driver.common.aidl

import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.Rect
import android.util.Log
import android.view.MotionEvent
import android.view.Surface
import android.view.View
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import cn.itbox.auto.driver.R
import cn.itbox.auto.driver.SurfaceViewManager
import cn.itbox.auto.driver.app.DriverApplication.Companion.context
import cn.itbox.auto.driver.common.user.UserManager
import com.drake.interval.Interval
import org.xutils.common.util.LogUtil
import java.util.concurrent.TimeUnit

/**
 *  管理Surface跨端绘制
 *  @author wangzhe
 */
class SurfaceManager : SurfaceViewManager.Stub() {
    private var mCanvas: Canvas? = null
    private var mPaint: Paint? = null
    private var mSurface: Surface? = null
    private var homeView: View? = null
    private var naviView: View? = null
    private var progressBar: ProgressBar? = null
    private var ivCar: ImageView? = null
    private var ivLabel: ImageView? = null
    private var tvDriverName: TextView? = null
    private var progress = 0
    private val interval = Interval(1, TimeUnit.SECONDS).subscribe {
        LogUtil.i("progress : $progress")
        resetDrawNaviView()
        progress++
    }

    private fun resetDrawNaviView() {
        naviView = View.inflate(context, R.layout.small_widget_navi, null)
        tvDriverName = naviView?.findViewById(R.id.driverNameTextView)
        progressBar = naviView?.findViewById(R.id.naviProgressBar)
        ivCar = naviView?.findViewById(R.id.naviCarImageView)

        tvDriverName?.text = UserManager.driverInfo.name
        progressBar?.progress = progress
        ivCar?.x = 1000f * progress / 100
        draw(naviView)
    }

    override fun surfaceCreated(surface: Surface) {
        Log.i("SurfaceManager", "surfaceCreated: ")
        // 拿到客户端Surface
        mSurface = surface
        initView()
        //展示首页默认的布局样式
        draw(homeView)
    }

    private fun initView() {
        homeView = View.inflate(context, R.layout.small_widget_home, null)
        naviView = View.inflate(context, R.layout.small_widget_navi, null)
        progressBar = naviView?.findViewById(R.id.naviProgressBar)
        ivCar = naviView?.findViewById(R.id.naviCarImageView)
        ivLabel = naviView?.findViewById(R.id.labelImageView)
        tvDriverName = naviView?.findViewById(R.id.driverNameTextView)
        tvDriverName?.text = UserManager.driverInfo.name

    }

    override fun surfaceChanged(surface: Surface) {
        Log.i("SurfaceManager", "surfaceChanged: ")
    }

    override fun surfaceDestroyed(surface: Surface) {
        Log.i("SurfaceManager", "surfaceDestroyed: ")
    }

    //    @Override
    override fun sendTouchEvent(event: MotionEvent) {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
            }

            MotionEvent.ACTION_MOVE -> {
            }

            MotionEvent.ACTION_UP -> {
                interval.start()
            }
        }
        LogUtil.i("摸到服务端的SurfaceView了" + event.action);
    }

    private fun draw(view: View?) {
        try {
            mPaint = Paint()
            val rect = Rect(0, 0, 2020, 1000)
            //获得canvas对象
            mCanvas = mSurface?.lockCanvas(rect)
            view?.measure(
                View.MeasureSpec.makeMeasureSpec(2000, View.MeasureSpec.AT_MOST),
                View.MeasureSpec.makeMeasureSpec(1280, View.MeasureSpec.AT_MOST)
            )
            val bitmap = Bitmap.createBitmap(1500, 1500, Bitmap.Config.ARGB_8888)
            val canvas = Canvas(bitmap)
            canvas.drawColor(0, PorterDuff.Mode.CLEAR)
            view?.layout(0, 0, 1500, 1000)
            view?.draw(canvas)
            mCanvas?.drawBitmap(bitmap, 0f, 0f, null)
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            if (mCanvas != null) {
                //释放canvas对象并提交画布
                mSurface?.unlockCanvasAndPost(mCanvas)
                mCanvas = null
            }
            if (progress >= 100) {
                progress = 0
                interval.cancel()
            } else {
                progress++
            }
        }
    }
}

 上述代码中,我们通过外部传入的Surface获取到Canvas(画布)对象,然后将xml进行inflate(充气),最后将inflate的布局转为bitmap渲染到Canvas中

4.Service创建

由于我们自身的项目是作为布局的提供者,建议是作为服务端来实现,因此我们这里要新建一个Service提供给其他应用来进行绑定

package cn.itbox.auto.driver.common.aidl

import android.app.Service
import android.content.Intent
import android.os.IBinder
import org.xutils.common.util.LogUtil

class SurfaceViewService : Service() {
    override fun onBind(intent: Intent): IBinder {
        // 将SurfaceManager作为Binder返回。
        return SurfaceManager().asBinder()
    }

    override fun onCreate() {
        LogUtil.e("onCreate")
        super.onCreate()
    }

    override fun onUnbind(intent: Intent?): Boolean {
        LogUtil.e("onUnbind")
        return super.onUnbind(intent)
    }

}

同时,不要忘了Service需要在清单文件中注册,需要注意的是exported要设置为true,不然其他应用会访问不到

<service
            android:name=".common.aidl.SurfaceViewService"
            android:exported="true" />

上述就是实现aidl服务端(提供Surface)以及渲染的全部代码了,接下来,我们可以自己实现一个客户端来进行自测

5.客户端(接收方)实现aidl

aidl要求文件名、文件路径、方法名在两个进程中必须完全一致,因此我们直接复制服务端的aidl文件过来即可,注意文件目录要保持一致(否则项目访问不到会崩溃)

 接下来,直接去绑定服务端的Service即可,此过程不过多赘述

package cn.itbox.auto.test.serfaceviewclient;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.widget.Button;

import cn.itbox.auto.driver.SurfaceViewManager;

public class MainActivity extends Activity implements View.OnClickListener, SurfaceHolder.Callback, SurfaceView.OnTouchListener {

    Button bindServiceBt;
    Button createSurfaceBt;
    SurfaceView surfaceView;
    SurfaceViewManager manager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    @SuppressLint("ClickableViewAccessibility")
    private void initView() {
        bindServiceBt = findViewById(R.id.bind_service);
        createSurfaceBt = findViewById(R.id.create_surface);
        surfaceView = findViewById(R.id.surface);
        bindServiceBt.setOnClickListener(this);
        createSurfaceBt.setOnClickListener(this);
        surfaceView.getHolder().addCallback(this);
        surfaceView.setClickable(true);
        surfaceView.setOnTouchListener(this);
    }

    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.bind_service) {
            // 绑定service
            Intent intent = new Intent();
            intent.setClassName("cn.itbox.auto.driver", "cn.itbox.auto.driver.common.aidl.SurfaceViewService");
            bindService(intent, serviceConn, Context.BIND_AUTO_CREATE);
        } else if (view.getId() == R.id.create_surface) {
            // 将SurfaceView设为可见,这也是SurfaceView生命周期的开始。
            surfaceView.setVisibility(View.VISIBLE);
        }
    }

    ServiceConnection serviceConn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            Log.e("MainActivity", "onServiceConnected: success");
            // 连接成功后拿到管理器
            manager = SurfaceViewManager.Stub.asInterface(iBinder);
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    };

    /**
     * 将SurfaceView的生命周期对应时间点完成绘制
     */
    @Override
    public void surfaceCreated(SurfaceHolder surfaceHolder) {
        try {
            if (manager != null)
                manager.surfaceCreated(surfaceHolder.getSurface());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
        try {
            if (manager != null)
                manager.surfaceChanged(surfaceHolder.getSurface());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
        try {
            if (manager != null)
                manager.surfaceDestroyed(surfaceHolder.getSurface());
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    /**
     * 将SurfaceView的Touch事件传递给Service处理
     */
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        try {
            if (manager != null)
                manager.sendTouchEvent(motionEvent);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
        return false;
    }
}

布局也很简单,跨进程渲染的内容通过SurfaceView来展示

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <SurfaceView
        android:id="@+id/surface"
        android:layout_width="match_parent"
        android:layout_height="350dp"
        android:visibility="gone" />

    <Button
        android:id="@+id/bind_service"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="连接服务!" />

    <Button
        android:id="@+id/create_surface"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Surface created!" />

</LinearLayout>

至此,aidl传递客户端和服务端的全部实现完毕,开发者优先开启服务端后再开启客户端实现数据传递和展示自测

参考文献:Android Surface的跨进程绘制_surfaceview跨进程-CSDN博客

最后,原创不易希望大家能多点赞收藏支持,有什么不懂的也可以底下留言评论探讨

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1596273.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式学习day16-22(2024.04.06-13)

文章目录 C语言网络编程socket主机与网络字节序转换inet_addr、inet_aton&#xff08;ip转换&#xff09;inet_ntoa 网络字节序转换为IP字符串端口转换为网络字节序网络字节序转换为端口atoi &#xff08;字符串转换为整数&#xff09; UDP通信流程UDP多进程并发服务器服务端客…

【已测 非网上加密版】全新UI彩虹站长在线工具箱系统源码下载 全开源版本

支持高达72种站长工具、开发工具、娱乐工具等功能。本地调用API、自带免费API接口&#xff0c;是一个多功能性工具程序支持后台管理、上传插件、添加增减删功能。 环境要求 * PHP > 7.3 * MySQL > 5.6 * fileinfo扩展 * 使用Redis缓存需安装Redis扩展 部署 * 下载源代码 …

【嵌入式】交叉编译指南:将开源软件带到嵌入式世界

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向的学习指导…

Java反序列化Commons-Collections-CC1链

环境搭建 JDK8u71以下&#xff0c;这个漏洞已经被修复了&#xff0c;这个JDK的以上版本都修复了漏洞 JDK8u65 下载地址 https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html这个时候来到 pom.xml 配置Maven依赖下载CommonsCollections3.2.…

20V/1.5A替代LT1963低压差线性稳压器

概述(替代LT1963) PCD3941 是一款低压差稳压器&#xff0c;专为快速瞬态响应而优化。该装置能够提供 1.5A 的输出电流&#xff0c;典型压降为 160mV。工作静态电流为 1mA&#xff0c;关机时降至 1μA以下&#xff0c;同时压差模式下静态电流控制良好。除了快速瞬态响应外&…

【Linux】封装一下简单库 理解文件系统

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、封装一下简单库 二、理解一下stdin(0)、stdout(1)、stderr(3) 2.1、为什么要有0、1、2呢&#xff1f; 2.2、特点 2.3、如果我想让2也和1重定向到一个文件…

护眼台灯哪个牌子好?护眼灯十大品牌推荐,绝对真香!

对于有孩子的家庭&#xff0c;特别是阅读爱好者&#xff0c;晚上阅读时的光线问题至关重要。昏暗环境长时间阅读&#xff0c;会严重伤害孩子的眼睛。因此&#xff0c;选择一款合适的护眼台灯显得尤为重要。但市场上品牌众多&#xff0c;护眼台灯哪个牌子好?这往往让人难以抉择…

【Tomcat 文件读取/文件包含(CVE-2020-1938)漏洞复现】

文章目录 前言 一、漏洞名称 二、漏洞描述 三、受影响端口 四、受影响版本 五、漏洞验证 六、修复建议 前言 近日在做漏扫时发现提示服务器存在CVE-2020-1938漏洞&#xff0c;故文章记录一下相关内容。 一、漏洞名称 Tomcat 文件读取/文件包含漏洞(CVE-2020-1938) 二、漏洞描…

a == 1 a== 2 a== 3 返回 true ?

1. 前言 下面这道题是 阿里、百度、腾讯 三个大厂都出过的面试题&#xff0c;一个前端同事跳槽面试也被问了这道题 // &#xff1f; 位置应该怎么写&#xff0c;才能输出 trueconst a ?console.log(a 1 && a 2 && a 3) 看了大厂的面试题会对面试官的精神…

这款开发工具大大降低IoT开发门槛!完全开源,上手超简单

对开发者来说&#xff0c;IoT 开发的难点是什么&#xff1f;首先&#xff0c;IoT 涉及到多个领域和多种开发技术&#xff0c;每一层的技术接口、协议都需要跨平台、跨领域、跨系统的合作协同&#xff1b;在互联互通方面&#xff0c;智能设备间的兼容性亟待进一步地打通融合&…

ANSYS 2023版 下载地址及安装教程

ANSYS是一款著名的工程仿真软件&#xff0c;广泛应用于航空航天、汽车、能源和制造等领域。它为工程师和设计师提供了强大的建模、分析和优化工具&#xff0c;可以帮助他们预测和优化产品的性能。 ANSYS提供了广泛的模拟功能&#xff0c;包括结构力学、流体力学、电磁场和热传…

Unity | Shader基础知识(第十二集:颜色混合)

目录 前言 一、日常生活中的常见现象 二、unity自带的一个结构体&#xff08;表面着色器SurfaceOutputStandard&#xff09; 三、自己写一个颜色混合的Shader 1.只加基础颜色Albedo 2.加入法线 3.加入光滑度 4.加入金属度 5.加入自发光 四、作者的话 前言 shader里每一…

2024第十五届蓝桥杯 JAVA B组

目录 前言&#xff1a;试题 A: 报数游戏试题 B: 类斐波那契循环数试题C:分布式队列 前言&#xff1a; 没参加这次蓝桥杯算法赛&#xff0c;十四届蓝桥杯被狂虐&#xff0c;对算法又爱又恨&#xff0c;爱我会做的题&#xff0c;痛恨我连题都读不懂的题&#x1f62d;,十四届填空只…

计算方法实验5:对鸢尾花数据集进行主成分分析(PCA)并可视化

任务 iris数据集包含150条数据&#xff0c;从iris.txt读取&#xff0c;每条数据有4个属性值和一个标签&#xff08;标签取值为0&#xff0c;1&#xff0c;2&#xff09;。要求对这150个4维数据进行PCA&#xff0c;可视化展示这些数据在前两个主方向上的分布&#xff0c;其中不…

鸿蒙原生应用元服务-访问控制(权限)开发Stage模型向用户申请授权

一、向用户申请授权 当应用需要访问用户的隐私信息或使用系统能力时&#xff0c;例如获取位置信息、访问日历、使用相机拍摄照片或录制视频等&#xff0c;应该向用户请求授权。这需要使用 user_grant 类型权限。在此之前&#xff0c;应用需要进行权限校验&#xff0c;以判断当前…

Golang教程一(环境搭建,变量,数据类型,数组切片map)

目录 一、环境搭建 1.windows安装 2.linux安装 3.开发工具 二、变量定义与输入输出 1.变量定义 2.全局变量与局部变量 3.定义多个变量 4.常量定义 5.命名规范 6.输出 格式化输出 7.输入 三、基本数据类型 1.整数型 2.浮点型 3.字符型 4.字符串类型 转义字…

贝叶斯公式中的先验概率、后验概率、似然概率

欢迎关注博主 Mindtechnist 或加入【智能科技社区】一起学习和分享Linux、C、C、Python、Matlab&#xff0c;机器人运动控制、多机器人协作&#xff0c;智能优化算法&#xff0c;滤波估计、多传感器信息融合&#xff0c;机器学习&#xff0c;人工智能等相关领域的知识和技术。关…

Neo4j 图形数据库中有哪些构建块?

Neo4j 图形数据库具有以下构建块 - 节点属性关系标签数据浏览器 节点 节点是 Graph 的基本单位。 它包含具有键值对的属性&#xff0c;如下图所示。 NEmployee 节点 在这里&#xff0c;节点 Name "Employee" &#xff0c;它包含一组属性作为键值对。 属性 属性是…

华为HarmonyOS 4.2公测升级计划扩展至15款新机型

华为近日宣布&#xff0c;HarmonyOS 4.2操作系统的公测升级计划将扩展到包括华为P50系列在内的15款设备。这一更新旨在为用户提供更优化的系统性能和增强的功能。 参与此次公测的机型包括华为P50、华为P50 Pro及其典藏版、华为P50E、华为P50 Pocket及其艺术定制版、华为nova系…

Unity开发HoloLens2应用时,用VisualStudio进行真机在线Debug调试

一、需求 用Unity开发的应用&#xff0c;部署到真机设备出现启动崩溃&#xff0c;此时可以用在线调试&#xff0c;排查错误。 二、开发环境说明 MRholoLens2 Unity 2021.3.18 Win Win10 VS vs2022 三、调试操作步骤 1、HoloLens2与电脑的连接&#xff0c;Wifi连接&…