Android屏幕共享-传输图片

该篇主要讲解安卓与安卓之间如何截图传输,属于安卓屏幕共享的入门篇。适用场景:安卓屏幕共享工具类应用。

效果图

效果图


demo界面

前言

目前安卓屏幕共享的方式有很多种软件已经实现,究其根源,无非就是采集-传输-播放的过程,而安卓端无论是截图传输还是硬解码,也一定离不开核心类MediaProjection,它就是Google开放了视频录制的接口(屏幕采集的接口)。

比较优秀的软件举例:

  • TeamViewer
  • 向日葵
  • Vysor

其中Vysor就是通过adb实现无root静默截图,然后传输,实现电脑控制手机。在整个屏幕共享的过程中,重点在于如何采集,采集到画质清晰内存又小的技术成为核心。

常用的安卓端采集技术有:

  • MediaProjection 实现截图
  • MediaProjection 硬解码
  • ffmpeg 软解码
  • adb 实现无root截图

功能点

  1. android5.0及以上免root截图
  2. 安卓屏幕变化的时候采集
  3. 多端共享拓展简单
  4. socket传输字节数组
  5. 尽可能都使用原生代码实现

功能讲解

截图

随着安卓系统的不断升级,权限也越来越收紧,无root并且不连接数据线做不到静默截图,这里指截取不属于该app的区域。

  • 在允许连接数据线的情况下,可以通过adb shell push 事先编译好的 dex 文件到手机中,实现静默截图。
  • 在没有链接数据线无root的情况下,只能显式的让用户有感知的进行截取。本篇截图使用的是 MediaProjection, 如下图:

MediaProjection 是Android 5.0 开放的屏幕截图与录制视频的接口,是一个系统级的服务,在使用前需要动态申请权限,并在onActivityResult进行处理。
eg:

     /*** 申请截屏权限*/private void tryStartScreenShot() {MediaProjectionManager mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);if (mProjectionManager != null) {startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);}}
    @Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) {if (requestCode == REQUEST_MEDIA_PROJECTION && data != null) {if (resultCode == RESULT_OK) {// 截屏的回调ScreenShotHelper screenShotHelper = new ScreenShotHelper(this, resultCode, data, this);screenShotHelper.startScreenShot();} else if (resultCode == RESULT_CANCELED) {LogWrapper.d(TAG, "用户取消");}}}

在屏幕共享时,一直截图就会很费性能,如果在屏幕改变时进行截图就好了。
刚好,在ImageReader中有setOnImageAvailableListener可以让我们方便的拿到屏幕变化的数据。eg:

     /*** 屏幕发生变化时截取*/private class ImageAvailableListener implements ImageReader.OnImageAvailableListener {@Overridepublic void onImageAvailable(ImageReader reader) {try (Image image = reader.acquireLatestImage()) {if (image != null) {// todo }} catch (Exception e) {e.printStackTrace();}}}

图片处理

图片格式:

  • png:无损压缩图片格式,支持Alpha通道
  • jpeg:有损压缩图片格式,不支持背景透明
  • webp:“WebP 是 Android 4.2.1(API 级别 17)支持的较新图片格式。这种格式可为网络上的图片提供出色的无损压缩和有损压缩效果。使用 WebP,开发者可以创建更小、更丰富的图片。WebP 无损图片文件比 PNG 平均缩小了 26%。这些图片文件还支持透明度(也称为 Alpha 通道),只需增加 22% 的字节。
    WebP 有损图片比采用等效 SSIM 质量指标的同等 JPG 图片缩小 25-34%。对于可以接受有损 RGB 压缩的情况,有损 WebP 也支持透明度,生成的文件大小通常比 PNG 小 3 倍。” – 出自谷歌官方文档

笔者曾尝试过webp格式的编码,大小确实降低了50%左右,但是编码耗时却长了100%,相当于用CPU换网速。这样看来webp并不适用该场景。

图片压缩:

  • 压缩大小
  • 压缩图片质量

该Demo的代码中是将bitmap压缩到固定的大小

Server:

/*** 压缩图片 (压缩后不代表实际大小,有差异)** @param bitmap    被压缩的图片* @param sizeLimit 大小限制  单位 k* @return 压缩后的图片*/public static Bitmap compressBitmap(Bitmap bitmap, long sizeLimit) {ByteArrayOutputStream baos = new ByteArrayOutputStream();int quality = 90;bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);// 循环判断压缩后图片是否超过限制大小while (baos.toByteArray().length / 1024 > sizeLimit) {baos.reset();bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos);quality -= 10;}return BitmapFactory.decodeStream(new ByteArrayInputStream(baos.toByteArray()), null, null);}

socket传输

原生socket在发送大量数据时,会进行分包,接收的时候也就必须要合包;
有一些第三方socket框架会帮助我们处理合包的操作,websocket则是在它的协议标准中处理了这个问题。
所以该demo中选用了websocket进行通讯。拿到的截图的数据放进去就可以了。


import com.talon.screen.quick.util.LogWrapper;
import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;import java.net.InetSocketAddress;/*** @author by Talon, Date on 2020-04-13.* note: websocket 服务端*/
public class MWebSocketServer extends WebSocketServer {private final String TAG = "MWebSocketServer";private WebSocket mWebSocket;private boolean mIsStarted = false;private CallBack mCallBack;public MWebSocketServer(int port, CallBack callBack) {super(new InetSocketAddress(port));this.mCallBack = callBack;setReuseAddr(true);setConnectionLostTimeout(5 * 1000);}@Overridepublic void onOpen(WebSocket webSocket, ClientHandshake handshake) {LogWrapper.d(TAG, "有用户链接");mWebSocket = webSocket;}@Overridepublic void onClose(WebSocket conn, int code, String reason, boolean remote) {LogWrapper.d(TAG, "有用户离开");}@Overridepublic void onMessage(WebSocket conn, String message) {LogWrapper.e(TAG, "接收到消息:" + message);}@Overridepublic void onError(WebSocket conn, Exception ex) {LogWrapper.e(TAG, "发生error:" + ex.toString());}@Overridepublic void onStart() {updateServerStatus(true);}/*** 停止服务器*/public void socketStop() {try {super.stop(100);updateServerStatus(false);} catch (InterruptedException e) {e.printStackTrace();}}/*** 发送二进制** @param bytes*/public void sendBytes(byte[] bytes) {if (mWebSocket != null)mWebSocket.send(bytes);}private void updateServerStatus(boolean isStarted) {mIsStarted = isStarted;LogWrapper.e(TAG, "mIsStarted:" + mIsStarted);// 回调if (mCallBack != null)mCallBack.onServerStatus(isStarted);}public boolean isStarted() {LogWrapper.e(TAG, "mIsStarted:" + mIsStarted);return mIsStarted;}public interface CallBack {void onServerStatus(boolean isStarted);}}

Client:

import com.talon.screen.quick.util.BitmapUtils;
import com.talon.screen.quick.util.LogWrapper;import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;import java.net.SocketException;
import java.net.URI;
import java.nio.ByteBuffer;/*** @author by Talon, Date on 2020-04-13.* note: websocket 客户端*/
public class MWebSocketClient extends WebSocketClient {private final String TAG = "MWebSocketClient";private boolean mIsConnected = false;private CallBack mCallBack;public MWebSocketClient(URI serverUri, CallBack callBack) {super(serverUri);this.mCallBack = callBack;}@Overridepublic void onOpen(ServerHandshake handshakeData) {LogWrapper.e(TAG, "onOpen");updateClientStatus(true);try {getSocket().setReceiveBufferSize(5 * 1024 * 1024);} catch (SocketException e) {e.printStackTrace();}}@Overridepublic void onMessage(String message) {}@Overridepublic void onMessage(ByteBuffer bytes) {byte[] buf = new byte[bytes.remaining()];bytes.get(buf);if (mCallBack != null)mCallBack.onBitmapReceived(BitmapUtils.decodeImg(buf));}@Overridepublic void onClose(int code, String reason, boolean remote) {updateClientStatus(false);}@Overridepublic void onError(Exception ex) {updateClientStatus(false);}private void updateClientStatus(boolean isConnected) {mIsConnected = isConnected;LogWrapper.d(TAG, "mIsConnected:" + mIsConnected);// 回调if (mCallBack != null)mCallBack.onClientStatus(isConnected);}public boolean isConnected() {LogWrapper.d(TAG, "mIsConnected:" + mIsConnected);return mIsConnected;}public interface CallBack {void onClientStatus(boolean isConnected);void onBitmapReceived(Bitmap bitmap);}}

Demo下载

demo下载(github)
demo下载(CSDN)

Android屏幕共享-传输图片相关推荐

  1. Android屏幕共享及远程控制【免root】

    Android屏幕共享及远程控制[免root] 使用方式 对于mac 笔记本用户: Android手机开启开发者选项 用数据线连接Android手机和mac 运行lib目录下的Client,用于显示和 ...

  2. android屏幕共享demo,屏幕共享

    # 屏幕共享 功能简介: 在视频会话中为了提高沟通效率,可以将自己的屏幕内容分享给其他参与方观看.还支持在屏幕上进行标注,以及授权其他参与方进行远程控制.当屏幕共享者开启标注后,控件就进入标注模式.此 ...

  3. 组播屏幕共享、Android屏幕共享开发小结

               因近期项目需要,实现了一套多种网络拓扑.多种应用场景的多平台屏幕共享系统,包括组播屏幕共享.服务器转发屏幕共享.P2P屏幕共享,暂支持Windows屏幕共享给Windows,Wi ...

  4. android屏幕共享demo_「手机共享」Android手机之间实现屏幕共享 - seo实验室

    手机共享 已经实现,优化空间还很大. 效果Gif 原理: 方法一:A手机不停的调用系统截图,将得到的数据压缩后不停的socket发送至服务器,服务器得到数据后推送给B手机,B手机显示图片.  服务器我 ...

  5. macbook android 屏幕共享,苹果设备小技巧:iPhone,iPad,Mac进行屏幕共享和远程控制...

    随着生活的发展,对视频通话和屏幕共享的需求已大大增加.有时,当您不在身边时,可以轻松地通过电话或短信进行故障排除,但通常可以在屏幕上看到发生了什么,并可以远程访问权限.接下来,我们将介绍如何与iPho ...

  6. android屏幕共享mac,Mirror for Android TV

    应用程序将您的Mac屏幕和声音镜像到Android TV. 适用于任何电视,机顶盒或使用Android TV操作系统的媒体播放器.你也可以把单个视频文件从Mac电脑中传到电视上.另外,我们还给您提供了 ...

  7. android屏幕共享解决方案,Android手机之间实现屏幕共享-Go语言中文社区

    已经实现,优化空间还很大. 效果Gif 原理: 方法一:A手机不停的调用系统截图,将得到的数据压缩后不停的Socket发送至服务器,服务器得到数据后推送给B手机,B手机显示图片.  服务器我用node ...

  8. android usb传输图片,关于Android接入USB外接摄像头以及控制拍照并保存图片

    关于Android接入外接摄像头,首先毋庸置疑的是需要给你的app配置相应的权限 1.首先构建相应的视图view xmlns:tools="http://schemas.android.co ...

  9. android 蓝牙传输图片吗,如何使用蓝牙将Android手机中的照片和视频副本发送到树莓派...

    步骤1:将Raspberry Pi放入蓝牙设备 完成以下步骤我以前的版本将Raspberry Pi转换为可指导完成此步骤的Bluetooth设备. 步骤2:使Raspberry Pi设备成为Bluet ...

  10. android屏幕共享实现方式,基于WebRtc在Android端实现屏幕共享

    注:本文默认你已经掌握了如何用WebRtc建立视频连接.如果没有,请参考https://www.jianshu.com/p/eb5fd116e6c8,这里将不再赘述. 第一步:向系统发起屏幕截取请求 ...

最新文章

  1. 高级算法专家储开颜:无端不视频 阿里视频云三大端上技术能力
  2. Java代码统计某个字符串出现的次数
  3. 电子商务(六)-作业题解-第3章
  4. 五一快到了,我也该走了
  5. 打印SAP Spartacus generic link指向的url
  6. Node.js的helloworld 程序
  7. 立体匹配十大概念综述---立体匹配算法介绍
  8. 均胜群英:PC+移动端数字化管理,两年降本7%,人均产值提高300%
  9. Java实现MySQL数据库备份(一)
  10. html 父元素右下角,html – 如何在父元素和父元素的兄弟元素上显示子元素?
  11. 全网最详细的HBase启动以后,HMaster进程启动了,几秒钟以后自动关闭问题的解决办法(图文详解)
  12. 2020-02-16 Git客户端下载
  13. android中函数的直接使用用import就可以了吗各种类不用创建对象吗_React Hooks 如何安全地使用state...
  14. 传统梯度下降法面临的挑战
  15. beamer插入图片_在beamer中插入动画
  16. redis cluster master failover问题
  17. 静态化freemarker,分布式文件系统minIO
  18. TSP问题解析篇之自适应大邻域搜索(ALNS)算法深度通读(附python代码)
  19. opencv + face_recognition —— 人脸识别案例
  20. Boost库-功能介绍-Geometry-图形开发库-计算几何-常用功能封装-GraphicalDebugging(二)

热门文章

  1. 【华为云技术分享】漫谈LIteOS-物联网操作系统介绍
  2. 深度学习在搜狗无线搜索广告中的应用
  3. 5分钟学会用代码发送邮件
  4. DEP(数据执行保护)介绍
  5. 围棋的基本下法与规则
  6. 银行卡系统(面向对象进阶习题)
  7. matlab中zeta函数,黎曼zeta函数是什么,具体点
  8. dede php 里加nofollow,浅谈在dede当前位置与下一页中如何利用nofollow
  9. nofollow、noopener和noreferrer标签的区别
  10. VMware Workstation虚拟机安装及虚拟机搭建(内有虚拟机安装包及序列号和系统镜像)...