From 99b1849e7f893131c4081e9d948588b854515777 Mon Sep 17 00:00:00 2001 From: "let5sne.win10" Date: Sat, 14 Feb 2026 20:50:33 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DAndroid=20app=20UI?= =?UTF-8?q?=E4=B8=8D=E6=98=BE=E7=A4=BA=E4=BF=A1=E6=81=AF=E5=8F=8A=E6=8B=8D?= =?UTF-8?q?=E7=85=A7=E4=BF=9D=E5=AD=98=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 切换到Light主题,解决深色模式下文字不可见 - 重新布局为横屏左右分栏,显示设备IP和连接方式 - 拍照改用MediaStore写入系统相册,修复原acquireLatestImage竞争导致的空帧 - 修正ImageFormat导入包路径 - 补充gradle-wrapper.jar Co-Authored-By: Claude Opus 4.6 --- android-app/app/src/main/AndroidManifest.xml | 2 +- .../main/java/com/usbwebcam/CameraHelper.kt | 82 +++++++++------- .../main/java/com/usbwebcam/MainActivity.kt | 62 +++++++----- .../app/src/main/res/layout/activity_main.xml | 90 ++++++++++++------ android-app/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 45633 bytes 5 files changed, 146 insertions(+), 90 deletions(-) create mode 100644 android-app/gradle/wrapper/gradle-wrapper.jar diff --git a/android-app/app/src/main/AndroidManifest.xml b/android-app/app/src/main/AndroidManifest.xml index b424460..bcd96f4 100644 --- a/android-app/app/src/main/AndroidManifest.xml +++ b/android-app/app/src/main/AndroidManifest.xml @@ -11,7 +11,7 @@ + android:theme="@style/Theme.AppCompat.Light.NoActionBar"> - val buffer = image.planes[0].buffer - val bytes = ByteArray(buffer.remaining()) - buffer.get(bytes) + /** + * 保存最新一帧到系统相册,返回是否成功 + */ + fun captureAndSave(): Boolean { + val jpeg = latestJpeg ?: return false - val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) - val file = File( - context.getExternalFilesDir(null), - "envelope_$timestamp.jpg" - ) + val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + val filename = "envelope_$timestamp.jpg" - FileOutputStream(file).use { it.write(bytes) } + val contentValues = ContentValues().apply { + put(MediaStore.Images.Media.DISPLAY_NAME, filename) + put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/信封拍照") + put(MediaStore.Images.Media.IS_PENDING, 1) + } + } + + val resolver = context.contentResolver + val uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) + ?: return false + + return try { + resolver.openOutputStream(uri)?.use { it.write(jpeg) } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + contentValues.clear() + contentValues.put(MediaStore.Images.Media.IS_PENDING, 0) + resolver.update(uri, contentValues, null, null) + } + true + } catch (e: Exception) { + resolver.delete(uri, null, null) + e.printStackTrace() + false } } @@ -156,25 +174,15 @@ class CameraHelper( e.printStackTrace() } - try { - captureSession?.close() - } catch (e: Exception) { - e.printStackTrace() - } + try { captureSession?.close() } catch (_: Exception) {} captureSession = null - try { - imageReader?.close() - } catch (e: Exception) { - e.printStackTrace() - } + try { imageReader?.close() } catch (_: Exception) {} imageReader = null - try { - cameraDevice?.close() - } catch (e: Exception) { - e.printStackTrace() - } + try { cameraDevice?.close() } catch (_: Exception) {} cameraDevice = null + + latestJpeg = null } } diff --git a/android-app/app/src/main/java/com/usbwebcam/MainActivity.kt b/android-app/app/src/main/java/com/usbwebcam/MainActivity.kt index 9ed0e29..be94d38 100644 --- a/android-app/app/src/main/java/com/usbwebcam/MainActivity.kt +++ b/android-app/app/src/main/java/com/usbwebcam/MainActivity.kt @@ -3,12 +3,14 @@ package com.usbwebcam import android.Manifest import android.content.pm.PackageManager import android.os.Bundle +import android.view.View import android.widget.Button import android.widget.TextView import android.widget.Toast import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import java.net.NetworkInterface class MainActivity : AppCompatActivity() { private var mjpegServer: MjpegServer? = null @@ -28,29 +30,24 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - val btnStart = findViewById