You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python服务端传Windows桌面截图至Kotlin客户端显示异常求助

问题:Socket传输截图至Android ImageView无法显示

我正在开发一个项目,流程是用Python服务端捕获Windows桌面截图,通过Socket把截图发送给Kotlin客户端,最后在ImageView里显示。但现在图片没法正常显示。

现有代码

Python服务端

import socket
import pyscreenshot as ImageGrab

HOST = 'localhost'  
PORT = 8080  

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()

    conn, addr = s.accept()
    with conn:
        print('Connected by', addr)
        while True:
            img = ImageGrab.grab()
            img_bytes = img.tobytes()
            conn.sendall(len(img_bytes).to_bytes(4, 'big') + img_bytes)

Kotlin客户端(ImageClient)

package com.voxadmin.windowtranslation

import android.graphics.BitmapFactory
import android.os.AsyncTask
import android.widget.ImageView
import java.io.DataInputStream
import java.net.Socket

class ImageClient(private val imageView: ImageView) : AsyncTask<Void, Void, ByteArray>() {

    override fun doInBackground(vararg params: Void?): ByteArray? {
        var socket: Socket? = null
        var dataInputStream: DataInputStream? = null

        try {
            socket = Socket("localhost", 8080)
            dataInputStream = DataInputStream(socket.getInputStream())

            val imageSize = dataInputStream.readInt()

            val imageBytes = ByteArray(imageSize)
            dataInputStream.readFully(imageBytes)

            return imageBytes
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            dataInputStream?.close()
            socket?.close()
        }

        return null
    }

    override fun onPostExecute(result: ByteArray?) {
        super.onPostExecute(result)
        if (result != null && result.isNotEmpty()) {
            val bitmap = BitmapFactory.decodeByteArray(result, 0, result.size)
            imageView.setImageBitmap(bitmap)
        }
    }
}

MainActivity

package com.voxadmin.windowtranslation

import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.ByteArrayInputStream
import java.io.InputStream

class MainActivity : AppCompatActivity() {

    private lateinit var imageView: ImageView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        imageView = findViewById(R.id.imageView)

        GlobalScope.launch(Dispatchers.IO) {
            val imageBytes = ImageClient(imageView).execute().get()
            if (imageBytes != null && imageBytes.isNotEmpty()) {
                val bitmap = byteArrayToBitmap(imageBytes)
                runOnUiThread {
                    imageView.setImageBitmap(bitmap)
                }
            }
        }
    }

    private fun byteArrayToBitmap(byteArray: ByteArray): Bitmap
    {
        val inputStream: InputStream = ByteArrayInputStream(byteArray)
        return BitmapFactory.decodeStream(inputStream)
    }
}

问题原因及修复方案

1. 原始像素数据无法被BitmapFactory解析

Python中img.tobytes()输出的是原始像素数据(如RGB/RGBA),但Android的BitmapFactory只能识别JPEG/PNG这类编码后的图片字节流。需要先将截图编码为标准图片格式。

修改后的Python服务端:

import socket
import pyscreenshot as ImageGrab
from io import BytesIO

HOST = 'localhost'  
PORT = 8080  

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()

    conn, addr = s.accept()
    with conn:
        print('Connected by', addr)
        while True:
            img = ImageGrab.grab()
            # 将截图编码为JPEG格式(也可以用PNG,JPEG体积更小)
            buffer = BytesIO()
            img.save(buffer, format='JPEG', quality=80)  # quality控制压缩率,可选
            img_bytes = buffer.getvalue()
            # 先发送字节长度,再发送图片数据
            conn.sendall(len(img_bytes).to_bytes(4, 'big') + img_bytes)

2. 客户端只接收一次数据就断开连接

原客户端ImageClient的AsyncTask只接收一次图片就关闭Socket,但服务端是无限循环发送的。同时MainActivity中用execute().get()阻塞协程,还重复处理了字节转Bitmap的逻辑。

修改后的Kotlin客户端:

package com.voxadmin.windowtranslation

import android.graphics.BitmapFactory
import android.os.Handler
import android.os.Looper
import android.widget.ImageView
import java.io.DataInputStream
import java.net.Socket

class ImageClient(private val imageView: ImageView) {
    private val uiHandler = Handler(Looper.getMainLooper())
    private var isRunning = true

    // 启动持续接收线程
    fun startReceiving() {
        Thread {
            var socket: Socket? = null
            var inputStream: DataInputStream? = null

            try {
                socket = Socket("localhost", 8080)
                inputStream = DataInputStream(socket.getInputStream())

                while (isRunning) {
                    // 读取图片长度
                    val imageSize = inputStream.readInt()
                    val imageBytes = ByteArray(imageSize)
                    inputStream.readFully(imageBytes)

                    // 在UI线程更新ImageView
                    uiHandler.post {
                        val bitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
                        imageView.setImageBitmap(bitmap)
                    }
                }
            } catch (e: Exception) {
                e.printStackTrace()
            } finally {
                inputStream?.close()
                socket?.close()
            }
        }.start()
    }

    // 停止接收
    fun stopReceiving() {
        isRunning = false
    }
}

修改后的MainActivity:

package com.voxadmin.windowtranslation

import android.os.Bundle
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity

class MainActivity : AppCompatActivity() {
    private lateinit var imageView: ImageView
    private lateinit var imageClient: ImageClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        imageView = findViewById(R.id.imageView)
        imageClient = ImageClient(imageView)
        imageClient.startReceiving()
    }

    // 页面销毁时停止接收,避免内存泄漏
    override fun onDestroy() {
        super.onDestroy()
        imageClient.stopReceiving()
    }
}

3. Android网络权限与明文通信限制

  • 在AndroidManifest.xml中添加网络权限:
<uses-permission android:name="android.permission.INTERNET" />
  • 如果是Android 9(API 28)及以上,默认禁止明文Socket通信,需要在<application>标签内添加:
android:usesCleartextTraffic="true"

4. 模拟器连接问题

如果用Android模拟器测试,需要通过adb反向代理让模拟器访问电脑的localhost:

adb reverse tcp:8080 tcp:8080

内容的提问来源于stack exchange,提问作者Keith Engel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 09:54:53