安卓第一个项目

测试所有摄像头

安卓CameraX:https://developer.android.com/media/grow/spatial-audio?hl=zh-cn

1、MainActivity.java

java 复制代码
// 定义包名
package com.mms.densenapplication;

// 引入 AppCompatActivity,支持兼容性更强的 Activity
import androidx.appcompat.app.AppCompatActivity;

// 引入上下文和生命周期相关的类
import android.content.Context;
import android.os.Bundle;

// 引入 CameraX 的预览控件
import androidx.camera.view.PreviewView;

// Java 的集合类
import java.util.ArrayList;
import java.util.Map;
import java.util.List;
import java.util.HashMap;

// 下拉选择框相关类
import android.widget.Spinner;
import android.widget.ArrayAdapter;
import android.widget.AdapterView;

// View 相关类
import android.view.View;

// Camera2 API 相关类
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;

// CameraX 核心组件
import androidx.camera.core.CameraSelector;
import androidx.camera.core.CameraInfo;
import androidx.camera.core.Preview;
import androidx.camera.core.ImageAnalysis;
import androidx.camera.lifecycle.ProcessCameraProvider;

// 异步执行与线程池
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;

// 工具类
import android.util.Size;
import android.util.Log;

import androidx.core.content.ContextCompat;


// CameraX 与 Camera2 的互操作类
import androidx.camera.camera2.interop.Camera2CameraInfo;

public class MainActivity extends AppCompatActivity {
    // 摄像头预览组件
    private PreviewView previewView;
    // 摄像头选择下拉框
    private Spinner cameraSpinner;
    // 用于显示在下拉框中的摄像头列表(带说明文字)
    private List<String> cameraIdList = new ArrayList<>();
    // 用于保存显示名和真实 cameraId 的映射关系
    private Map<String, String> cameraIdMap = new HashMap<>();
    // Camera2 管理器
    private CameraManager cameraManager;
    // CameraX 的生命周期相机提供器
    private ProcessCameraProvider cameraProvider;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 加载布局文件 activity_main.xml
        setContentView(R.layout.activity_main);

        // 获取布局中的预览视图和摄像头选择下拉框控件
        previewView = findViewById(R.id.previewView);
        cameraSpinner = findViewById(R.id.cameraSpinner);

        // 获取系统摄像头服务
        cameraManager = (CameraManager) getSystemService(Context.CAMERA_SERVICE);

        try {
            // 遍历系统中所有摄像头
            for (String id : cameraManager.getCameraIdList()) {
                // 获取该摄像头的属性
                CameraCharacteristics cc = cameraManager.getCameraCharacteristics(id);
                // 获取摄像头朝向(前置/后置)
                Integer facing = cc.get(CameraCharacteristics.LENS_FACING);

                // 生成显示用的摄像头名称
                String name = "Camera ID: " + id;
                if (facing != null) {
                    if (facing == CameraCharacteristics.LENS_FACING_BACK) name += " (BACK)";
                    else if (facing == CameraCharacteristics.LENS_FACING_FRONT) name += " (FRONT)";
                    else name += " (EXTERNAL)";
                }

                // 添加到显示列表和 ID 映射表中
                cameraIdList.add(name);
                cameraIdMap.put(name, id);
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }

        // 设置下拉框的适配器
        ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, cameraIdList);
        cameraSpinner.setAdapter(adapter);

        // 当用户选择某个摄像头时触发
        cameraSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
            @Override
            public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
                // 获取选中的显示项
                String selected = cameraIdList.get(position);
                // 查找对应的真实摄像头 ID
                String realCameraId = cameraIdMap.get(selected);
                // 调用绑定摄像头的方法
                if (realCameraId != null) {
                    bindCamera(realCameraId);
                }
            }

            @Override public void onNothingSelected(AdapterView<?> parent) {}
        });

        // 异步获取 ProcessCameraProvider 实例(生命周期感知)
        ProcessCameraProvider.getInstance(this).addListener(() -> {
            try {
                cameraProvider = ProcessCameraProvider.getInstance(this).get();
            } catch (ExecutionException | InterruptedException e) {
                e.printStackTrace();
            }
        }, ContextCompat.getMainExecutor(this));
    }

    /**
     * 绑定指定 ID 的摄像头,并显示预览及处理图像帧
     */
    private void bindCamera(String cameraId) {
        if (cameraProvider == null) return;

        // 解绑之前所有的摄像头使用
        cameraProvider.unbindAll();

        // 构建 CameraSelector,通过 cameraId 过滤匹配的摄像头
        CameraSelector cameraSelector = new CameraSelector.Builder()
                .addCameraFilter(cameras -> {
                    List<CameraInfo> matched = new ArrayList<>();
                    for (CameraInfo info : cameras) {
                        // 将 CameraInfo 转换为 Camera2CameraInfo 以获取其 cameraId
                        Camera2CameraInfo camera2CameraInfo = Camera2CameraInfo.from(info);
                        if (camera2CameraInfo.getCameraId().equals(cameraId)) {
                            matched.add(info);
                        }
                    }
                    return matched;
                }).build();

        // 创建预览用的 Preview 对象,并设置其 SurfaceProvider
        Preview preview = new Preview.Builder().build();
        preview.setSurfaceProvider(previewView.getSurfaceProvider());

        // 创建图像分析器,并设置分辨率与策略
        ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
                .setTargetResolution(new Size(224, 224)) // 设置目标分辨率
                .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) // 丢弃旧帧
                .build();

        // 设置分析器线程和回调(这里只打印帧大小)
        imageAnalysis.setAnalyzer(Executors.newSingleThreadExecutor(), image -> {
            Log.d("CameraDebug", "Frame: " + image.getWidth() + "x" + image.getHeight());
            image.close(); // 一定要关闭,否则会卡住
        });

        // 将预览和分析器绑定到生命周期
        cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageAnalysis);
    }
}

2、layout/activity_main.xml

html 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Spinner
        android:id="@+id/cameraSpinner"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <androidx.camera.view.PreviewView
        android:id="@+id/previewView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"/>
</LinearLayout>

3、build.gradle.kts(app)

groovy 复制代码
import com.android.build.gradle.internal.packaging.createDefaultDebugStore

plugins {
    alias(libs.plugins.androidApplication)
}

android {
    namespace = "com.mms.densenapplication"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.mms.densenapplication"
        minSdk = 30
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        ndk {
//            abiFilters += listOf("armeabi-v7a", "arm64-v8a")
            abiFilters += listOf("arm64-v8a")
        }


        externalNativeBuild {
            cmake {
                cppFlags("")
                arguments("-DANDROID_STL=c++_shared")
            }
        }
    }
    signingConfigs{
        getByName("debug") {
            storeFile = file("${project.rootDir}/platform_2.0.jks")
            storePassword = "android"
            keyAlias = "androiddebugkey"
            keyPassword = "android"
        }
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    externalNativeBuild {
        cmake {
            path = file("src/main/cpp/CMakeLists.txt")
            version = "3.22.1"
        }
    }
    buildFeatures {
        viewBinding = true
    }
    aaptOptions {
        noCompress("dat", "zip", "bin", "")
    }
}

dependencies {

    implementation(libs.appcompat)
    implementation("com.fasterxml.jackson.core:jackson-databind:2.15.0")
    implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.15.0")
    implementation(libs.material)
    implementation(libs.constraintlayout)
    testImplementation(libs.junit)
    androidTestImplementation(libs.ext.junit)
    androidTestImplementation(libs.espresso.core)
    implementation("androidx.camera:camera-camera2:1.3.0")  // 下面三个是摄像头相关
    implementation("androidx.camera:camera-lifecycle:1.3.0")
    implementation("androidx.camera:camera-view:1.3.0")
}

4、AndroidManifest.xml

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:sharedUserId="android.uid.system">
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA" />  <!-- 摄像头权限 -->


    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.DensenApplication"
        tools:targetApi="31">
        <uses-native-library
            android:name="libcdsprpc.so"
            android:required="true" />
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
相关推荐
木白CPP1 小时前
SELinux 介绍和基本使用
嵌入式硬件·安卓·安全架构
火柴就是我1 小时前
Flutter 项目本地 Maven 引入 AAR 找不到?一次 Gradle 路径问题排查
android
火柴就是我2 小时前
Android 打包报错 25.0.3
android·前端
码上有光2 小时前
Linux进程通信——共享内存、消息队列和信号量
android·linux·运维·共享内存·通信
Android打工仔6 小时前
Kotlin 协程源码解析:DispatchedContinuation 里的 Dispatcher 从哪里来?
android·kotlin
打工仔折腾 AI6 小时前
从BPE到SentencePiece:Transformer分词原理与Python实战对比
android·人工智能·python·深度学习·langchain·transformer·ai agent 实战
Dovis(誓平步青云)7 小时前
几个方案来回选不定?做一个随时切换的候选推荐页
android·java·服务器·开发语言·javascript·数据库·智能化
恋猫de小郭10 小时前
KMP 又改了编译流程, Separate Compilation 禁止了 `commonMain` 的依赖穿透
android·前端·flutter
执明wa11 小时前
Android RecyclerView 实战:点击频道栏切换对应内容
android·开发语言·windows·microsoft·android studio
新鲜势力呀11 小时前
PHP 定时数据同步实战:从第三方接口超时到异步同步 + 增量更新架构优化全过程
android