You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何无需打开相机应用,通过短信指令实现Android后台拍照?

Android短信指令触发自动拍照实现方案

需求说明

我正在开发一款Android应用,该应用可在收到特定指令的短信时执行拍照、发送图片或录制声音等操作。目前已完成接收授权号码指令并响应的功能,且运行正常。现在需要实现:收到//photo指令时,无需打开相机应用及手动交互,自动完成拍照操作。

现有代码

MainActivity

package uk.co.lorenzopulcinelli.smsinterpreter

import android.Manifest
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.core.app.ActivityCompat

class MainActivity : AppCompatActivity() {

    private val requestReceiveSms = 2
    // 新增权限请求码
    private val requestCameraStorage = 3

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        checkPermissions()
    }

    // 统一检查所需权限
    private fun checkPermissions() {
        val requiredPermissions = arrayOf(
            Manifest.permission.RECEIVE_SMS,
            Manifest.permission.CAMERA,
            Manifest.permission.WRITE_EXTERNAL_STORAGE
        )
        val missingPermissions = mutableListOf<String>()
        for (permission in requiredPermissions) {
            if (ActivityCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                missingPermissions.add(permission)
            }
        }
        if (missingPermissions.isNotEmpty()) {
            ActivityCompat.requestPermissions(this, missingPermissions.toTypedArray(), requestCameraStorage)
        }
    }
}

SmsInterpreter

package uk.co.lorenzopulcinelli.smsinterpreter

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.telephony.SmsMessage
import android.widget.Toast

class SmsInterpreter : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {

        val extras = intent.extras

        if (extras != null) {
            val sms = extras.get("pdus") as Array<*>

            for (i in sms.indices) {
                val format = extras.getString("format")

                val smsMessage = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
                    SmsMessage.createFromPdu(sms[i] as ByteArray, format)
                }else{
                    SmsMessage.createFromPdu(sms[i] as ByteArray)
                }

                val phoneNumber = smsMessage.originatingAddress
                val messageText =  smsMessage.messageBody.toString()

                val numberEnabled = "+393332217878"    //授权号码示例,仅允许该号码发送指令

                if (phoneNumber.equals(numberEnabled)){
                    when (messageText) {
                        "//photo" -> { 
                            // 启动拍照服务,避免在BroadcastReceiver中执行耗时操作
                            val photoIntent = Intent(context, PhotoCaptureService::class.java)
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                                context.startForegroundService(photoIntent)
                            } else {
                                context.startService(photoIntent)
                            }
                        }
                        "//sound" -> {
                            // 优化MediaPlayer使用方式,避免内存泄漏
                            val mp = MediaPlayer.create(context, R.raw.jazzysound)
                            mp.start()
                            mp.setOnCompletionListener { it.release() }
                        }
                        "//send" -> { println("Send") }
                        "//record" -> { println("Record") }
                        "//rubrica" -> { println("rubrica") }
                        else -> {
                            println("无效指令!!")
                        }
                    }

                }else{
                    Toast.makeText(context, "未授权号码!!", Toast.LENGTH_SHORT).show()
                }
            }
        }
    }
}

AndroidManifest.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">

    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <!-- Android 10+ 需添加前台服务权限 -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

    <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.SmsInterpreter"
        tools:targetApi="31">
        <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>

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
        <receiver android:name=".SmsInterpreter"
            android:exported="true"
            android:permission="android.permission.BROADCAST_SMS">
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
            </intent-filter>
        </receiver>
        <!-- 注册拍照服务 -->
        <service
            android:name=".PhotoCaptureService"
            android:exported="false"
            android:foregroundServiceType="camera"/>
    </application>

</manifest>

新增拍照服务实现(PhotoCaptureService.kt)

使用CameraX实现后台拍照,简化相机操作并提升兼容性:

package uk.co.lorenzopulcinelli.smsinterpreter

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.IBinder
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.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import java.io.File
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors

class PhotoCaptureService : Service() {
    private lateinit var cameraExecutor: ExecutorService
    private var imageCapture: ImageCapture? = null

    override fun onCreate() {
        super.onCreate()
        cameraExecutor = Executors.newSingleThreadExecutor()
        // 启动前台服务,Android 8.0+ 必须
        createNotificationChannel()
        val notification = NotificationCompat.Builder(this, "CAMERA_CHANNEL")
            .setContentTitle("正在执行拍照操作")
            .setSmallIcon(R.mipmap.ic_launcher)
            .build()
        startForeground(1, notification)
        startCamera()
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val serviceChannel = NotificationChannel(
                "CAMERA_CHANNEL",
                "Camera Service",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            val manager = getSystemService(NotificationManager::class.java)
            manager.createNotificationChannel(serviceChannel)
        }
    }

    private fun startCamera() {
        val cameraProviderFuture = ProcessCameraProvider.getInstance(this)

        cameraProviderFuture.addListener({
            val cameraProvider: ProcessCameraProvider = cameraProviderFuture.get()

            val preview = Preview.Builder()
                .build()

            imageCapture = ImageCapture.Builder()
                .build()

            val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA

            try {
                cameraProvider.unbindAll()
                cameraProvider.bindToLifecycle(this, cameraSelector, preview, imageCapture)
                // 绑定后立即拍照
                takePhoto()
            } catch(exc: Exception) {
                println("相机绑定失败: ${exc.message}")
            }

        }, ContextCompat.getMainExecutor(this))
    }

    private fun takePhoto() {
        val imageCapture = imageCapture ?: return

        val photoFile = File(
            externalMediaDirs.firstOrNull(),
            SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US)
                .format(System.currentTimeMillis()) + ".jpg"
        )

        val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()

        imageCapture.takePicture(
            outputOptions,
            cameraExecutor,
            object : ImageCapture.OnImageSavedCallback {
                override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
                    val msg = "照片已保存至: ${photoFile.absolutePath}"
                    println(msg)
                    // 拍照完成后停止服务
                    stopSelf()
                }

                override fun onError(exc: ImageCaptureException) {
                    println("拍照失败: ${exc.message}")
                    stopSelf()
                }
            })
    }

    override fun onBind(intent: Intent): IBinder? {
        return null
    }

    override fun onDestroy() {
        super.onDestroy()
        cameraExecutor.shutdown()
    }
}

关键注意事项

  • 权限处理:确保动态申请CAMERA、WRITE_EXTERNAL_STORAGE权限,Android 10+ 可改用MANAGE_EXTERNAL_STORAGE权限或通过MediaStore保存图片。
  • 后台操作限制:BroadcastReceiver的onReceive方法不能执行耗时操作,必须使用前台服务处理拍照逻辑(相机操作属于后台受限功能)。
  • 资源释放:注意MediaPlayer、相机线程等资源的释放,避免内存泄漏。
  • 兼容性:CameraX自动适配不同设备,相比Camera2 API大幅减少兼容性问题。

内容的提问来源于stack exchange,提问作者George Jung

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 17:15:43