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

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

需求:需要将一个自定义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博客

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AliyunCTF 2024 - BadApple

文章目录 前言环境搭建漏洞分析漏洞利用参考 前言 本文首发于看雪论坛 https://bbs.kanxue.com/thread-281291.htm 依稀记得那晚被阿里CTF支配的恐惧&#xff0c;今年的阿里CTF笔者就做了一道签到PWN题&#xff0c;当时也是下定决心要学习 jsc pwn 然后复现这道 BadApple 题目…

30元腾讯云服务器搭建幻兽帕鲁Palworld多人联机游戏,畅玩

幻兽帕鲁太火了&#xff0c;官方palworld服务器不稳定&#xff1f;不如自建服务器&#xff0c;基于腾讯云幻兽帕鲁服务器成本32元全自动部署幻兽帕鲁服务器&#xff0c;超简单有手就行&#xff0c;全程自动化一键部署10秒钟即可搞定&#xff0c;无需玩家手动部署幻兽帕鲁游戏程…

Python基础整理(一万三千字)(一)

目录 一、Python解释器 解释器的作用&#xff1a; 下载Python解释器&#xff1a; 安装Python解释器&#xff1a; 二、注释 三、变量 定义变量&#xff1a; 标识符&#xff1a; 命名习惯&#xff1a; 变量使用&#xff1a; 变量的数据类型&#xff1a; 四、输出 格式化输出 …

聚酰亚胺PI材料难于粘接,用什么胶水粘接?那么让我们先一步步的从认识它开始(二十六): 聚酰亚胺PI材料为什么难于粘接

聚酰亚胺PI材料为什么难于粘接 聚酰亚胺&#xff08;PI&#xff09;材料难以粘接主要是由于其特殊的化学结构和物理性质&#xff1a; 化学稳定性&#xff1a;聚酰亚胺PI材料具有出色的化学稳定性&#xff0c;这使其对大多数化学溶剂和酸碱溶液都表现出良好的抵抗性&#xff0c;…

23电赛D题 CORDIC算法实践——Chisel计算对数函数

一、介绍 在本专栏之前的文章中:用Chisel快速搭建FFT流水线电路Chisel实践 —— 短时傅里叶变换模块的实现与测试 已经介绍到了如何使用Chisel开发FFT运算模块和STFT模块&#xff0c;此篇文章将详细介绍如何使用Chisel进行对数运算模块的开发。 如何使用硬件语言实现对数运算&…

得帆用户有福了!全新社区论坛携手AI助手华丽上线,积分好礼等你拿!

盼望着&#xff0c;盼望着&#xff0c;春天的脚步近了&#xff0c;得帆云社区迎来全新升级&#xff0c;社区论坛携手AI知识库助手上线了&#xff01; 得帆云官方社区论坛&#xff1a; https://edu.definesys.cn/community/community-forum 您也可以点击本文末尾左下方“阅读…

.rdl.data是什麼文件

https://learn.microsoft.com/zh-cn/sql/reporting-services/tools/reporting-services-in-sql-server-data-tools-ssdt?viewsql-server-ver16&redirectedfromMSDN

如何在Odoo 17库存中通过批次号和序列号追踪产品

在Odoo 17库存管理中&#xff0c;通过批次号和序列号追踪产品是一种确保产品从生产到销售全程可追溯的重要方式。在产品打包时或生产过程中会分配这些编号。批次号是指应用于具有相似属性的一组产品的一系列数字或代码&#xff0c;而序列号则是分配给特定单一物品的独特编号。O…

MATLAB5:数据和函数的可视化

文章目录 一、实验目的二、实验内容三、仿真结果四、实践中遇到的问题及解决方法 一、实验目的 1. 掌握基本的二维绘图中曲线图的绘制方法。   2. 掌握三维绘图中曲面图的绘制方法。   3. 掌握三维绘图中网线图的绘制方法。   4. 了解三维表面图的绘制方法。   5. 了解…

【Java框架】Mybatis教程(一)——环境搭建及基本CRUD操作

目录 持久化与ORMORM&#xff08;Object Relational Mapping&#xff09;ORM解决方案包含下面四个部分 MyBatis简介特点MyBatis框架优缺点优点缺点 搭建MyBatis开发环境步骤1. 创建Maven工程&#xff0c;导入MyBatis依赖的组件2. 编写MyBatis核心配置文件(mybatis-config.xml)示…

【C 数据结构】静态链表

文章目录 【 1. 基本原理 】1.1 静态链表中的节点1.2 备用链表 【 2. 静态链表的创建 】2.1 实例1 - 创建静态链表&#xff0c;指定值2.2 实例2 - 创建静态链表&#xff0c;默认值 【 3. 静态链表 添加元素 】【 4. 静态链表 删除元素 】【 5. 静态链表 查找元素 】【 6. 静态链…

腾讯EdgeOne产品测评体验—基于EO新特性与传统CDN的对比以凸显EO绝对优势【以导航站为例】

精益求精&#xff0c;卓越非凡。 ——《论语集注》 EdgeOne 作为腾讯云下一代的 CDN &#xff0c;提供域名解析、动静态智能加速、TCP/UDP 四层加速、DDoS/CC/Web/Bot 防护、边缘函数计算等一体化服务&#xff0c;也支持用户按业务需求&#xff0c;配置自定义复杂访问控制规…

Qt配置外部库(Windows平台)

这里以C的外部库nlopt为例子来示范&#xff0c;右键工程选择添加库&#xff0c;然后选择库文件的目录&#xff08;dll.a&#xff09;&#xff0c;会自动设置好包含路径&#xff08;一般是include的目录&#xff09;&#xff0c;添加库&#xff08;最下面一行&#xff09; &…

【Java】maven传递依赖冲突解决

传递依赖的概念&#xff1a; 传递依赖:&#xff1a; A.jar 依赖 B.jar, B.jar 依赖 C.jar, 这个时候我们就说B是A的直接依赖, C是A传递依赖; 传递依赖可能会产生冲突: 联系着上面, 新导入一个jar包D.jar, D依赖C.jar, 但是B依赖的1.1版本, 而D依赖的是1.2版本, 这时候C这个j…

ROS2从入门到精通1-3:详解ROS2动作通信机制与自定义动作

目录 0 专栏介绍1 动作通信模型2 动作模型实现(C)3 动作模型实现(Python)4 自定义动作 0 专栏介绍 本专栏旨在通过对ROS2的系统学习&#xff0c;掌握ROS2底层基本分布式原理&#xff0c;并具有机器人建模和应用ROS2进行实际项目的开发和调试的工程能力。 &#x1f680;详情&a…

设计模式——观察者模式17

观察者模式指多个对象间存在一对多的依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都得到通知并被自动更新。这种模式有时又称作发布-订阅模式。 中介者模式是N对N的双向关系。观察者模式是1对N的单向关系。 设计模式&#xff0c;一定要敲代码…

【Linux网络编程】UDP协议

UDP协议 1.再谈端口号端口号划分认识知名端口号(Well-Know Port Number)两个问题netstatpidof 2.UDP协议2.1UDP的特点2.2面向数据报2.3UDP的缓冲区2.4UDP使用注意事项2.5基于UDP的应用层协议 喜欢的点赞&#xff0c;收藏&#xff0c;关注一下把&#xff01; 1.再谈端口号 端口…

如何选择适用于Mac的文件恢复软件?适用于 Mac 的最佳数据恢复软件清单

有人会说&#xff0c;我们的数字生活正变得几乎和我们的物理生活一样重要。我们在线工作&#xff0c;将记忆保存在数码照片库中&#xff0c;在信使中交流&#xff0c;并保留各种文档的数字扫描。 每个人都知道备份是必不可少的。建议每天至少同步一个数字备份&#xff08;例如…

Spring Boot 学习(3)——Spring Initializr 创建项目问题解决

产生问题的原因&#xff0c;各种的版本都较老&#xff0c;所以导致出现问题。目前暂未打到合适的教程&#xff0c;按老教程学起来先。 小白瞎学&#xff0c;大神勿喷&#xff01; 再次强调环境&#xff1a;maven 3.3.9、jdk 1.8、idea 2017、Spring 4.3.13、Spring Boot 1.5.…

C/C++基础----指针

指针的定义 在c/c中&#xff0c;有一个特殊的变量指向我们电脑中某个内存地址&#xff0c;进而可以让我们操作这段内存&#xff0c;指的就是指针类型 语法&#xff1a; int a 10; int* p &a;&符号是取出某个变量的内存地址 把这个内存地址赋值给一个变量p&#xff…