如何在Kotlin开发的Android应用中无网络通过SMS发送图片
基于Kotlin的Android SMS图片传输实现方案
实现流程
- 权限申请:获取相机、存储访问、SMS发送/接收的必要权限
- 图片获取:从系统相机拍摄或本地相册选取图片
- 图片压缩:将图片压缩至运营商MMS限制的大小范围内
- 消息发送:将压缩后的图片封装为MMS消息,通过系统API或短信应用发送
- 状态反馈:监听发送状态,向用户展示成功/失败提示
工作原理
普通SMS仅支持文本内容,传输图片需依赖MMS(多媒体短信)——这是SMS的扩展协议,依托蜂窝短信通道传输,无需WiFi或额外数据流量(部分运营商仅需开启数据开关但不消耗流量)。
核心逻辑:将图片文件压缩后编码为多媒体附件,封装进MMS消息体,通过Android系统的短信服务发送至目标号码。由于不同运营商对单条MMS的大小限制在100KB-1MB之间,图片压缩是确保发送成功的关键步骤。
具体实现方法
1. 权限配置
在AndroidManifest.xml中声明所需权限:
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <!-- 适配Android 13以下版本的存储权限 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
动态申请危险权限(Android 6.0+要求):
private val requiredPermissions = arrayOf( Manifest.permission.CAMERA, Manifest.permission.READ_MEDIA_IMAGES, Manifest.permission.SEND_SMS ) private fun checkAndRequestPermissions() { val missingPermissions = requiredPermissions.filter { ContextCompat.checkSelfPermission(this, it) != PackageManager.PERMISSION_GRANTED } if (missingPermissions.isNotEmpty()) { registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { results -> // 可在这里处理权限申请结果,比如拒绝后提示用户 }.launch(missingPermissions.toTypedArray()) } }
2. 获取图片
调用相机拍照
private var photoUri: Uri? = null private val takePictureLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) { success -> if (success) photoUri?.let { handleImage(it) } } private fun takePhoto() { val photoFile = createTempImageFile() ?: return photoUri = FileProvider.getUriForFile( this, "${packageName}.fileprovider", photoFile ) takePictureLauncher.launch(photoUri) } private fun createTempImageFile(): File? { val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES) return try { File.createTempFile("JPEG_${timeStamp}_", ".jpg", storageDir) } catch (e: IOException) { null } }
从相册选图
private val pickImageLauncher = registerForActivityResult(ActivityResultContracts.GetContent()) { uri -> uri?.let { handleImage(it) } } private fun pickImageFromGallery() { pickImageLauncher.launch("image/*") }
3. 图片压缩处理
private fun handleImage(uri: Uri) { val compressedFile = compressImage(uri) ?: return // 替换为目标号码后调用发送方法 sendImageViaSystemSms("13800138000", compressedFile) } private fun compressImage(uri: Uri): File? { val inputStream = contentResolver.openInputStream(uri) ?: return null val bitmap = BitmapFactory.decodeStream(inputStream) val outputFile = File(getExternalFilesDir(Environment.DIRECTORY_PICTURES), "compressed_img.jpg") val outputStream = FileOutputStream(outputFile) // 先尝试质量压缩(50%质量) bitmap.compress(Bitmap.CompressFormat.JPEG, 50, outputStream) outputStream.flush() outputStream.close() bitmap.recycle() // 如果压缩后仍超过100KB,再进行分辨率压缩 return if (outputFile.length() > 1024 * 100) { val resizedBitmap = resizeBitmap(bitmap, 800, 600) ?: return null val newOutputStream = FileOutputStream(outputFile) resizedBitmap.compress(Bitmap.CompressFormat.JPEG, 50, newOutputStream) newOutputStream.flush() newOutputStream.close() resizedBitmap.recycle() outputFile } else { outputFile } } private fun resizeBitmap(bitmap: Bitmap, maxWidth: Int, maxHeight: Int): Bitmap? { val scale = min(maxWidth.toFloat() / bitmap.width, maxHeight.toFloat() / bitmap.height) val matrix = Matrix().apply { postScale(scale, scale) } return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true) }
4. 发送MMS消息
方式一:调用系统短信应用(兼容性好)
private fun sendImageViaSystemSms(phoneNumber: String, imageFile: File) { val imageUri = Uri.fromFile(imageFile) val intent = Intent(Intent.ACTION_SEND).apply { type = "image/jpeg" putExtra("address", phoneNumber) putExtra(Intent.EXTRA_STREAM, imageUri) } startActivity(Intent.createChooser(intent, "选择短信应用发送")) }
方式二:直接通过SMS Manager发送(自定义程度高)
private fun sendImageViaMms(phoneNumber: String, imageFile: File) { val smsManager = SmsManager.getDefault() // 插入MMS基本信息 val mmsValues = ContentValues().apply { put(Telephony.Mms.ADDRESS, phoneNumber) put(Telephony.Mms.SUBJECT, "图片消息") put(Telephony.Mms.BODY, "您收到一张图片") put(Telephony.Mms.DATE, System.currentTimeMillis()) put(Telephony.Mms.MESSAGE_TYPE, Telephony.Mms.MESSAGE_TYPE_SEND) } val mmsUri = contentResolver.insert(Telephony.Mms.CONTENT_URI, mmsValues) ?: return // 添加图片附件 val partValues = ContentValues().apply { put(Telephony.Mms.Part.MID, mmsUri.lastPathSegment) put(Telephony.Mms.Part.CONTENT_TYPE, "image/jpeg") put(Telephony.Mms.Part.NAME, imageFile.name) } val partUri = contentResolver.insert(Telephony.Mms.Part.CONTENT_URI, partValues) ?: return // 将图片写入附件 contentResolver.openOutputStream(partUri)?.use { outputStream -> FileInputStream(imageFile).copyTo(outputStream) } // 发送MMS smsManager.sendMultimediaMessage(this, mmsUri, null, null, null) }
5. 监听发送状态
注册广播接收器监听MMS发送结果:
private val mmsSentReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { when (resultCode) { Activity.RESULT_OK -> Toast.makeText(context, "图片发送成功", Toast.LENGTH_SHORT).show() else -> Toast.makeText(context, "图片发送失败", Toast.LENGTH_SHORT).show() } context?.unregisterReceiver(this) } } // 发送前注册广播 registerReceiver(mmsSentReceiver, IntentFilter("android.provider.Telephony.MMS_SENT"))
注意事项
- MMS大小限制:不同运营商的单条MMS上限不同,建议压缩至100KB以内以提升成功率。
- FileProvider配置:调用相机拍照时需在
AndroidManifest.xml中配置FileProvider,避免FileUriExposedException。 - 权限适配:Android 13及以上需使用
READ_MEDIA_IMAGES权限,旧版本使用READ_EXTERNAL_STORAGE。
内容的提问来源于stack exchange,提问作者Patrick Renaud
相关产品推荐
相关产品推荐

