如何无需打开相机应用,通过短信指令实现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
相关产品推荐
相关产品推荐

