diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bcb8d41..463100c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -26,6 +26,10 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/example/app/CameraActivity.kt b/app/src/main/java/com/example/app/CameraActivity.kt index a2f21d7..348b9f6 100644 --- a/app/src/main/java/com/example/app/CameraActivity.kt +++ b/app/src/main/java/com/example/app/CameraActivity.kt @@ -49,6 +49,7 @@ class CameraActivity : AppCompatActivity() { private lateinit var photoPreviewLayout: LinearLayout private var imageCapture: ImageCapture? = null private lateinit var cameraExecutor: ExecutorService + private var cameraProvider: ProcessCameraProvider? = null // 存储拍摄的图片URI private val capturedImageUris = mutableListOf() @@ -65,6 +66,9 @@ class CameraActivity : AppCompatActivity() { settingsButton = findViewById(R.id.settingsButton) photoPreviewLayout = findViewById(R.id.photoPreviewLayout) + // 初始化线程池 + cameraExecutor = Executors.newSingleThreadExecutor() + // 请求权限 if (allPermissionsGranted()) { startCamera() @@ -77,9 +81,12 @@ class CameraActivity : AppCompatActivity() { // 设置点击监听器 captureButton.setOnClickListener { takePhoto() } settingsButton.setOnClickListener { openSettings() } + } - // 初始化线程池 - cameraExecutor = Executors.newSingleThreadExecutor() + override fun onDestroy() { + super.onDestroy() + cameraExecutor.shutdown() + cameraProvider?.unbindAll() } private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { @@ -90,35 +97,39 @@ class CameraActivity : AppCompatActivity() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener({ - val cameraProvider = cameraProviderFuture.get() - - // 预览 - val preview = Preview.Builder() - .build() - .also { - it.setSurfaceProvider(viewFinder.surfaceProvider) - } - - imageCapture = ImageCapture.Builder() - .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) - .build() - - val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA - try { - cameraProvider.unbindAll() - cameraProvider.bindToLifecycle( + cameraProvider = cameraProviderFuture.get() + + // 预览 + val preview = Preview.Builder() + .build() + .also { + it.setSurfaceProvider(viewFinder.surfaceProvider) + } + + imageCapture = ImageCapture.Builder() + .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) + .build() + + val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + + cameraProvider?.unbindAll() + cameraProvider?.bindToLifecycle( this, cameraSelector, preview, imageCapture ) } catch (e: Exception) { - Log.e(TAG, "相机绑定失败", e) + Log.e(TAG, "相机初始化失败: ${e.message}", e) + Toast.makeText(this, "相机启动失败: ${e.message}", Toast.LENGTH_LONG).show() } }, ContextCompat.getMainExecutor(this)) } @SuppressLint("MissingPermission") private fun takePhoto() { - val imageCapture = this.imageCapture ?: return + val imageCapture = this.imageCapture ?: run { + Toast.makeText(this, "相机未准备好", Toast.LENGTH_SHORT).show() + return + } val outputOptions = createOutputFileOptions() @@ -143,7 +154,7 @@ class CameraActivity : AppCompatActivity() { override fun onError(exception: ImageCaptureException) { Log.e(TAG, "拍照失败: ${exception.message}", exception) - Toast.makeText(baseContext, "拍照失败", Toast.LENGTH_SHORT).show() + Toast.makeText(baseContext, "拍照失败: ${exception.message}", Toast.LENGTH_LONG).show() } } ) @@ -308,8 +319,9 @@ class CameraActivity : AppCompatActivity() { if (allPermissionsGranted()) { startCamera() } else { - Toast.makeText(this, "权限被拒绝,相机功能不可用", Toast.LENGTH_SHORT).show() - // 可以在这里提供降级功能,比如手动输入位置信息 + Toast.makeText(this, "相机权限被拒绝,应用无法使用相机功能", Toast.LENGTH_LONG).show() + // 可以选择关闭应用或提供替代功能 + finish() } } } diff --git a/app/src/main/java/com/example/app/MainActivity.kt b/app/src/main/java/com/example/app/MainActivity.kt index c81cc19..943a394 100644 --- a/app/src/main/java/com/example/app/MainActivity.kt +++ b/app/src/main/java/com/example/app/MainActivity.kt @@ -9,8 +9,8 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - // 直接启动相机Activity - startActivity(Intent(this, CameraActivity::class.java)) + // 启动极简相机Activity + startActivity(Intent(this, SimpleCameraActivity::class.java)) finish() // 关闭MainActivity,避免返回键回到空白页面 } } \ No newline at end of file diff --git a/app/src/main/java/com/example/app/SimpleCameraActivity.kt b/app/src/main/java/com/example/app/SimpleCameraActivity.kt new file mode 100644 index 0000000..d151723 --- /dev/null +++ b/app/src/main/java/com/example/app/SimpleCameraActivity.kt @@ -0,0 +1,170 @@ +package com.example.app + +import android.Manifest +import android.content.ContentValues +import android.content.pm.PackageManager +import android.os.Build +import android.os.Bundle +import android.provider.MediaStore +import android.util.Log +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageCapture +import androidx.camera.core.ImageCaptureException +import androidx.camera.core.Preview +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.camera.view.PreviewView +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.google.android.material.floatingactionbutton.FloatingActionButton +import java.text.SimpleDateFormat +import java.util.* +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class SimpleCameraActivity : AppCompatActivity() { + + companion object { + private const val TAG = "SimpleCamera" + private const val REQUEST_CODE_PERMISSIONS = 10 + private val REQUIRED_PERMISSIONS = arrayOf( + Manifest.permission.CAMERA + ) + } + + private lateinit var viewFinder: PreviewView + private lateinit var captureButton: FloatingActionButton + private var imageCapture: ImageCapture? = null + private lateinit var cameraExecutor: ExecutorService + private var cameraProvider: ProcessCameraProvider? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_simple_camera) + + // 初始化视图 + viewFinder = findViewById(R.id.viewFinder) + captureButton = findViewById(R.id.captureButton) + + // 初始化线程池 + cameraExecutor = Executors.newSingleThreadExecutor() + + // 请求权限 + if (allPermissionsGranted()) { + startCamera() + } else { + ActivityCompat.requestPermissions( + this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS + ) + } + + // 设置拍照按钮点击监听器 + captureButton.setOnClickListener { takePhoto() } + } + + override fun onDestroy() { + super.onDestroy() + cameraExecutor.shutdown() + cameraProvider?.unbindAll() + } + + private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { + ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED + } + + private fun startCamera() { + val cameraProviderFuture = ProcessCameraProvider.getInstance(this) + + cameraProviderFuture.addListener({ + try { + cameraProvider = cameraProviderFuture.get() + + // 预览 + val preview = Preview.Builder() + .build() + .also { + it.setSurfaceProvider(viewFinder.surfaceProvider) + } + + imageCapture = ImageCapture.Builder() + .setCaptureMode(ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY) + .build() + + val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + + cameraProvider?.unbindAll() + cameraProvider?.bindToLifecycle( + this, cameraSelector, preview, imageCapture + ) + + Log.d(TAG, "相机启动成功") + } catch (e: Exception) { + Log.e(TAG, "相机初始化失败: ${e.message}", e) + Toast.makeText(this, "相机启动失败: ${e.message}", Toast.LENGTH_LONG).show() + finish() + } + }, ContextCompat.getMainExecutor(this)) + } + + private fun takePhoto() { + val imageCapture = this.imageCapture ?: run { + Toast.makeText(this, "相机未准备好", Toast.LENGTH_SHORT).show() + return + } + + val timestamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) + + val contentValues = ContentValues().apply { + put(MediaStore.MediaColumns.DISPLAY_NAME, "巡检_$timestamp") + put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + put(MediaStore.MediaColumns.RELATIVE_PATH, "Pictures/LogCam") + } + } + + val contentResolver = contentResolver + val uri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) + + if (uri == null) { + Toast.makeText(this, "创建文件失败", Toast.LENGTH_SHORT).show() + return + } + + val outputOptions = ImageCapture.OutputFileOptions.Builder(contentResolver, uri, contentValues).build() + + imageCapture.takePicture( + outputOptions, + ContextCompat.getMainExecutor(this), + object : ImageCapture.OnImageSavedCallback { + override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { + val savedUri = outputFileResults.savedUri ?: return + val msg = "照片已保存: ${savedUri.lastPathSegment}" + Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() + Log.d(TAG, msg) + } + + override fun onError(exception: ImageCaptureException) { + Log.e(TAG, "拍照失败: ${exception.message}", exception) + Toast.makeText(baseContext, "拍照失败: ${exception.message}", Toast.LENGTH_LONG).show() + } + } + ) + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == REQUEST_CODE_PERMISSIONS) { + if (allPermissionsGranted()) { + startCamera() + } else { + Toast.makeText(this, "相机权限被拒绝,应用无法使用相机功能", Toast.LENGTH_LONG).show() + finish() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_simple_camera.xml b/app/src/main/res/layout/activity_simple_camera.xml new file mode 100644 index 0000000..0c04083 --- /dev/null +++ b/app/src/main/res/layout/activity_simple_camera.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f8c6127..e88c0b3 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -7,4 +7,5 @@ #FF018786 #FF000000 #FFFFFFFF + #FF6200EE \ No newline at end of file diff --git a/create-signing.sh b/create-signing.sh new file mode 100644 index 0000000..0bf51fc --- /dev/null +++ b/create-signing.sh @@ -0,0 +1,83 @@ +#!/bin/bash +# 创建Android应用签名文件并生成Base64字符串 + +echo "=== Android应用签名生成工具 ===" +echo "" + +# 检查keytool是否可用 +if ! command -v keytool &> /dev/null; then + echo "❌ keytool命令未找到,请安装Java JDK" + exit 1 +fi + +# 签名配置 +KEYSTORE_NAME="logcam-release.jks" +ALIAS="logcam-release-key" +STORE_PASS="android123" +KEY_PASS="android123" +DNAME="CN=LogCam, OU=Development, O=Example, L=Beijing, ST=Beijing, C=CN" + +echo "📋 签名配置:" +echo " - 密钥库文件: $KEYSTORE_NAME" +echo " - 别名: $ALIAS" +echo " - 密钥库密码: $STORE_PASS" +echo " - 密钥密码: $KEY_PASS" +echo "" + +# 生成签名文件 +echo "🔑 生成签名文件..." +keytool -genkeypair -v \ + -keystore "$KEYSTORE_NAME" \ + -keyalg RSA -keysize 2048 \ + -validity 10000 \ + -alias "$ALIAS" \ + -storepass "$STORE_PASS" \ + -keypass "$KEY_PASS" \ + -dname "$DNAME" + +if [ $? -eq 0 ]; then + echo "✅ 签名文件生成成功: $KEYSTORE_NAME" + ls -la "$KEYSTORE_NAME" +else + echo "❌ 签名文件生成失败" + exit 1 +fi + +echo "" +echo "🔄 生成Base64字符串..." +# 生成Base64字符串 +SIGNING_KEY_BASE64=$(base64 -w 0 "$KEYSTORE_NAME") +if [ $? -eq 0 ]; then + echo "✅ Base64字符串生成成功" +else + echo "❌ Base64转换失败" + exit 1 +fi + +echo "" +echo "📋 GitHub Secrets配置信息:" +echo "========================================" +echo "SIGNING_KEY (密钥文件Base64):" +echo "$SIGNING_KEY_BASE64" +echo "========================================" +echo "" +echo "ALIAS (别名): $ALIAS" +echo "KEYSTORE_PASSWORD (密钥库密码): $STORE_PASS" +echo "KEY_PASSWORD (密钥密码): $KEY_PASS" +echo "========================================" +echo "" +echo "📱 后续步骤:" +echo "1. 访问 https://github.com/xiajid/logcam/settings/secrets/actions" +echo "2. 创建以下Secrets:" +echo " - SIGNING_KEY: 粘贴上面的Base64字符串" +echo " - ALIAS: \"$ALIAS\"" +echo " - KEYSTORE_PASSWORD: \"$STORE_PASS\"" +echo " - KEY_PASSWORD: \"$KEY_PASS\"" +echo "3. 提交新工作流文件" +echo "4. 触发构建获得签名版APK" +echo "" +echo "🔧 签名文件信息:" +keytool -list -v -keystore "$KEYSTORE_NAME" -storepass "$STORE_PASS" | grep -E "别名|有效期|指纹" + +echo "" +echo "✅ 签名流程完成" \ No newline at end of file