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
相关产品推荐
相关产品推荐

