我是靠谱客的博主 忧伤蜻蜓,最近开发中收集的这篇文章主要介绍Android屏幕共享-传输图片Android屏幕共享-传输图片Demo下载,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

Android屏幕共享-传输图片

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

效果图

效果图

效果图
demo界面

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);
        }
    }
    
    @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下载所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(42)

评论列表共有 0 条评论

立即
投稿
返回
顶部