首页 > 其他分享 >Android开发笔记[18]-使用本地模块

Android开发笔记[18]-使用本地模块

时间:2024-04-23 21:36:10浏览次数:33  
标签:compose val androidx implementation 18 ui 模块 import Android

摘要

将摄像头预览页面封装到Android模块中并在app中使用这个本地模块.

关键信息

  • Android Studio:Iguana | 2023.2.1
  • Gradle:distributionUrl=https://services.gradle.org/distributions/gradle-8.4-bin.zip
  • jvmTarget = '1.8'
  • minSdk 26
  • targetSdk 34
  • compileSdk 34
  • 开发语言:Kotlin,Java
  • ndkVersion = '21.1.6352462'
  • kotlin版本:1.9.20
  • kotlinCompilerExtensionVersion '1.5.4'
  • com.android.library:8.3

原理简介

CameraX简介

[https://juejin.cn/post/6895278749630070798]
CameraX 是一个 Jetpack 支持库,旨在帮助您简化相机应用的开发工作。
它提供一致且易于使用的 API Surface,适用于大多数 Android 设备,并可向后兼容至 Android 5.0
基于Camera2,即对Camera2的封装.

Android模块开发简介

[https://www.jianshu.com/p/0ea37b2c7ce7]
模块化开发思路就是:单独开发每个模块,用集成的方式把他们组合起来,就能拼出一个app。app可以理解成很多功能模块的组合,而且有些功能模块是通用的,必备的,像自动更新,反馈,推送,都可以提炼成模块,和搭积木很像,由一个壳包含很多个模块。

Android使用OpenCV库的简单方式

[https://github.com/QuickBirdEng/opencv-android]
Easy way to integrate OpenCV into your Android project via Gradle.
No NDK dependency needed - just include this library and you are good to go.
OpenCV Contribution's package naming has been changed to make it as per the naming guideline.
Old: opencv:VERSION-contrib
New: opencv-contrib:VERSION

Each versions is available in only OpenCV as well as OpenCV with contributions.

‼️ Please use 4.5.3.0 instead of 4.5.3. They are both the same versions, however, 4.5.3 has some runtime issues on some of the Android versions while 4.5.3.0 works fine.

实现

核心代码

  1. Android Studio 文件->New->New Module新建安卓模块grape_realtime_detect
  2. 编辑模块的build.gradle.kts文件
repositories {
  mavenCentral()
}
implementation `com.quickbirdstudios:opencv:4.5.3.0`
  1. app添加模块:
    settings.gradle
include ':app:grape_realtime_detect'

build.gradle

implementation ('com.github.zynkware:Document-Scanning-Android-SDK:1.1.1') /* 文档扫描库*/ {
    // 去除OpenCV冲突依赖
    exclude group: "com.github.zynkware", module: "Tiny-OpenCV"
}
/* start 实时识别相关 */
implementation project(':app:grape_realtime_detect')
/* end 实时识别相关 */
  1. 编辑模块的AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-feature android:name="android.hardware.camera2" android:required="false"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    <application>
        <!-- 目标检测页模板 -->
        <activity
            android:name=".RealtimeDetectActivityTemplate"
            android:exported="true"
            android:label="@string/title_activity_realtime_detect"
            android:theme="@style/Theme.Grapeyolov5detectandroid" />
    </application>

</manifest>
  1. 模块代码
    RealtimeDetectActivityTemplate.kt
package cn.qsbye.grape_realtime_detect

import android.app.Activity
import android.content.Context
import android.content.res.AssetManager
import android.graphics.BitmapFactory
import android.os.Bundle
import android.util.Log
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.OptIn
import androidx.camera.core.CameraSelector
import androidx.camera.core.ExperimentalGetImage
import androidx.camera.core.ImageCapture
import androidx.camera.core.ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
import androidx.camera.core.ImageCaptureException
import androidx.camera.core.ImageProxy
import androidx.camera.core.UseCase
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.video.Recorder
import androidx.camera.view.PreviewView
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.MutatePriority
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Button
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.text.drawText
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.camera.core.Preview as PreviewCameraX
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import cn.qsbye.grape_realtime_detect.ui.theme.Grapeyolov5detectandroidTheme
import com.hjq.permissions.OnPermissionCallback
import com.hjq.permissions.Permission
import com.hjq.permissions.XXPermissions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.opencv.videoio.VideoCapture
import java.util.concurrent.Executor
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

import org.opencv.core.Core
import org.opencv.core.Mat
import org.opencv.core.Scalar
import org.opencv.imgcodecs.Imgcodecs
import java.io.File

// 全局引用Activity
val LocalActivity = compositionLocalOf<Activity?> { null }

/* start 扩展Context */
// 添加相机画面
suspend fun Context.getCameraProvider(): ProcessCameraProvider = suspendCoroutine { continuation ->
    ProcessCameraProvider.getInstance(this).also { future ->
        future.addListener({
            continuation.resume(future.get())
        }, executor)
    }
}

val Context.executor: Executor
    get() = ContextCompat.getMainExecutor(this)
/* end 扩展Context */

abstract class RealtimeDetectActivityTemplate : ComponentActivity() {
    // 显示Toast消息
    private fun Context.toast(message: CharSequence) =
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        /* start 检查相机权限 */
        XXPermissions.with(this)
            // 申请单个权限
            .permission(Permission.CAMERA)
            .request(object : OnPermissionCallback {

                override fun onGranted(permissions: MutableList<String>, allGranted: Boolean) {
                    if (!allGranted) {
                        // toast("相机权限正常")
                        return
                    }
                    toast("获取相机权限成功")
                }

                override fun onDenied(permissions: MutableList<String>, doNotAskAgain: Boolean) {
                    if (doNotAskAgain) {
                        toast("被永久拒绝授权,请手动授予相机权限")
                        // 如果是被永久拒绝就跳转到应用权限系统设置页面
                        XXPermissions.startPermissionActivity(this@RealtimeDetectActivityTemplate, permissions)
                    } else {
                        toast("获取相机权限失败")
                        // 结束当前活动
                        finish()
                    }
                }
            })
        /* end 检查相机权限 */

        // 初始化assets文件夹实例
        val assetManager: AssetManager = assets

        setContent {
            Grapeyolov5detectandroidTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                )   {
                    provideActivity(activity = this) {
                        realtimeDetectPage()
                    }
                }
            }
        }// end setContent
    }
}

// 实时检测页面布局
@Preview(showBackground = true)
@Composable
fun realtimeDetectPage(_modifier: Modifier = Modifier){

    Column (
        _modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Bottom,
        // horizontalAlignment = Alignment.CenterHorizontally
    ){
        cameraArea(_modifier = Modifier.weight(0.8f))
        navigationBarArea(
            _modifier = Modifier
            .weight(1f)
        )
    }
}

// 实时显示区域
@Composable
fun cameraArea(_modifier: Modifier = Modifier, onUseCase: (UseCase) -> Unit = { }, onImageFileSaveSucceed:(File)-> Unit={
}){
    // 获取上下文
    val context = LocalContext.current
    // 协程视图
    val coroutineScope = rememberCoroutineScope()
    // 绑定当前生命周期
    val lifecycleOwner = LocalLifecycleOwner.current
    // 相机缩放方式
    val scaleType: PreviewView.ScaleType = PreviewView.ScaleType.FILL_CENTER
    // 选定使用后置摄像头
    val cameraSelector: CameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
    // 图片预览实例
    var previewUseCase by remember { mutableStateOf<UseCase>(PreviewCameraX.Builder().build()) }
    // 图片捕捉实例
    val imageCaptureUseCase by remember {
        mutableStateOf(
            ImageCapture.Builder()
                .setCaptureMode(CAPTURE_MODE_MAXIMIZE_QUALITY)
                .build()
        )
    }


    Box(
        modifier = _modifier
    ){
        // 图像Bitmap状态
        var imageBitmap by remember { mutableStateOf<ImageBitmap?>(null) }
        // 使用remember来记忆boxes数组和isRandom变量的变化
        val s_boxes = remember { mutableStateOf(arrayOf(arrayOf<Float>())) }
        val s_isRandom = remember { mutableStateOf<Boolean>(false) }
        // 获取上下文
        val context2 = LocalContext.current

        /* start 摄像头画面Preview */
        AndroidView(
            modifier = _modifier,
            factory = { context ->
                val previewView = PreviewView(context).apply {
                    this.scaleType = scaleType
                    layoutParams = ViewGroup.LayoutParams(
                        ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT
                    )
                }

                // CameraX Preview UseCase
                // 这里注意导入androidx.camera.core.Preview as PreviewCameraX而不是androidx.compose.ui.tooling.preview.Preview
                previewUseCase = PreviewCameraX.Builder()
                    .build()
                    .also {it->
                        it.setSurfaceProvider(previewView.surfaceProvider)
                    }

                // 协程启动
                coroutineScope.launch {
                    val cameraProvider = context.getCameraProvider()
                    try {
                        // 在下一次绑定之前需要解绑上一个
                        cameraProvider.unbindAll()
                        cameraProvider.bindToLifecycle(
                            lifecycleOwner, cameraSelector, previewUseCase, imageCaptureUseCase
                        )
                    } catch (ex: Exception) {
                        Log.e("CameraPreview", "Use case binding failed", ex)
                    }
                }

                // 返回previewView画面
                previewView
            }
        )
        /* end 摄像头画面Preview */

        /* start 预览图片 */
        // 绘制图像
        imageBitmap?.let { bitmap ->
            Canvas(modifier = _modifier
                .height(120.dp)
                .width(90.dp)) {
                drawImage(
                    image = bitmap,
                    topLeft = Offset(x = 0.dp.toPx(), y = 0.dp.toPx()),
                    alpha = 0.5f, // 设置透明度为50%
                )
            }
        }
        /* end 预览图片 */

        /* start 绘制矩形框 */
        // 使用remember监听boxes变量和isRandom变量变化并更新显示
        s_boxes.value = arrayOf(
                arrayOf(100f, 200f, 400f, 500f),
                arrayOf(50f, 150f, 300f, 450f),
                arrayOf(20f, 300f, 100f, 500f)
        )

        drawRectangleBox(_modifier = _modifier, isRandom = s_isRandom.value, boxes = s_boxes.value)
        /* end 绘制矩形框 */

        /* start 拍照协程 */
        LaunchedEffect(Unit) {
            coroutineScope.launch(Dispatchers.IO) {
                while (true) {
                    try {
                        /* start 调用takePicture */
                        imageCaptureUseCase.takePicture(
                            context.executor, // 执行器
                            object : ImageCapture.OnImageCapturedCallback() {
                                fun calculateInSampleSize(options: BitmapFactory.Options, reqWidth: Int, reqHeight: Int): Int {
                                    // 计算实际尺寸
                                    val (actualWidth, actualHeight) = options.run { outWidth to outHeight }
                                    // 计算缩放因子
                                    var inSampleSize = 1
                                    if (actualWidth > reqWidth || actualHeight > reqHeight) {
                                        val halfWidth = actualWidth / 2
                                        val halfHeight = actualHeight / 2
                                        inSampleSize = if (halfWidth < reqWidth || halfHeight < reqHeight) {
                                            halfWidth.coerceAtLeast(halfHeight).toInt()
                                        } else {
                                            halfWidth.coerceAtMost(halfHeight).toInt()
                                        }
                                    }
                                    // 计算最终的缩放因子
                                    inSampleSize = Math.round(Math.pow(2.0, ((Integer.SIZE - Integer.numberOfLeadingZeros(
                                        (inSampleSize - 1)
                                    )).toDouble()))).toInt()
                                    return inSampleSize
                                }
                                @OptIn(ExperimentalGetImage::class)
                                override fun onCaptureSuccess(imageProxy: ImageProxy) {
                                    // 图片捕获成功,可以在这里处理ImageProxy
                                    Log.d("CameraX", "图片捕获成功")
                                    // 将ImageProxy转换为Bitmap
                                    val image = imageProxy.image
                                    val buffer = image!!.planes[0].buffer
                                    val bytes = ByteArray(buffer.remaining())
                                    buffer.get(bytes)
                                    val options = BitmapFactory.Options()
                                    options.inJustDecodeBounds = true
                                    BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)
                                    options.inSampleSize = calculateInSampleSize(options, 256, 256)
                                    options.inJustDecodeBounds = false
                                    val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)
                                    //val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
                                    // 将Bitmap转换为ImageBitmap
                                    val _imageBitmap = bitmap?.asImageBitmap()
                                    // 更新图像状态
                                    imageBitmap = _imageBitmap
                                    // 调用TFLite识别葡萄
                                    LocalActivity.run{
                                        s_boxes.value=GrapeDetect.detectGrapes(imageBitmap!!,context2)
                                    }
                                    Log.d("GrapeRealtimeDetect", "识别葡萄完成!")
                                    // 释放资源
                                    imageProxy.close()
                                } // end imageProxy
                                override fun one rror(exception: ImageCaptureException) {
                                    // 处理捕获过程中的错误
                                    Log.d("CameraX", "图片捕获失败: ${exception.message}")
                                }
                            }
                        )
                        /* end 调用takePicture */

                    } catch (e: Exception) {
                        // 处理异常情况
                        Log.d("CameraX","图片捕获异常")
                    }

                    // 等待500毫秒
                    delay(500)
                }
            }
        }
        /* end 拍照协程 */

    } // end Box
}

// 返回按钮区域
@Composable
fun navigationBarArea(_modifier: Modifier = Modifier){
    // 获取当前上下文
    val context = LocalContext.current
    // 获取当前活动
    val activity = LocalActivity.current

    Column(
        modifier = Modifier
            .background(Color(0xFFDBDF74))
            .fillMaxWidth(),
        horizontalAlignment= Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ){
        Button(
            modifier = Modifier
                .fillMaxWidth(0.8f),
            onClick = {
                // 关闭当前Activity
                activity?.finish()
        }){
            Text(
                text = "返回"
            )
        }
    }
}

// 提供当前活动
@Composable
fun provideActivity(activity: Activity, content: @Composable () -> Unit) {
    CompositionLocalProvider(LocalActivity provides activity) {
        content()
    }
}

// 绘制矩形框
@Composable
fun drawRectangleBox(_modifier : Modifier,isRandom: Boolean = false, boxes: Array<Array<Float>>) {
    Canvas(modifier = _modifier.fillMaxSize()) {
        // 遍历每个矩形框
        for ((startX, startY, endX, endY) in boxes) {
            // 判断isRandom,选择颜色
            var color = if (isRandom) Color.Red else Color.Green
            // 绘制空心矩形框
            drawRect(
                color = color,
                // start = Offset(startX, startY),
                size = Size(endX - startX, endY - startY),
                style = Stroke(width = 5f) // 设置边框宽度
            )

            // 计算矩形框的中心点
            val centerX = startX + (endX - startX) / 2
            val centerY = startY + (endY - startY) / 2
            class TextMeasurer {
                fun measureText(text: String, fontSize: Float, font: Font): Size {
                    // 这里是测量文本尺寸的代码
                    // 可能需要根据实际的图形库或API来编写
                    // 返回文本的宽度和高度
                    return Size(50f, 30f)
                }
            }
            // 使用 TextMeasurer 来测量文本尺寸
            val textMeasurer = TextMeasurer()

            val nativePaint = android.graphics.Paint().let {
                it.apply {
                    textSize = 36f
                    color = Color(0,255,0)
                }
            }
            // 绘制文字
            drawContext.canvas.nativeCanvas.drawText(
                "葡萄",
                centerX,
                centerY,
                nativePaint
            )

        }
    }
}

GrapeRealtimeDetect.kt

package cn.qsbye.grape_realtime_detect

import android.content.Context
import android.content.res.AssetManager
import android.graphics.Bitmap
import android.graphics.RectF
import android.util.Log
import androidx.compose.ui.graphics.ImageBitmap
import org.opencv.android.OpenCVLoader
import kotlin.random.Random

/*
GrapeRealtimeDetect模块入口
 */
object GrapeRealtimeDetect {

        // 初始化模块
        fun init() {
                /* start 初始化OpenCV库 */
                Log.d("GrapeRealtimeDetect","开始初始化!")
                if (!OpenCVLoader.initDebug()) {
                        Log.d("GrapeRealtimeDetect", "无法加载OpenCV库!");
                } else {
                        Log.d("GrapeRealtimeDetect", "OpenCV加载成功!");
                }
                /* end 初始化OpenCV库 */
        }
}

/* start 葡萄检测 */
// 葡萄识别总函数
object GrapeDetect {
        fun detectGrapes(imageBitmap: ImageBitmap,context: Context): Array<Array<Float>> {
                // TODO 调用TFLite识别葡萄
                // 假设这里会有一些图像处理的代码来识别葡萄
                // ...
                // 随机生成矩形的宽度和高度(>=100f)
                val width = Random.nextFloat() * (imageBitmap.width - 100) + 100
                val height = Random.nextFloat() * (imageBitmap.height - 100) + 100
                // 随机生成矩形的坐标
                val x = Random.nextFloat() * (imageBitmap.width - width)
                val y = Random.nextFloat() * (imageBitmap.height - height)
                // 更新 s_boxes 状态
                return arrayOf(arrayOf(x, y, x + width, y + height))
        }
}

// 图像预处理函数
private fun preprocessImage(bitmap: Bitmap): Bitmap {
        // 这里应根据模型的要求对图像进行预处理
        // 例如,调整图像大小、归一化等
        return bitmap
}

// TensorFlow Lite模型加载和推理的辅助类
class TfLiteModelLoader {
        companion object {
//                fun loadModelFromFile(context: Context, modelName: String): TfLiteModel {
//                        // 实现模型加载逻辑
//
//                        return null
//                }
        }
}

class TfLiteModel {
//        fun recognizeImage(image: Bitmap): List<RecognitionResult> {
//                // 实现推理逻辑,并返回检测结果
//                return null
//        }
}

// 检测结果的数据类
data class RecognitionResult(
        val boundingBox: RectF,
        // 其他可能的字段,如类别、置信度等
)
/* end 葡萄检测 */

build.gradle.kts

import com.android.build.api.dsl.AaptOptions

plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")
}

android {
    namespace = "cn.qsbye.grape_realtime_detect"
    compileSdk = 34

    defaultConfig {
        minSdk = 24

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles("consumer-rules.pro")
        vectorDrawables {
            useSupportLibrary = true
        }
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.4"
    }
    packagingOptions {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }
    aaptOptions {
        // 不压缩模型文件
        noCompress("tflite")
        noCompress("lite")
    }
}

dependencies {

    /* start Android系统相关 */
    implementation("androidx.core:core-ktx:1.13.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.11.0")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
    implementation("androidx.activity:activity-compose:1.8.2")
    implementation(platform("androidx.compose:compose-bom:2024.04.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.ui:ui-graphics")
    implementation("androidx.compose.ui:ui-tooling-preview")
    implementation("androidx.compose.material3:material3")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
    /* end Android系统相关 */

    /* start 相机相关 */
    implementation("io.reactivex.rxjava3:rxandroid:3.0.0")
    implementation("androidx.camera:camera-camera2:1.3.3")
    implementation("androidx.camera:camera-lifecycle:1.3.3")
    implementation("androidx.camera:camera-view:1.3.3")
    implementation("androidx.camera:camera-core:1.3.3")
    implementation("com.google.android.exoplayer:exoplayer-core:2.19.1")
    /* end 相机相关 */

    /* start 图像处理相关 */
    //implementation("com.github.zynkware:Tiny-OpenCV:4.4.0-4")
    implementation("com.quickbirdstudios:opencv-contrib:4.5.3.0")
    /* end 图像处理相关 */

    /* start 数据同步框架相关 */
    implementation("com.tencent:mmkv:1.3.4")
    /* end 数据同步框架相关 */

    /* start 权限相关 */
    implementation("com.github.getActivity:XXPermissions:18.6")
    androidTestImplementation(platform("androidx.compose:compose-bom:2024.04.00"))
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")
    debugImplementation("androidx.compose.ui:ui-tooling")
    debugImplementation("androidx.compose.ui:ui-test-manifest") // 动态权限库
    /* end 权限相关 */

    /* start 测试相关 */
    testImplementation("androidx.test.ext:junit:1.1.5")
    testImplementation("androidx.test:rules:1.5.0")
    testImplementation("androidx.test:runner:1.5.2")
    testImplementation("androidx.test.espresso:espresso-core:3.5.1")
    testImplementation("org.robolectric:robolectric:4.4")
    /* end 测试相关 */

    /* start tflite相关 */
    implementation("androidx.window:window:1.2.0")
    implementation("org.tensorflow:tensorflow-lite-task-vision:0.4.0")
    implementation("org.tensorflow:tensorflow-lite-gpu-delegate-plugin:0.4.0")
    implementation("org.tensorflow:tensorflow-lite-gpu:2.9.0")
    /* end tflite相关 */
}
  1. 编辑app的初始化代码以初始化模块
    BootApp.kt
class BootApp:Application() {
    companion object {
        private const val FILE_SIZE = 1000000L
        private const val FILE_QUALITY = 100
        private val FILE_TYPE = Bitmap.CompressFormat.JPEG
    }

    override fun onCreate() {
        super.onCreate()
        /* start 初始化实时识别模块 */
        GrapeRealtimeDetect.init()
        Log.d("GrapeRealtimeDetect","初始化完成!")
        /* end 初始化实时识别模块 */
    }
}

效果

模块中调用CameraX相机

标签:compose,val,androidx,implementation,18,ui,模块,import,Android
From: https://www.cnblogs.com/qsbye/p/18153774

相关文章

  • 序列化模块,subprocess模块,re模块,常用正则
    Ⅰ序列化模块【1】json模块'''json模块是一个序列化模块,主要用于跨语言传输'''1.由下图可知json格式数据是不同编程语言之间数据交互的媒介2.json格式数据的具体特征 结论一中:数据基于网络传输肯定是二进制格式在python中bytes类型的数据可以直接看成是二进制格式......
  • 模块(time、datetime、os、random、日志logging、hashlib)
    【一】time模块【1】表示时间的三种方式时间戳元组(struct_time)格式化的时间字符串:格式化的时间字符串(FromatString):'1999-12-06'【2】时间转换(1)导入时间模块importtime(2)时间戳[1]生成时间戳importtime#生成时间戳,时间戳是浮点数类型time_str=time.time......
  • 【android】获取手机安装的所有程序
     1.获取包管理器对象PackageManagerpm=context.getPackageManager();2.得到所有安装的程序包名List<PackageInfo>infos=pm.getInstallPackages(PackageManager.GET_UNINSTALLED_PACKAGES);3.然后遍历这个集合for(PackageInfopackInfo:infos){Drawabl......
  • 使用 MediaCodec 在 Android 上进行硬解码
    要使用MediaCodec在Android上进行硬解码,并获取RGBA数据,你可以按照以下步骤进行操作:创建MediaExtractor对象并设置要解码的MP4文件路径:MediaExtractorextractor=newMediaExtractor();extractor.setDataSource(filePath);根据需要选择音频或视频轨道:inttrackCo......
  • Android Studio 蓝牙 示例代码(转)
    原文:https://blog.csdn.net/qq_40511184/article/details/122698077因为androidstudio升级,下面代码中的startactivityresult函数有变化,不能使用,需要更换为publicActivityResultLauncher<Intent>register;ActivityResultLauncher<Intent>startBlueTooth=registerForActi......
  • JTCR-java.util 更多实用类-18
    BitSetBitSet类是用于存放二进制位值的布尔数组,数组大小按需增加。构造器为BitSet();//指定初始化数组大小BitSet(intsize);publicclassBitSetDemo{//bit1的值//{0,2,4,6,8,10,12,14}//bit2的值//{1,2,3,4,6,7,8,9,11,12,......
  • springboot项目找不到符号问题以及模块聚合项目maven插件使用的相关问题 问题如图
    参考:https://www.cnblogs.com/coderxiaobai/p/15005181.html问题:更换maven,清空缓存重新导入依赖依然无效后(1)解决方法:方式一:删除项目中.idea文件夹,重新打开项目,选中jdk版本,重新导入依赖即可。(2)如果不是上述的原因可能是项目是模块聚合项目,原因就是父工程的pom中存在maven插......
  • 模块与包、json模块
    【一】模块与包介绍【1】什么是模块在Python中,一个py文件就是一个模块,文件名为xxx.py,模块名则是xxx,导入模块可以引入模块中已经写好的功能。如果把开发程序比喻成制造一台电脑编写模块就像是在制造电脑的零部件准备好零件后,剩下的工作就是按照逻辑把它们组装到一起。将......
  • CF1863G Swaps 题解
    题目链接点击打开链接题目解法这么牛的题!!!先套路地建图,连边\(i\toa_i\)考虑交换\(a_i\)和\(a_{a_i}\)的含义,我们把边\(i\toa_i,\;a_i\toa_{a_i}\)变成了\(i\toa_{a_i}\)和\(a_i\)的自环每次交换的话分析过于复杂,考虑打\(tag\),我们把所有操作过的\(i\),在\(i......
  • 13.模块 包
    【一】模块介绍1)概念在Python中,一个py文件就是一个模块文件名xxx.py,其中xxx就是模块名2)优点极大的提高了开发速率增加程序的健壮性和可读性3)来源内置的:python解释器自带的,可直接用第三方:别人写的,需要下载在使用自定义:自己写的4)存在形式1.单模块自定义的功能所......