【Android】屏幕共享与反向控制功能的实现

  • 前言
  • 一、功能介绍
    • 1.屏幕共享
    • 2.反向控制
  • 二、功能原理
    • 1.原理框图
    • 2.工作原理
      • (1)MediaProjection截屏
      • (2)SurfaceView显示
      • (3)TCP传输Bitmap
      • (4)ADB端口转发
  • 三、效果演示
  • 总结

前言

之前用了一下QQ电脑版的远程协助,发现这个功能很方便实用,于是就想开发一款类似功能的APP,无奈本人只会一点点Android和Java,开发过程中爬了很多坑,但是经过不懈努力,终于把基本功能实现了。


一、功能介绍

1.屏幕共享

这个APP主要有屏幕共享和反向控制两个功能。屏幕共享功能的实现需要两台手机,一台手机作为服务端,共享屏幕;另一台手机做客户端,显示屏幕。服务端与客户端需要在同一局域网或热点连接。服务端主要是通过MediaProjection实时截屏,通过TCP把图片数据发送给客户端;客户端则把TCP接收的图片数据通过SurfaceView渲染显示。

2.反向控制

反向控制的功能主要是结合了ADB。这个功能的实现需要手机服务端先开启 开发者模式及USB调试,然后用USB连接电脑端。共享屏幕时,在电脑端运行Python或其他语言编写的脚本,客户端的SurfaceView会侦听用户的触摸事件,并通过服务端TCP传输给电脑端,电脑端则发送ADB命令给服务端,从而实现客户端反向控制服务端的功能。

二、功能原理

1.原理框图

2.工作原理

(1)MediaProjection截屏

MediaProjection是Google在Android5.0之后给开发者提供的截屏或录屏方法。在使用MediaProjection之前需要先申请权限。

    private void Request_Media_Projection_Permission() {MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) this.getSystemService(Context.MEDIA_PROJECTION_SERVICE);Intent intent = mediaProjectionManager.createScreenCaptureIntent();startActivityForResult(intent, REQUEST_MEDIA_PROJECTION_CODE);}@Overridepublic void onActivityResult(int requestCode, int resultCode, Intent data) {super.onActivityResult(requestCode, resultCode, data);if (requestCode == REQUEST_MEDIA_PROJECTION_CODE) {if (resultCode != Activity.RESULT_OK) {Toast.makeText(this, "Media Projection Permission Denied", Toast.LENGTH_SHORT).show();return;}MyUtils.setResultCode(resultCode);MyUtils.setResultData(data);}}private ScreenCapture(Context context, int resultCode, Intent data) {MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);screen_width = MyUtils.getScreenWidth();screen_height = MyUtils.getScreenHeight();screen_density = MyUtils.getScreenDensity();mImageReader = ImageReader.newInstance(screen_width,screen_height,PixelFormat.RGBA_8888,2);}public static ScreenCapture getInstance(Context context, int resultCode, Intent data) {if(screenCapture == null) {synchronized (ScreenCapture.class) {if(screenCapture == null) {screenCapture = new ScreenCapture(context, resultCode, data);}}}return screenCapture;}

MediaProjection通过createVirtualDisplay来截屏,我们可以通过ImageReader的setOnImageAvailableListener把截屏数据转为Bitmap数据。

    private void setUpVirtualDisplay() {mVirtualDisplay = mMediaProjection.createVirtualDisplay("ScreenCapture",screen_width,screen_height,screen_density,DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,mImageReader.getSurface(),null,null);mImageReader.setOnImageAvailableListener(this, null);}@Overridepublic void onImageAvailable(ImageReader imageReader) {try {Image image = imageReader.acquireLatestImage();if(image != null) {Image.Plane[] planes = image.getPlanes();ByteBuffer buffer = planes[0].getBuffer();int pixelStride = planes[0].getPixelStride();int rowStride = planes[0].getRowStride();int rowPadding = rowStride - pixelStride * screen_width;Bitmap bitmap = Bitmap.createBitmap(screen_width + rowPadding / pixelStride, screen_height, Bitmap.Config.ARGB_8888);bitmap.copyPixelsFromBuffer(buffer);MyUtils.setBitmap(bitmap);image.close();}} catch (Exception e) {e.printStackTrace();}}

(2)SurfaceView显示

SurfaceView渲染图片是在独立线程里进行的,所以它显示大图片会更快更流畅。我们可以新建一个View来继承它,并在这个View里实现我们想要的功能,比如显示Bitmap。侦听用户的触摸事件主要是通过View的OnTouchListener来实现的。

    public void drawBitmap() {Canvas canvas = surfaceHolder.lockCanvas();if (canvas != null) {bitmap = getBitmap();if (bitmap != null) {canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);Rect rect = new Rect(0, 0, viewWidth, viewHeight);canvas.drawBitmap(bitmap, null, rect, null);}surfaceHolder.unlockCanvasAndPost(canvas);}}@Overridepublic boolean onTouch(View view, MotionEvent motionEvent) {switch (motionEvent.getAction()) {case MotionEvent.ACTION_DOWN:int staX = (int) (motionEvent.getX() * getWidthConvert());int staY = (int) (motionEvent.getY() * getHeightConvert());MyUtils.setStartX(staX);MyUtils.setStartY(staY);touchClientRunnable.setTouchDown(true);break;case MotionEvent.ACTION_UP:int endX = (int) (motionEvent.getX() * getWidthConvert());int endY = (int) (motionEvent.getY() * getHeightConvert());MyUtils.setEndX(endX);MyUtils.setEndY(endY);touchClientRunnable.setTouchUp(true);break;}return true;}@Overridepublic void run() {while (isDraw) {try {drawBitmap();setOnTouchListener(this);Thread.sleep(10);} catch (Exception e) {e.printStackTrace();}}}

(3)TCP传输Bitmap

由于截屏的图片很大,直接传输会很慢,所以我们需要对图片进行压缩处理,这里采用的是缩放压缩。

    public static Bitmap BitmapMatrixCompress(Bitmap bitmap) {Matrix matrix = new Matrix();matrix.setScale(0.5f, 0.5f);return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);}

服务端发送Bitmap

    private final static byte[] PACKAGE_HEAD = {(byte)0xFF, (byte)0xCF, (byte)0xFA, (byte)0xBF, (byte)0xF6, (byte)0xAF, (byte)0xFE, (byte)0xFF};public static byte[] BitmaptoBytes(Bitmap bitmap) {ByteArrayOutputStream baos = new ByteArrayOutputStream();bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);return baos.toByteArray();}private void ServerTransmitBitmap() {try {DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());if (bitmap != null) {byte[] bytes = MyUtils.BitmaptoBytes(bitmap);dataOutputStream.write(PACKAGE_HEAD);dataOutputStream.writeInt(MyUtils.getScreenWidth());dataOutputStream.writeInt(MyUtils.getScreenHeight());dataOutputStream.writeInt(bytes.length);dataOutputStream.write(bytes);}dataOutputStream.flush();} catch (IOException e) {e.printStackTrace();}}

客户端接收Bitmap

    private final static byte[] PACKAGE_HEAD = {(byte)0xFF, (byte)0xCF, (byte)0xFA, (byte)0xBF, (byte)0xF6, (byte)0xAF, (byte)0xFE, (byte)0xFF};public static Bitmap BytestoBitmap(byte[] b) {if(b.length != 0) {return BitmapFactory.decodeByteArray(b, 0, b.length);} else {return null;}}private void ClientReceiveBitmap() {try {InputStream inputStream = socket.getInputStream();boolean isHead = true;for (byte b : PACKAGE_HEAD) {byte head = (byte) inputStream.read();if (head != b) {isHead = false;break;}}if (isHead) {DataInputStream dataInputStream = new DataInputStream(inputStream);int width = dataInputStream.readInt();int height = dataInputStream.readInt();int len = dataInputStream.readInt();byte[] bytes = new byte[len];dataInputStream.readFully(bytes, 0, len);Bitmap bitmap = MyUtils.BytestoBitmap(bytes);if (bitmap != null && width != 0 && height != 0) {if (listener != null) {listener.onClientReceiveBitmap(bitmap, width, height);}}}} catch (Exception e) {e.printStackTrace();}}

(4)ADB端口转发

反向控制主要是用到了adb forward命令进行端口转发,其实也是TCP通信。使用这种方法主要是手机不用ROOT。

import json
import os
import socket
isConnect = False
isTouch = False
ack = os.popen('adb forward tcp:50003 tcp:50004').read()
if ack.find('error') == 0:isConnect = Falseprint('no device')
else:isConnect = True
if isConnect:client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)client.connect(('127.0.0.1', 50003))while True:try:msg = client.recv(2048)data = json.loads(msg.decode('utf-8'))staX = data.get('staX')staY = data.get('staY')endX = data.get('endX')endY = data.get('endY')action = data.get('action')if action != 0:isTouch = Trueif isTouch:cmd = ''if action == 1:cmd = 'adb shell input tap {} {}'.format(staX, staY)elif action == 2:cmd = 'adb shell input swipe {} {} {} {}'.format(staX, staY, endX, endY)elif action == 3:cmd = 'adb shell input keyevent 4'os.system(cmd)isTouch = Falseaction = 0print(cmd)except Exception:continue

以上是部分代码片段。

三、效果演示


总结

现阶段主要是实现了基本功能,还存在很多缺陷,现在只支持在局域网或热点下共享屏幕,屏幕显示有很明显的延迟,反向控制需要连接电脑等。

【Android】局域网屏幕共享与反向控制功能的实现相关推荐

  1. Android屏幕投影及反向控制原理

    这一周过的是够有意思的,先停两天电,然后感冒了,然后项目出Bug了,然后发烧了,呵呵哒,赶紧只能过来写点东西压压惊.鉴于最近正好在研究Android投屏及反像控制和Android双开的技术原理,本周就 ...

  2. Windows上Android手机屏幕共享指南

    把Android手机屏幕共享的电脑的方法一般有如下两种: 方法一: 使用XX手机助手 网上有很多手机助手,比如百度手机助手.在电脑上下载安装后,里边 有个小工具叫屏幕共享,连上手机,打开就可以用了. ...

  3. 基于 P2P 技术的 Android 局域网内设备通信实践

    Android 局域网内的多设备通信方式有多种,其中常见的方式有: 基于 TCP/UDP 的 Socket 通信 基于 Bluetooth 的近场通信 基于 Wifi 的 Wi-Fi Direct 连 ...

  4. android不装电池打开无线,你用或者不用,Android 11都支持反向无线充电

    原标题:你用或者不用,Android 11都支持反向无线充电 谷歌刚正式发布的Android 11开发者预览版中被XDA大神发现了名为"Battery share"的隐藏新功能.B ...

  5. android 12.0 wifi开关控制功能实现

    1.前言 在12.0的产品rom定制化开发中,在产品开发中,对于功能的开发的功能也是挺多的,而在对于wifi的功能定制需求,有要求需要通过系统属性来控制wifi开关是否可以打开 来控制是否可以连接wi ...

  6. android 局域网 发现,局域网内android设备发现及通讯

    最近一个项目需要实现在局域网内android手机操控另一个没有显示屏的android设备(音箱),具体实现就是手机端向音箱端发送命令字符串,音箱端通过解析命令字符串来完成操作,而手机端也要实时显示音箱 ...

  7. android手机屏幕共享神器踩坑指南

    开源项目地址:https://github.com/Genymobile/scrcpy scrcpy,由 Genymobile 推出的可跨平台的.可自定义码率的.开源的屏幕共享工具.它提供了在 USB ...

  8. Android 局域网扫描

    本篇简单介绍通过UDP广播扫描局域网设备IP,并且通过ZMQ进行通信. UDP连接 主要流程: 1.1 Step1:主机发送广播信息,并指定接收端的端口(9000) 广播地址(255.255.255. ...

  9. vlc android局域网rtsp,VLC mosaic分屏显示多路RTSP媒体流问题。

    局域网环境内有两台Android机,装了Spydroid来获取传送RTSP媒体流,配置如下: Video Encoder: H.264 Resolution: 640*480 Framerate: 8 ...

  10. Android局域网实现FTP文件上传下载客户端与服务端

    文章目录 前言 一.FTP是什么? 二.使用步骤 1 服务端 1.1 服务端的代码实现 2 客户端 2.1 客户端的代码实现 附件 前言 最近在公司的项目中,使用到了 局域网通信,不同的设备直接传递消 ...

最新文章

  1. 百度Apollo升级发布15大新品,还要化身无人车基建狂魔 | 一文看尽首届Apollo生态大会...
  2. 一次mysql数据库连接池泄露的解决经历
  3. drbd配置文件_Linux数据安全工具:数据镜像软件DRBD的安装与配置
  4. React 状态管理库: Mobx
  5. Linux集群和自动化维1.4.2 优化Linux下的内核TCP参数以提高系统性能
  6. 没有体现JAVA接口功能_深入浅出分析Java抽象类和接口【功能,定义,用法,区别】...
  7. jquery 判断点击次数_jquery编程开发实现点击页面计算点击次数
  8. django ForeignKey on_delete属性相关参数的使用
  9. C++学习笔记(14) static_cast 与 dynamic_cast
  10. 若依图片上传成功不能显示的解决办法?
  11. 从Iris数据集开始---机器学习入门
  12. word计算机排版怎么选,Word选择题选项对齐排版方法 查找替换工具搞定
  13. CDC::Arc 汉化参数明说及举例
  14. 《善用佳软:高效能人士的软件应用之道》一2.2 流程图绘制软件:免费的Visio替代品...
  15. 数据库与excel数据对比
  16. 怎么取消苹果订阅自动续费?教你一招,2分钟搞定!
  17. 谈谈网络工程师的就业方向与薪资水平
  18. ERROR: Exception when publishing, exception message
  19. 软件测试基础篇(3)
  20. android的app勾选了通知消息,为何Android中大部分锁屏APP都要手动勾选“通知使用权”(Notification Access)?...

热门文章

  1. 易语言EXUI游戏充值系统源码
  2. 元胞自动机(又称细胞自动机)
  3. [架构之路-42]:目标系统 - 系统软件 - Linux下的网络通信-2-无线局域网WIFI原理、WIFI与3G/4G/以太网/蓝牙的协议转换
  4. 车辆检测和跟踪技术的研究与实现
  5. 机器学习种9种常用算法
  6. Hotspot 偏向锁BiasedLocking 源码解析
  7. 如何去除 WinRAR 的弹窗广告
  8. SPSS时序全局主成分分析方法
  9. 51单片机驱动WS2811彩灯源程序方案
  10. STM32驱动WS2811