Add SimpleCameraActivity for crash debugging - minimal camera with only preview and capture

This commit is contained in:
xiajiid
2026-02-08 22:08:39 +08:00
parent 5eceac79ed
commit b261fdb4b3
7 changed files with 342 additions and 26 deletions

View File

@@ -26,6 +26,10 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".SimpleCameraActivity"
android:exported="false"
android:screenOrientation="portrait" />
</application>
</manifest>

View File

@@ -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<Uri>()
@@ -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()
}
}
}

View File

@@ -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避免返回键回到空白页面
}
}

View File

@@ -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<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CODE_PERMISSIONS) {
if (allPermissionsGranted()) {
startCamera()
} else {
Toast.makeText(this, "相机权限被拒绝,应用无法使用相机功能", Toast.LENGTH_LONG).show()
finish()
}
}
}
}

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".SimpleCameraActivity">
<!-- 相机预览视图 -->
<androidx.camera.view.PreviewView
android:id="@+id/viewFinder"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<!-- 拍照按钮 -->
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/captureButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:src="@android:drawable/ic_menu_camera"
app:backgroundTint="@color/primary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- 状态提示 -->
<TextView
android:id="@+id/statusText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="极简相机 - 点击下方按钮拍照"
android:textColor="@android:color/white"
android:textSize="14sp"
android:background="#80000000"
android:padding="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -7,4 +7,5 @@
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="primary">#FF6200EE</color>
</resources>