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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 18:12:49