概述
Android屏幕共享-传输图片
该篇主要讲解安卓与安卓之间如何截图传输,属于安卓屏幕共享的入门篇。适用场景:安卓屏幕共享工具类应用。
效果图
效果图
demo界面
前言
目前安卓屏幕共享的方式有很多种软件已经实现,究其根源,无非就是采集-传输-播放
的过程,而安卓端无论是截图传输还是硬解码,也一定离不开核心类MediaProjection
,它就是Google开放了视频录制的接口(屏幕采集的接口)。
比较优秀的软件举例:
- TeamViewer
- 向日葵
- Vysor
其中Vysor就是通过adb
实现无root静默截图,然后传输,实现电脑控制手机。在整个屏幕共享的过程中,重点在于如何采集,采集到画质清晰内存又小的技术成为核心。
常用的安卓端采集技术有:
- MediaProjection 实现截图
- MediaProjection 硬解码
- ffmpeg 软解码
- adb 实现无root截图
功能点
- android5.0及以上免root截图
- 安卓屏幕变化的时候采集
- 多端共享拓展简单
- socket传输字节数组
- 尽可能都使用原生代码实现
功能讲解
截图
随着安卓系统的不断升级,权限也越来越收紧,无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);
}
}
@Override
protected 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 {
@Override
public 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);
}
@Override
public void onOpen(WebSocket webSocket, ClientHandshake handshake) {
LogWrapper.d(TAG, "有用户链接");
mWebSocket = webSocket;
}
@Override
public void onClose(WebSocket conn, int code, String reason, boolean remote) {
LogWrapper.d(TAG, "有用户离开");
}
@Override
public void onMessage(WebSocket conn, String message) {
LogWrapper.e(TAG, "接收到消息:" + message);
}
@Override
public void onError(WebSocket conn, Exception ex) {
LogWrapper.e(TAG, "发生error:" + ex.toString());
}
@Override
public 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;
}
@Override
public void onOpen(ServerHandshake handshakeData) {
LogWrapper.e(TAG, "onOpen");
updateClientStatus(true);
try {
getSocket().setReceiveBufferSize(5 * 1024 * 1024);
} catch (SocketException e) {
e.printStackTrace();
}
}
@Override
public void onMessage(String message) {
}
@Override
public void onMessage(ByteBuffer bytes) {
byte[] buf = new byte[bytes.remaining()];
bytes.get(buf);
if (mCallBack != null)
mCallBack.onBitmapReceived(BitmapUtils.decodeImg(buf));
}
@Override
public void onClose(int code, String reason, boolean remote) {
updateClientStatus(false);
}
@Override
public 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屏幕共享-传输图片Android屏幕共享-传输图片Demo下载的全部内容,希望文章能够帮你解决Android屏幕共享-传输图片Android屏幕共享-传输图片Demo下载所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复