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

1 篇文章 0 订阅
1 篇文章 0 订阅

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

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

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

  • 10
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,下面是一个简单的使用SurfaceView绘制图形的Android示例程序: 首先,在XML布局文件中定义一个SurfaceView: ```xml <SurfaceView android:id="@+id/surfaceView" android:layout_width="match_parent" android:layout_height="match_parent" /> ``` 然后,在Java代码中创建SurfaceView类并实现SurfaceHolder.Callback接口: ```java public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback { private SurfaceHolder holder; private Paint paint; private boolean isDrawing; public MySurfaceView(Context context, AttributeSet attrs) { super(context, attrs); holder = getHolder(); holder.addCallback(this); paint = new Paint(); paint.setColor(Color.RED); paint.setStyle(Paint.Style.STROKE); paint.setStrokeWidth(5); isDrawing = false; } @Override public void surfaceCreated(SurfaceHolder holder) { isDrawing = true; new Thread(new Runnable() { @Override public void run() { Canvas canvas = null; try { canvas = holder.lockCanvas(); canvas.drawCircle(100, 100, 50, paint); } finally { if (canvas != null) { holder.unlockCanvasAndPost(canvas); } } } }).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { isDrawing = false; } } ``` 在这个例子中,我们创建了一个自定义视图MySurfaceView,实现了SurfaceHolder.Callback接口。在构造函数中,我们获取SurfaceHolder对象,并设置为回调接口。在surfaceCreated方法中,我们创建一个线程,在线程中获取Canvas对象并绘制一个圆形。在surfaceDestroyed方法中,我们停止绘制线程。 这个例子只是一个简单的使用SurfaceView绘制图形的示例,你可以根据自己的需求和应用场景进行更加复杂和丰富的图形绘制。同时需要注意的是,在使用SurfaceView绘制图形时,需要考虑线程同步和资源释放的问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值