휴대폰에서 보내는 문자를 흔히 다 SMS라고 부르지만, 엄밀히 나누면 SMS(Short Message Service)는 짧은 단문이고 길이가 넘어가거나 이미지가 붙으면 MMS(Multimedia Messaging Service)로 넘어갑니다. 앱에서 문자 기능을 넣을 때 이 차이가 꽤 크게 작용합니다.

가장 흔한 방법은 Intent로 기본 문자 앱을 띄우는 건데, 이건 사용자가 전송 버튼을 직접 눌러야 하죠. 백그라운드에서 코드로 바로 보내야 한다면 SmsManager를 쓰게 되는데, 단문이라 길이 제한에 걸리고 이미지를 붙일 수 없습니다. 이미지를 넣거나 긴 내용을 코드에서 바로 보내야 할 때 쓰는 방법이 아래의 MMS 전송입니다.

광고

먼저 알고 들어갈 점

MMS는 통신사와 단말기에 따라 되기도 하고 안 되기도 합니다. 원 작성자도 받는 쪽이 안드로이드든 아이폰이든 수신되는 건 확인했지만, 일부 통신사는 확인할 표본이 없었다고 밝혀 두었습니다. 그러니 "무조건 된다"는 전제로 기능을 설계하면 곤란합니다. 앱의 핵심 기능이 문자 발송이라면 단문 SMS로 구현해 두는 쪽이 훨씬 마음이 편하고, MMS는 부가 기능 정도로 생각하는 게 맞습니다. 요금 체계도 SMS와 MMS가 다르니 대량 발송을 계획한다면 미리 확인해 두세요.

사용하는 라이브러리는 klinker41/android-smsmms이고 Apache 라이선스입니다.

그대로 쓰면 전송이 안 되는 부분

여기서 많이들 막힙니다. 라이브러리에 들어 있는 Transaction 클래스를 그대로 쓰면 메시지가 나가지 않습니다. 원 작성자는 이 클래스의 일부를 주석 처리한 버전을 따로 만들어 두었고, 그 파일을 프로젝트에 직접 넣어서 써야 합니다.

  1. 수정된 Transaction 파일을 받아 프로젝트에 추가합니다: android-smsmms-Transaction
  2. Gradle에 라이브러리 의존성을 넣습니다.
implementation 'com.klinkerapps:android-smsmms:5.1.0'
  1. 매니페스트에 TransactionService를 등록합니다.
<service android:name="com.android.mms.transaction.TransactionService"/>

매니페스트 권한

smsmms 주석 아래쪽이 라이브러리 제작자가 권장하는 권한입니다. 저장공간, 전화, 연락처 권한은 원문에서 함께 적혀 있었지만, 원 작성자는 따로 추가하지 않아도 된다고 했습니다. 필요한 기능에 맞춰 골라 넣으면 됩니다.

    <!-- smsmms-->
    <uses-permission android:name="android.permission.SEND_SMS"/>
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.WRITE_SMS"/>
    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
    <uses-permission android:name="android.permission.RECEIVE_MMS"/>
    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.provider.Telephony.SMS_RECEIVED" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <!--저장공간 쓰기-->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <!--전화 권한-->
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.CALL_PHONE"/>

    <!-- Contact 권한-->
    <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
    <uses-permission android:name="android.permission.READ_CONTACTS"/>

    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

SMS 관련 권한은 위험 권한이라 매니페스트에 적는 것만으로 끝나지 않습니다. 최근 안드로이드에서는 실행 중에 사용자에게 권한 요청을 띄워야 하니 그 처리도 잊지 마세요.

SMS와 MMS 요금 차이를 보여 주는 검색 결과 캡처

이미지 붙여서 MMS 보내기

첨부 이미지는 drawable 리소스에 있는 것이든 폰에 저장된 파일이든 상관없지만, 어느 쪽이든 Bitmap으로 바꿔서 addImage에 넘겨야 합니다. 여러 장을 보내고 싶으면 addImage를 장수만큼 호출하면 됩니다. 제목이 없는 메시지라면 주석 처리된 생성자 쪽을 쓰면 되고요.

    public void sendMMS(String phone) {

        Log.d(TAG, "sendMMS(Method) : " + "start");

        String subject = "제목";
        String text = "내용";

        // 예시 (절대경로) : String imagePath = "/storage/emulated/0/Pictures/Screenshots/Screenshot_20190312-181007.png";
        String imagePath = "이미지 경로";

        Log.d(TAG, "subject : " + subject);
        Log.d(TAG, "text : " + text);
        Log.d(TAG, "imagePath : " + imagePath);

        Settings settings = new Settings();
        settings.setUseSystemSending(true);

        // TODO : 이 Transaction 클래스는 위 링크에서 받은 수정본을 써야 함
        Transaction transaction = new Transaction(this, settings);

        // 제목이 있을 경우
        Message message = new Message(text, phone, subject);

        // 제목이 없을 경우
        // Message message = new Message(text, number);

        if(!(imagePath.equals("") || imagePath == null)) {
           // 예시2 (앱 내부 리소스) :
           // Bitmap mBitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.mms_test_1);
           Bitmap mBitmap = BitmapFactory.decodeFile(imagePath);
           // TODO : image를 여러 장 넣고 싶은 경우, addImage를 계속 호출해서 넣으면 됨
           message.addImage(mBitmap);
       }

       long id = android.os.Process.getThreadPriority(android.os.Process.myTid());

       transaction.sendNewMessage(message, id);

주의할 점이 두 가지 있습니다. 원본 코드가 메서드 마지막 닫는 중괄호 } 없이 끝나 있으니 붙여 넣을 때 직접 닫아 주세요. 그리고 imagePath.equals("")가 null 비교보다 먼저 실행되기 때문에 imagePath가 실제로 null이면 그 자리에서 NullPointerException이 납니다. imagePath == null || imagePath.equals("") 순서로 바꿔 두는 편이 안전합니다.

외부 저장소의 이미지를 절대경로로 읽는 방식은 안드로이드 버전이 올라가면서 저장소 접근 정책이 바뀌어 그대로는 안 열리는 경우가 있습니다. 테스트는 앱 리소스(decodeResource) 쪽으로 먼저 해 보고, 전송이 확인되면 그다음에 파일 경로 문제를 잡는 순서가 디버깅하기 편합니다.

참고: 안드로이드 - SMS, MMS 문자 코드로 보내기 (조길상 블로그)