Android Socket使用TCP协议实现手机投屏

news2024/11/24 21:01:02

本节主要通过实战来了解Socket在TCP/IP协议中充当的是一个什么角色,有什么作用。通过Socket使用TCP协议实现局域网内手机A充当服务端,手机B充当客户端,手机B连接手机A,手机A获取屏幕数据转化为Bitmap,通过Socket传递个手机B显示。

实现效果:

一、 Socket是什么?

Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

主机 A 的应用程序要能和主机 B 的应用程序通信,必须通过 Socket 建立连接,而建立 Socket 连接必须需要底层TCP/IP 协议来建立 TCP 连接。建立 TCP 连接需要底层 IP 协议来寻址网络中的主机。我们知道网络层使用的 IP 协议可以帮助我们根据 IP 地址来找到目标主机,但是一台主机上可能运行着多个应用程序,如何才能与指定的应用程序通信就要通过 TCP 或 UPD 的地址也就是端口号来指定。这样就可以通过一个 Socket 实例唯一代表一个主机上的一个应用程序的通信链路了。 

短连接:连接->传输数据->关闭连接:

传统HTTP是无状态的,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。也可以这样说:短连接是指SOCKET连接后发送后接收完数据后马上断开连接。

长连接:连接->传输数据->保持连接 -> 传输数据-> …… ->关闭连接:

长连接指建立SOCKET连接后不管是否使用都保持连接。

什么时候用长连接,短连接?

长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况,。每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,下次处理时直接发送数据包就OK了,不用建立TCP连接。例如:数据库的连接用长连接, 如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。

而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源。

总之,长连接和短连接的选择要视情况而定。 而我们接下来要实现的手机实时投屏效果使用的就是长连接。

二、Socket的使用:

在使用Socket时,我们会使用到ServiceSocket和Socket,ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作。连接成功后,双方通过输入和输出流进行同步阻塞式通信。

1、创建TcpServerRunnable TCP服务端

1)由于Android在使用网络通讯时要放在子线程中执行,所以可以将TcpServerRunnable实现Runnable接口。

public class TcpServerRunnable implements Runnable {
    @Override
    public void run() {
       
    }
}

2)在执行run方法里面创建ServiceSocket,ServerSocket内部使用的是TCP协议,如果想要使用UDP协议可以使用DatagramSocket。

private boolean ServerCreate() {
	try {
		serverSocket = new ServerSocket(port, 1);
	} catch (Exception e) {
		e.printStackTrace();
		if (listener != null) {
			listener.onServerClose();
		}
		return false;
	}
	return true;
}

3)创建成功后,开启 while 循环监听TCP服务端是否被客户端连接;

private void ServerRun() {
	if (!ServerCreate()) {
		return;
	}
	while (true) {
		if (!ServerListen()) {
			break;
		}
	}
}

private boolean ServerListen() {
	try {
		socket = serverSocket.accept();
	} catch (Exception e) {
		e.printStackTrace();
		return false;
	}
	if (listener != null) {
		listener.onServerConnect();
	}
	return true;
}

4)当有客户端连接到服务端时,开启 while 循环,从内存中拿取bitmap(屏幕数据),组装协议数据,发送给客户端。

private void ServerRun() {
	if (!ServerCreate()) {
		return;
	}
	while (true) {
		if (!ServerListen()) {
			break;
		}
		while (ServerIsConnect()) {
			ServerTransmitBitmap();
			ServerSleep(10);
		}
	}
}

private final static byte[] PACKAGE_HEAD = {(byte) 0xFF, (byte) 0xCF, (byte) 0xFA, (byte) 0xBF, (byte) 0xF6, (byte) 0xAF, (byte) 0xFE, (byte) 0xFF};

/**
 * 写入 协议头+投屏bitmap数据
 */
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();
	}
}

当执行到dataOutputStream.flush();时,就会将数据发送给客户端,由于ServerTransmitBitmap()方法是在 while 循环里面的,所以当手机屏幕数据刷新后,重新赋值给bitmap,服务端会自动将新的bitmap再次组装发送给客户端,实现投屏实时刷新的效果。

public class TcpServerRunnable implements Runnable {

    private static final String TAG = "TcpServerRunnable";

    private ServerSocket serverSocket;
    private Socket socket;
    private int port;
    private Bitmap bitmap;


    public void setPort(int port) {
        this.port = port;
    }

    public void setBitmap(Bitmap bitmap) {
        this.bitmap = bitmap;
    }

    @Override
    public void run() {
        ServerRun();
    }

    /**
     * 运行服务端
     */
    private void ServerRun() {
        if (!ServerCreate()) {
            return;
        }
        while (true) {
            if (!ServerListen()) {
                break;
            }
            while (ServerIsConnect()) {
                ServerTransmitBitmap();
                ServerSleep(10);
            }
        }
    }

    /**
     * 使用ServerSocket创建TCP服务端
     *
     * @return
     */
    private boolean ServerCreate() {
        try {
            serverSocket = new ServerSocket(port, 1);
        } catch (Exception e) {
            e.printStackTrace();
            if (listener != null) {
                listener.onServerClose();
            }
            return false;
        }
        return true;
    }

    /**
     * 循环监听服务端是否被连接
     *
     * @return
     */
    private boolean ServerListen() {
        try {
            socket = serverSocket.accept();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        if (listener != null) {
            listener.onServerConnect();
        }
        return true;
    }

    /**
     * 判断服务端是否被连接
     *
     * @return
     */
    private boolean ServerIsConnect() {
        return socket != null && !socket.isClosed() && socket.isConnected();
    }

    private final static byte[] PACKAGE_HEAD = {(byte) 0xFF, (byte) 0xCF, (byte) 0xFA, (byte) 0xBF, (byte) 0xF6, (byte) 0xAF, (byte) 0xFE, (byte) 0xFF};

    /**
     * 写入 协议头+投屏bitmap数据
     */
    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();
        }
    }

    private void ServerSleep(long millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭连接
     */
    public void close() {
        ServerClose();
    }

    private void ServerClose() {
        try {
            if (socket != null) {
                socket.close();
                serverSocket.close();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (listener != null) {
            listener.onServerClose();
        }
    }

    private ServerListener listener;

    public void setListener(ServerListener listener) {
        this.listener = listener;
    }

    public interface ServerListener {
        void onServerConnect();

        void onServerClose();
    }
}

2、获取手机屏幕数据,并转化为bitmap

1)创建ScreenCaptureService前台服务,执行处理捕获设备屏幕的单例类ScreenCapture。

public class ScreenCaptureService extends Service {

    private ScreenCapture screenCapture;

    public ScreenCaptureService() {
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            createNotificationChannel(); //创建通知栏,你正在录屏
        }
        Bundle bundle = intent.getExtras();
        if (bundle != null) {
            int resultCode = bundle.getInt("resultCode");
            Intent data = bundle.getParcelable("resultData");
            screenCapture = ScreenCapture.getInstance(this, resultCode, data);
        }
        screenCapture.startScreenCapture();
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        screenCapture.stopScreenCapture();
    }

    private void createNotificationChannel() {
       
    }
}

2)创建MediaProjection,捕获设备屏幕上的内容,并将数据转化为Bitmap,MyUtils.setBitmap(bitmap) 存储到静态变量中。

public class ScreenCapture implements ImageReader.OnImageAvailableListener{

    private static final String TAG = "ScreenCapture";

    private final MediaProjection mMediaProjection; // 用于捕获设备屏幕上的内容并进行录制或截图
    private VirtualDisplay mVirtualDisplay;
    private final ImageReader mImageReader;
    private final int screen_width;
    private final int screen_height;
    private final int screen_density;
    private static volatile ScreenCapture screenCapture;

    @SuppressLint("WrongConstant")
    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;
    }

    public void startScreenCapture() {
        if (mMediaProjection != null) {
            setUpVirtualDisplay();
        }
    }

    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);
    }

    public void stopScreenCapture() {
        if (mVirtualDisplay != null) {
            mVirtualDisplay.release();
        }
    }

    @Override
    public 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();
        }
    }
}

3、对获取的屏幕Bitmap进行压缩,降低Bitmap大小,加快Socket传输速度。(非必须,不压缩也行)。创建BitmapProcessRunnable实现Runnable接口,在子线程执行bitmap压缩操作。

public class BitmapProcessRunnable implements Runnable {

    private static final String TAG = "BitmapProcessRunnable";

    private boolean isRun = false;

    public void setRun(boolean isRun) {
        this.isRun = isRun;
    }

    @Override
    public void run() {
        while (isRun) {
            try {
                Bitmap bitmap = MyUtils.getBitmap();
                Log.i(TAG, "bitmap:" + bitmap);
                if (bitmap != null) {
                    bitmap = MyUtils.BitmapMatrixCompress(bitmap);
                    if (listener != null) {
                        listener.onProcessBitmap(bitmap);
                    }
                }
                Thread.sleep(10);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private ProcessListener listener;

    public interface ProcessListener {
        void onProcessBitmap(Bitmap bitmap);
    }

    public void setListener(ProcessListener listener) {
        this.listener = listener;
    }
}
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);
}

3、关联TCP客户端ServerFragment页面

1)在ServerFragment页面创建TcpServerRunnable(TCP服务端)和BitmapProcessRunnable(图片处理线程),当用户点击创建按钮时,调用MyUtils.ExecuteRunnable(tcpServerRunnable),执行TcpServerRunnable的run方法。

2)客户端连接TCP服务端后,在回调接口onServerConnect()里面,开启前台服务ScreenCaptureService,捕获屏幕数据,同时执行BitmapProcessRunnable的run方法,对获取到的bitmap进行压缩,压缩完成,将新bitmap赋值给TcpServerRunnable(TCP服务端)。

3)TcpServerRunnable(TCP服务端)的 while 循环里面读取到新的bitmap,进行组装bitmap协议数据,发送给客户端。

public class ServerFragment extends Fragment implements View.OnClickListener {

    private static final String TAG = "ServerFragment";

    private static boolean server_create = false;

    private TextView server_text;
    private Button create_button;

    private TcpServerRunnable tcpServerRunnable; // TCP服务端
    private BitmapProcessRunnable bitmapProcessRunnable; // 图片处理线程

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_server, container, false);
    }

    @Override
    public void onViewCreated(@NonNull @NotNull View view, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        server_text = view.findViewById(R.id.server_text);
        server_text.setText(MyUtils.getLocalAddress());

        create_button = view.findViewById(R.id.create_button);
        create_button.setOnClickListener(this);

        bitmapProcessRunnable = new BitmapProcessRunnable();
        bitmapProcessRunnable.setListener(bitmapProcessListener);

        tcpServerRunnable = new TcpServerRunnable();
        tcpServerRunnable.setPort(MyUtils.tcpSocketPort);
        tcpServerRunnable.setListener(tcpServerListener);
    }

    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.create_button) {
            if (!server_create) {
                server_create = true;
                MyUtils.ExecuteRunnable(tcpServerRunnable);
                create_button.setText("关闭");
            } else {
                server_create = false;
                tcpServerRunnable.close();
                create_button.setText("创建");
            }
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        tcpServerRunnable.close();
    }

    TcpServerRunnable.ServerListener tcpServerListener = new TcpServerRunnable.ServerListener() {
        @Override
        public void onServerConnect() {
            bitmapProcessRunnable.setRun(true);
            // 压缩图片
            MyUtils.ExecuteRunnable(bitmapProcessRunnable);
            Bundle bundle = new Bundle();
            Intent start = new Intent(getActivity(), ScreenCaptureService.class);
            bundle.putInt("resultCode", MyUtils.getResultCode());
            bundle.putParcelable("resultData", MyUtils.getResultData());
            start.putExtras(bundle);
            // 启动投屏服务
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                getActivity().startForegroundService(start);
            } else {
                getActivity().startService(start);
            }
        }

        @Override
        public void onServerClose() {
            bitmapProcessRunnable.setRun(false);
            Intent stop = new Intent(getActivity(), ScreenCaptureService.class);
            getActivity().stopService(stop);
        }
    };

    BitmapProcessRunnable.ProcessListener bitmapProcessListener = new BitmapProcessRunnable.ProcessListener() {
        @Override
        public void onProcessBitmap(Bitmap bitmap) {
            // 将压缩后的图片传给tcpServer,tcpServer发送给客户端
            tcpServerRunnable.setBitmap(bitmap);
        }
    };
}

4、创建TcpClientRunnable TCP客户端

1)同样在子线程中开启连接TCP服务端,这里需要知道服务端的IP地址和端口号。

@Override
public void run() {
	ClientRun();
}

private void ClientRun() {
	if (!ClientConnect()) {
		return;
	}
}

private boolean ClientConnect() {
	try {
		socket = new Socket(ip, port);
	} catch (Exception e) {
		e.printStackTrace();
		return false;
	}
	if (listener != null) {
		listener.onClientConnect();
	}
	return true;
}

2)连接成功后,开启 while 循环,接收服务端发送过来的bitmap数据,赋值给静态变量MyUtils.setBitmap()。

private void ClientRun() {
	if (!ClientConnect()) {
		return;
	}
	while (true) {
		while (ClientIsConnect()) {
			ClientReceiveBitmap();
			ClientSleep(10);
		}
	}
}

private void ClientReceiveBitmap() {
	try {
		Log.i(TAG,"循环读取服务端传过来的投屏Bitmap");
		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();
	}
}

5、客户端创建显示投屏的DisplayActivity

1)DisplayActivity通过自定义DisplayView来实时显示投屏数据;

public class DisplayActivity extends AppCompatActivity {

    private static final String TAG = "DisplayActivity";
    private DisplayView displayView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_display);
        initView();
    }

    private void initView() {
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
        displayView = findViewById(R.id.displayView);
        displayView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                displayView.setViewWidth(displayView.getWidth());
                displayView.setViewHeight(displayView.getHeight());
                displayView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
            }
        });
    }

}

2)DisplayView继承自SurfaceView,并实现Runnable接口,当DisplayView添加到Activity后,Surface第一次被创建时回调到void surfaceCreated(),然后在surfaceCreated方法里面启动自己的run方法,循环的将bitmap绘制到页面上。

public class DisplayView extends SurfaceView implements SurfaceHolder.Callback,  Runnable{

    private static final String TAG = "DisplayView";

    private int viewWidth;
    private int viewHeight;
    private Bitmap bitmap;
    private int width;
    private int height;
    private SurfaceHolder surfaceHolder;
    private boolean isDraw = false;

    public DisplayView(Context context) {
        super(context);
        initView();
    }

    public DisplayView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public DisplayView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView() {
        surfaceHolder = getHolder();
        surfaceHolder.addCallback(this);
        surfaceHolder.setFormat(PixelFormat.TRANSLUCENT);
        setZOrderMediaOverlay(true);
    }

    @Override
    public void surfaceCreated(@NonNull SurfaceHolder holder) {
        isDraw = true;
        MyUtils.ExecuteRunnable(this);
    }

    @Override
    public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
        isDraw = false;
    }

    @Override
    public void run() {
        while (isDraw) {
            try {
                drawBitmap();
                Thread.sleep(10);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    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);
        }
    }

    public Bitmap getBitmap() {
        // return bitmap;
        return MyUtils.getShowBitmap();
    }

    public void setBitmap(Bitmap bitmap, int width, int height) {
        this.bitmap = bitmap;
        this.width = width;
        this.height = height;
    }

    public void setViewWidth(int width) {
        this.viewWidth = width;
    }

    public void setViewHeight(int height) {
        this.viewHeight = height;
    }
}

6、关联TCP客户端ClientFragment页面

1)在ClientFragment页面创建TcpClientRunnable(TCP客户端端)当用户输入服务端IP地址,点击连接按钮时,调用MyUtils.ExecuteRunnable(tcpClientRunnable),执行TcpClientRunnable的run方法。

2)客户端连接TCP服务端后,在回调接口onClientConnect()里面,开启DisplayActivity,DisplayView创建,等待bitmap写入MyUtils.setBitmap,循环的将bitmap绘制到页面上。

3)当客户端接收到服务端发送过来的投屏bitmap时,回调到onClientReceiveBitmap(),将bitmap设置到静态变量里面MyUtils.setBitmap(bitmap, width, height)。

public class ClientFragment extends Fragment implements View.OnClickListener {

    private static final String TAG = "ClientFragment";

    private static boolean client_connect = false;

    private TextView client_edit;
    private Button connect_button;

    private TcpClientRunnable tcpClientRunnable;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_client, container, false);
    }

    @Override
    public void onViewCreated(@NonNull @NotNull View view, @Nullable @org.jetbrains.annotations.Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        client_edit = view.findViewById(R.id.client_edit);
        client_edit.setText(MyUtils.getLocalIp());

        connect_button = view.findViewById(R.id.connect_button);
        connect_button.setOnClickListener(this);

        tcpClientRunnable = new TcpClientRunnable();
        tcpClientRunnable.setPort(MyUtils.tcpSocketPort);
        tcpClientRunnable.setListener(tcpClientListener);
    }

    @Override
    public void onClick(View view) {
        if (view.getId() == R.id.connect_button) {
            if (client_edit.getText().toString().trim().length() == 0) {
                Toast.makeText(getActivity(), "请输入服务端IP", Toast.LENGTH_SHORT).show();
            } else {
                if (!client_connect) {
                    client_connect = true;
                    tcpClientRunnable.setIp(client_edit.getText().toString().trim());
                    MyUtils.ExecuteRunnable(tcpClientRunnable);
                    client_edit.setText(MyUtils.getLocalIp());
                    client_edit.setEnabled(false);
                    connect_button.setText("断开");
                } else {
                    client_connect = false;
                    tcpClientRunnable.close();
                    client_edit.setText(client_edit.getText().toString().trim());
                    client_edit.setEnabled(true);
                    connect_button.setText("连接");
                }
            }
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        tcpClientRunnable.close();
    }

    TcpClientRunnable.ClientListener tcpClientListener = new TcpClientRunnable.ClientListener() {
        @Override
        public void onClientConnect() {
            Log.i(TAG,"TCP连接成功,跳转DisplayActivity");
            Intent intent = new Intent(getActivity(), DisplayActivity.class);
            startActivity(intent);
        }

        @Override
        public void onClientClose() {
            ToastUtils.showShort("TCP连接断开!");
        }

        @Override
        public void onClientReceiveBitmap(Bitmap bitmap, int width, int height) {
            MyUtils.setBitmap(bitmap, width, height);
        }
    };
}

至此,通过Socket实现的手机局域网投屏软件完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/865748.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

打造专属花店展示小程序

在当今社会,微信小程序已经成为了各行各业拓展客户资源的利器,而花店行业也不例外。通过打造一个独特的花店小程序,你可以为你的花店带来更多的曝光和客户资源。那么,如何制作一个专属的花店小程序呢?下面我们就来一步…

领航优配:股票分红为什么股价下降?分红有什么好处?

股票分红是一种报答股东的方法,也是一种表现公司价值的方法。那么股票分红为什么股价下降?分红有什么优点?领航优配也为大家准备了相关内容,以供参考。 股票分红为什么股价下降? 股票进行分红后股价出现跌落是一种很常…

新疆大学841软件工程考研

1.软件生产的发展经历了三个阶段,分别是____、程序系统时代和软件工程时代时代。 2.可行性研究从以下三个方面研究每种解决方法的可行性:经济可行性、社会可行性和_____。 3.HIPO图的H图用于描述软件的层次关系&…

【ROS】fsd_algorithm架构学习与源码分析(致敬)

😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍fsd_algorithm架构学习与源码分析。 无专精则不能成,无涉猎则不能通。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下&am…

Git全栈体系(四)

第七章 IDEA 集成 Git 一、配置 Git 忽略文件 1. Eclipse 特定文件 2. IDEA 特定文件 3. Maven 工程的 target 目录 4. 问题 4.1 为什么要忽略他们? 与项目的实际功能无关,不参与服务器上部署运行。把它们忽略掉能够屏蔽 IDE 工具之间的差异。 4.2 …

eNSP 实现 CLI 窗口叠放

文章目录 1 问题截图2 问题解决3 扩展3.1 打开所有 CLI3.2 CLI:Command line interface 1 问题截图 问题描述:命令行窗口是分开的,找对应的窗口太麻烦了 2 问题解决 解决办法:点下图控件即可。 效果展示: 3 扩展 …

文档控件DevExpress Office File API v23.1新版亮点 - 支持.NET MAUI

DevExpress Office File API是一个专为C#, VB.NET 和 ASP.NET等开发人员提供的非可视化.NET库。有了这个库,不用安装Microsoft Office,就可以完全自动处理Excel、Word等文档。开发人员使用一个非常易于操作的API就可以生成XLS, XLSx, DOC, DOCx, RTF, CS…

大数据Flink(六十):Flink 数据流和分层 API介绍

文章目录 Flink 数据流和分层 API介绍 一、​​​​​​​​​​​​​​Flink 数据流

实现同时查找多个关键词——KeywordCrafter - 关键词匠心

具体功能:同时查找多个关键词,高亮加粗显示,并关键词显示出现次数。 🧐碎碎念:最近在写文案的时候,总是要避免出现一个敏感词汇,利用 (commandF) or (CtrF) 查找,只能一个一个单词去…

使用 AndroidX 增强 WebView 的能力

在App开发过程中,为了在多个平台上保持一致的用户体验和提高开发效率,许多应用程序选择使用 H5 技术。在 Android 平台上,通常使用 WebView 组件来承载 H5 内容以供展示。 一.WebView 存在的问题 自 Android Lollipop 起,WebVie…

TFN 新推出信息安全产品 ,手机安全(插卡监听器)探测器 FW5 反窃听数字协议无线探测器

本产品是新研制的检测设备,工程师或反监测专家把它作为一个可靠的工具,用来 跟踪各种无线电数字传输设备,例如 GSM 、蓝牙等新型视听设备。随着现代科学技术 的不断发展,不同的数字传输方式已在我们的生活中得到了广泛的应用。例…

01 - 工作区、暂存区、版本库、远程仓库 - 以一次连贯的提交操作为例

1. 工作区、暂存区、版本库、远程仓库 以一次连贯的提交操作为例。 1.1 工作区 Git的工作区也就是我们平时编辑代码的目录文件夹。 新建一个kongfu_person.txt文件,工作区的变化: 1.2 工作区 > 暂存区:git add 1.3 暂存区 > 版本库…

新能源汽车需要检测哪些项目

截至2022年底,中国新能源车保有量达1310万辆,其中纯电动汽车保有量1045万辆。为把好新能源汽车安全关,我国新能源汽车除了完善的强制性产品认证型式实验外,还建立了“车企-地方-国家”逐级上报的三级监管体系实行新能源汽车全生命…

2000-2021年地级市碳排放数据/地级市二氧化碳排放

2000-2021年地级市碳排放数据/地级市二氧化碳排放 1、时间:2000-2021年 2、来源:主要是各级统计年鉴、相关统计资料等。其中,能源部分分能源品种分部门的能 源消费数据来源于《中国能源统计年鉴》以及各级统计年鉴;工业过程和产…

编译Linux的时候出现 Restart config...

环境 buildroot 问题 在buildroot路径下执行 make linux-rebuild 出现了Restart config… 的问题 原因 我在修改linux源码的时候,在没有指定ARCH架构的情况下, 直接去linux源码目录(output/build/linux/)下执行了 make menuconfig并覆盖了原来的.c…

Linux 共享内存mmap,进程通信

文章目录 前言一、存储映射 I/O二、mmap, munmap三、父子进程间 mmap 通信四、非血缘关系进程间 mmap 提通信五、mmap 匿名映射区总结 前言 进程间通信是操作系统中重要的概念之一,使得不同的进程可以相互交换数据和进行协作。其中,共享内存…

Pycharm解决启动时候索引慢的问题

设置里去掉update里面的两个勾 shared indexes中,把自动下载索引改成不下载使用本地索引

JVM G1垃圾回收机制介绍

G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于“标记-整理”算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是&…

【OpenAI】当人工智能遇到道德困境问题(铁轨选择问题),它会怎么回复?

一条铁轨上,五个小孩在玩耍,而一辆火车即将撞上他们,这时你就在铁轨转轴拉杆的旁边,你可以选择拉下拉杆,让火车开向另一条铁轨,但是,另一条铁轨上,有一个铁轨工人正在休息。你会怎么…

赴日IT培训 想拿高度人才签证 加10分的IT资格哪个简单?

其实日本和我们国家一样,IT方面的资格证也是有很多的,你或许会担心万一我考的证没用可怎么办?我们应该知道,我们作为一个外国人,在掌握一些日本语言的情况下,考证本身对咱们来说就是一个学习的过程&#xf…