如何在Android中以编程方式从设备读取SMS消息?


249

我想从设备中检索SMS消息并显示它们吗?


@David Freitas可信任的链接+1
Shahzad Imam

3
@DavidFreitas此链接无效,您能否分享最新链接?
霍拜卜

3
@Khobaib,像往常一样,互联网上的事物瞬息万变。我在archive.org stackoverflow.com/a/19966227/40961上找到了一个副本,感谢他们(我最近捐赠了使它们继续运行)。但是我们应该考虑将网页的内容从web.archive.org/web/20121022021217/http://mobdev.olin.edu/…转换为markdown语法,以解决此问题。大概一个小时的工作。
大卫·德·埃·弗雷塔斯

Answers:


157

使用内容解析器(“ content:// sms / inbox”)读取收件箱中的SMS。

// public static final String INBOX = "content://sms/inbox";
// public static final String SENT = "content://sms/sent";
// public static final String DRAFT = "content://sms/draft";
Cursor cursor = getContentResolver().query(Uri.parse("content://sms/inbox"), null, null, null, null);

if (cursor.moveToFirst()) { // must check the result to prevent exception
    do {
       String msgData = "";
       for(int idx=0;idx<cursor.getColumnCount();idx++)
       {
           msgData += " " + cursor.getColumnName(idx) + ":" + cursor.getString(idx);
       }
       // use msgData
    } while (cursor.moveToNext());
} else {
   // empty box, no SMS
}

请添加READ_SMS权限。

我希望它会有所帮助:)


7
谢谢!您拼写了“ getColumnName”,除此之外,它像一个超级按钮一样工作。哦,如果有人会使用它,请不要忘记添加权限android.permission.READ_SMS。
qwerty 2012年

1
谢谢。我修改了它:)
Suryavel TR

5
这是否还会使用@CommonsWare在其对已接受答案的注释中指定的未记录的api?
Krishnabhadra

1
注意!别错过moveToFirst我。
Alexandr Priymak

4
@Krishnabhadra是的。它使用未记录的“ content:// sms / inbox”内容提供程序。
pm_labs

79
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        final String myPackageName = getPackageName();
        if (!Telephony.Sms.getDefaultSmsPackage(this).equals(myPackageName)) {

            Intent intent = new Intent(Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT);
            intent.putExtra(Telephony.Sms.Intents.EXTRA_PACKAGE_NAME, myPackageName);
            startActivityForResult(intent, 1);
        }else {
            List<Sms> lst = getAllSms();
        }
    }else {
        List<Sms> lst = getAllSms();
    }

将应用设置为默认短信应用

    @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1) {
    if (resultCode == RESULT_OK) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            final String myPackageName = getPackageName();
            if (Telephony.Sms.getDefaultSmsPackage(mActivity).equals(myPackageName)) {

                List<Sms> lst = getAllSms();
            }
        }
    }
}
}

获取短信功能

public List<Sms> getAllSms() {
    List<Sms> lstSms = new ArrayList<Sms>();
    Sms objSms = new Sms();
    Uri message = Uri.parse("content://sms/");
    ContentResolver cr = mActivity.getContentResolver();

    Cursor c = cr.query(message, null, null, null, null);
    mActivity.startManagingCursor(c);
    int totalSMS = c.getCount();

    if (c.moveToFirst()) {
        for (int i = 0; i < totalSMS; i++) {

            objSms = new Sms();
            objSms.setId(c.getString(c.getColumnIndexOrThrow("_id")));
            objSms.setAddress(c.getString(c
                    .getColumnIndexOrThrow("address")));
            objSms.setMsg(c.getString(c.getColumnIndexOrThrow("body")));
            objSms.setReadState(c.getString(c.getColumnIndex("read")));
            objSms.setTime(c.getString(c.getColumnIndexOrThrow("date")));
            if (c.getString(c.getColumnIndexOrThrow("type")).contains("1")) {
                objSms.setFolderName("inbox");
            } else {
                objSms.setFolderName("sent");
            }

            lstSms.add(objSms);
            c.moveToNext();
        }
    }
    // else {
    // throw new RuntimeException("You have no SMS");
    // }
    c.close();

    return lstSms;
}

Sms类如下:

public class Sms{
private String _id;
private String _address;
private String _msg;
private String _readState; //"0" for have not read sms and "1" for have read sms
private String _time;
private String _folderName;

public String getId(){
return _id;
}
public String getAddress(){
return _address;
}
public String getMsg(){
return _msg;
}
public String getReadState(){
return _readState;
}
public String getTime(){
return _time;
}
public String getFolderName(){
return _folderName;
}


public void setId(String id){
_id = id;
}
public void setAddress(String address){
_address = address;
}
public void setMsg(String msg){
_msg = msg;
}
public void setReadState(String readState){
_readState = readState;
}
public void setTime(String time){
_time = time;
}
public void setFolderName(String folderName){
_folderName = folderName;
}

}

不要忘记在AndroidManifest.xml中定义权限

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

2
那是一段不错的代码。仅一件事,时间以毫秒为单位。我认为,最好将其设为易于阅读的格式,例如String receiveDayTime = Functions.dateFromMilisec(Long.valueOf(c.getColumnIndexOrThrow("date")), "hh:mm a MMM dd, yyyy");
Bibaswann Bandyopadhyay 2015年

1
用getter和setter进行所有操作的目的是什么,我真的不明白为什么不只使用直接访问元素的assoc数组或类
michnovka 2015年

1
@TomasNavara:检查此代码以了解getter和setter的用法。pastebin.com/Nh8YXtyJ
错误发生

@BibaswannBandyopadhyay如果您不想使用任何东西,除了android库和java库。new SimpleDateFormat("hh:mm", Locale.US).format(new Date(Long.parseLong(_time)));这将给您24小时的时间。
克里斯

mActivity没有定义。这是什么?
dthree

61

这是一个微不足道的过程。您可以在源代码SMSPopup中看到一个很好的示例

检查以下方法:

SmsMmsMessage getSmsDetails(Context context, long ignoreThreadId, boolean unreadOnly)
long findMessageId(Context context, long threadId, long _timestamp, int messageType
void setMessageRead(Context context, long messageId, int messageType)
void deleteMessage(Context context, long messageId, long threadId, int messageType)

这是读取方法:

SmsMmsMessage getSmsDetails(Context context,
                            long ignoreThreadId, boolean unreadOnly)
{
   String SMS_READ_COLUMN = "read";
   String WHERE_CONDITION = unreadOnly ? SMS_READ_COLUMN + " = 0" : null;
   String SORT_ORDER = "date DESC";
   int count = 0;
   // Log.v(WHERE_CONDITION);
   if (ignoreThreadId > 0) {
      // Log.v("Ignoring sms threadId = " + ignoreThreadId);
      WHERE_CONDITION += " AND thread_id != " + ignoreThreadId;
   }
   Cursor cursor = context.getContentResolver().query(
                      SMS_INBOX_CONTENT_URI,
                      new String[] { "_id", "thread_id", "address", "person", "date", "body" },
                      WHERE_CONDITION,
                      null,
                      SORT_ORDER);
   if (cursor != null) {
      try {
         count = cursor.getCount();
         if (count > 0) {
            cursor.moveToFirst();
            // String[] columns = cursor.getColumnNames();
            // for (int i=0; i<columns.length; i++) {
            // Log.v("columns " + i + ": " + columns[i] + ": " + cursor.getString(i));
            // }                                         
            long messageId = cursor.getLong(0);
            long threadId = cursor.getLong(1);
            String address = cursor.getString(2);
            long contactId = cursor.getLong(3);
            String contactId_string = String.valueOf(contactId);
            long timestamp = cursor.getLong(4);

            String body = cursor.getString(5);                             
            if (!unreadOnly) {
                count = 0;
            }

            SmsMmsMessage smsMessage = new SmsMmsMessage(context, address,
                          contactId_string, body, timestamp,
                          threadId, count, messageId, SmsMmsMessage.MESSAGE_TYPE_SMS);
            return smsMessage;
         }
      } finally {
         cursor.close();
      }
   }               
   return null;
}

47
这不是Android SDK的一部分。此代码错误地假设所有设备都支持该未记录且不受支持的内容提供程序。Google明确表示,依靠它不是一个好主意:android-developers.blogspot.com/2010/05/…–
CommonsWare

1
@Janusz:没有记录和受支持的方法可在所有设备上的所有SMS客户端上使用。
CommonsWare 2010年

9
@CommonsWare是伤心地听到。然后可能必须使用此API。
Janusz

@Omer是否知道如何计算每个联系人的SMS消息数量?
SpicyWeenie

4
代码已移动。搜索SmsPopupUtils.java在谷歌代码中给了我一个新的链接。如果他们再次移动,或完全停止它,这里有一个备份链路- pastebin.com/iPt7MLyM
KalEl

25

从API 19开始,您可以使用Telephony类。由于硬核值不会在每个设备中检索消息,因为内容提供商Uri会因设备和制造商而异。

public void getAllSms(Context context) {

    ContentResolver cr = context.getContentResolver();
    Cursor c = cr.query(Telephony.Sms.CONTENT_URI, null, null, null, null);
    int totalSMS = 0;
    if (c != null) {
        totalSMS = c.getCount();
        if (c.moveToFirst()) {
            for (int j = 0; j < totalSMS; j++) {
                String smsDate = c.getString(c.getColumnIndexOrThrow(Telephony.Sms.DATE));
                String number = c.getString(c.getColumnIndexOrThrow(Telephony.Sms.ADDRESS));
                String body = c.getString(c.getColumnIndexOrThrow(Telephony.Sms.BODY));
                Date dateFormat= new Date(Long.valueOf(smsDate));
                String type;
                switch (Integer.parseInt(c.getString(c.getColumnIndexOrThrow(Telephony.Sms.TYPE)))) {
                    case Telephony.Sms.MESSAGE_TYPE_INBOX:
                        type = "inbox";
                        break;
                    case Telephony.Sms.MESSAGE_TYPE_SENT:
                        type = "sent";
                        break;
                    case Telephony.Sms.MESSAGE_TYPE_OUTBOX:
                        type = "outbox";
                        break;
                    default:
                        break;
                }


                c.moveToNext();
            }
        }

        c.close();

    } else {
        Toast.makeText(this, "No message to show!", Toast.LENGTH_SHORT).show();
    }
}

9
似乎是不使用未记录的API且未引用第三方库的唯一答案。
Ishamael

我试图使用此代码从环聊(这是我的默认短信应用)中获取短信。取而代之的是,它检索了我通过Messenger发送的最后一封外发邮件...您知道是什么原因造成的吗?
Miki P

@MikiP使用我的猜测能力,我会说Messenger App询问您要用Messenger取代SMS管理。其他一些消息传递应用程序也会发生这种情况。我没有其他解释。
m3nda

2
不要忘记调用c.close();。
西塞罗·莫拉

1
@SardarAgabejli如果我们使用诸如“ contenturi:sms”之类的硬性值,则并不是每个设备都一样,但是如果使用Telephony类,则可以直接访问该竞争uri或该设备的sms db路径,指向短信数据库的帮手类
Manoj Perumarath

23

这篇文章有些陈旧,但这是用于获取与SMSAndroid中的内容提供商相关的数据的另一个简单解决方案:

使用此库:https : //github.com/EverythingMe/easy-content-providers

  • 全部获取SMS

    TelephonyProvider telephonyProvider = new TelephonyProvider(context);
    List<Sms> smses = telephonyProvider.getSms(Filter.ALL).getList();
    

    每个Sms都有所有字段,因此您可以获得所需的任何信息:
    地址,正文,receivedDate,类型(INBOX,SENT,DRAFT,..),threadId,...

  • 全部凝胶MMS

    List<Mms> mmses = telephonyProvider.getMms(Filter.ALL).getList();
  • 全部凝胶Thread

    List<Thread> threads = telephonyProvider.getThreads().getList();
  • 全部凝胶Conversation

    List<Conversation> conversations = telephonyProvider.getConversations().getList();

它可与List或一起使用,Cursor并且有一个示例应用程序可查看其外观和工作方式。

实际上,所有Android内容提供商均提供支持,例如:联系人,通话记录,日历等... 带有所有选项的完整文档:https : //github.com/EverythingMe/easy-content-providers/wiki/Android-提供者

希望它也有帮助:)


1
github上的源代码和示例非常有用。对于大多数常见的提供者来说,这是一个很好的包装/外观。谢谢。
m3nda

14

步骤1:首先,我们必须在清单文件中添加权限,例如

<uses-permission android:name="android.permission.RECEIVE_SMS" android:protectionLevel="signature" />
<uses-permission android:name="android.permission.READ_SMS" />

步骤2:然后添加服务短信接收器类以接收短信

<receiver android:name="com.aquadeals.seller.services.SmsReceiver">
    <intent-filter>
        <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
    </intent-filter>
</receiver>

步骤3:添加运行时权限

private boolean checkAndRequestPermissions()
{
    int sms = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_SMS);

    if (sms != PackageManager.PERMISSION_GRANTED)
    {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_SMS}, REQUEST_ID_MULTIPLE_PERMISSIONS);
        return false;
    }
    return true;
}

步骤4:在您的应用中添加此类并测试 Interface类

public interface SmsListener {
   public void messageReceived(String messageText);
}

SmsReceiver.java

public class SmsReceiver extends BroadcastReceiver {
private static SmsListener mListener;
public Pattern p = Pattern.compile("(|^)\\d{6}");
@Override
public void onReceive(Context context, Intent intent) {
    Bundle data  = intent.getExtras();
    Object[] pdus = (Object[]) data.get("pdus");
    for(int i=0;i<pdus.length;i++)
    {
        SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) pdus[i]);
        String sender = smsMessage.getDisplayOriginatingAddress();
        String phoneNumber = smsMessage.getDisplayOriginatingAddress();
        String senderNum = phoneNumber ;
        String messageBody = smsMessage.getMessageBody();
        try
        {
  if(messageBody!=null){
   Matcher m = p.matcher(messageBody);
    if(m.find()) {
      mListener.messageReceived(m.group(0));  }
 else {}}  }
        catch(Exception e){} } }
public static void bindListener(SmsListener listener) {
    mListener = listener; }}

模式有什么作用?
Mark Buikema '16

好吧...(com.aquadeals.seller.services.SmsReceiver)是通用服务名称吗?
m3nda

Ya不是服务名称,也就是我的应用程序中的SmsReceiver类路径
Venkatesh

为什么需要LOCATION的许可?
赞姆·桑克

1
我正在尝试制作一个即使用户已被杀死却向用户弹出短信内容的应用程序
Anjani Mittal,

11

已经有很多答案,但是我认为所有这些答案都缺少这个问题的重要部分。在从内部数据库或其表中读取数据之前,我们必须了解如何在内部数据库中存储数据,然后才能找到上述问题的解决方案:

如何在Android中以编程方式从设备读取SMS消息?

因此,在android SMS表中是这样的

在此处输入图片说明

知道,我们可以从数据库中选择所需的内容。在我们的情况下,我们只需要

ID,地址和正文

如果阅读短信:

1.询问权限

int REQUEST_PHONE_CALL = 1;

   if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.READ_SMS}, REQUEST_PHONE_CALL);
        }

要么

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

2.现在你的代码是这样的

// Create Inbox box URI
Uri inboxURI = Uri.parse("content://sms/inbox");

// List required columns
String[] reqCols = new String[]{"_id", "address", "body"};

// Get Content Resolver object, which will deal with Content Provider
ContentResolver cr = getContentResolver();

// Fetch Inbox SMS Message from Built-in Content Provider
Cursor c = cr.query(inboxURI, reqCols, null, null, null);

// Attached Cursor with adapter and display in listview
adapter = new SimpleCursorAdapter(this, R.layout.a1_row, c,
        new String[]{"body", "address"}, new int[]{
        R.id.A1_txt_Msg, R.id.A1_txt_Number});
lst.setAdapter(adapter);

希望这一点对您有所帮助。谢谢。


7

Google Play服务有两个API,可用于简化基于SMS的验证过程

SMS检索器API

提供全自动的用户体验,无需用户手动键入验证码,也不需要任何额外的应用程序权限,因此应在可能的情况下使用。但是,它确实需要您在消息正文中放置自定义哈希码,因此您还必须对服务器端具有控制权

  • 消息要求 -唯一标识您的应用程序的11位哈希码
  • 发件人要求 -无
  • 用户互动 -无

在Android应用中请求短信验证

在服务器上执行SMS验证

SMS用户同意API

不需要自定义哈希码,但是需要用户批准您的应用程序访问包含验证码的消息的请求。为了最大程度地减少向用户显示错误消息的机会,SMS User Consent将在用户的“联系人”列表中过滤掉发件人的消息。

  • 邮件要求 -4-10位字母数字代码,至少包含一个数字
  • 发件人要求 -发件人不能在用户的联系人列表中
  • 用户交互 -一键批准

The SMS User Consent API是Google Play服务的一部分。要使用它,您至少需要17.0.0这些库的版本:

implementation "com.google.android.gms:play-services-auth:17.0.0"
implementation "com.google.android.gms:play-services-auth-api-phone:17.1.0"

步骤1:开始收听SMS消息

SMS用户同意将侦听包含一次性代码的传入SMS消息,最长持续五分钟。它不会在启动之前查看发送的任何消息。如果您知道将发送一次性代码的电话号码,则可以指定senderPhoneNumber,如果不null匹配,则可以指定任何号码。

 smsRetriever.startSmsUserConsent(senderPhoneNumber /* or null */)

步骤2:请求同意阅读邮件

一旦您的应用收到一条包含一次性代码的消息,就会通过广播通知该消息。在这一点上,您尚未同意阅读该消息-而是获得一个Intent提示,您可以开始提示用户同意。在您的内部BroadcastReceiver,您可以使用Intent中的来显示提示extras。当您启动该意图时,它将提示用户阅读一条消息的权限。将向他们显示将与您的应用共享的全部文本。

val consentIntent = extras.getParcelable<Intent>(SmsRetriever.EXTRA_CONSENT_INTENT)
startActivityForResult(consentIntent, SMS_CONSENT_REQUEST)

在此处输入图片说明

步骤3:解析一次性代码并完成SMS验证

当用户单击时“Allow”-是时候实际阅读邮件了!在里面,onActivityResult您可以从数据中获取SMS消息的全文:

val message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE)

然后,您解析SMS消息,并将一次性代码传递给您的后端!


4-10 digit alphanumeric code containing at least one number您能解释一下这是什么意思吗?这是否意味着整个消息的长度应该仅为短信代码的4-10个字符?
Zeeshan Shabbir

同样也谢谢您
Levon Petrosyan

这仅适用于OTP验证吗?读取手机内的所有其他消息,所有SMS等如何?有没有新的API,请告诉我。编码愉快!:)
Manoj Perumarath

我们总是收到超时错误。请帮帮我
Manikandan K

2
String WHERE_CONDITION = unreadOnly ? SMS_READ_COLUMN + " = 0" : null;

更改者:

String WHERE_CONDITION = unreadOnly ? SMS_READ_COLUMN + " = 0 " : SMS_READ_COLUMN + " = 1 ";

2

读短信的Kotlin代码:

1-将此权限添加到AndroidManifest.xml:

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

2-创建BroadCastreceiver类:

package utils.broadcastreceivers

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.telephony.SmsMessage
import android.util.Log

class MySMSBroadCastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
    var body = ""
    val bundle = intent?.extras
    val pdusArr = bundle!!.get("pdus") as Array<Any>
    var messages: Array<SmsMessage?>  = arrayOfNulls(pdusArr.size)

 // if SMSis Long and contain more than 1 Message we'll read all of them
    for (i in pdusArr.indices) {
        messages[i] = SmsMessage.createFromPdu(pdusArr[i] as ByteArray)
    }
      var MobileNumber: String? = messages[0]?.originatingAddress
       Log.i(TAG, "MobileNumber =$MobileNumber")         
       val bodyText = StringBuilder()
        for (i in messages.indices) {
            bodyText.append(messages[i]?.messageBody)
        }
        body = bodyText.toString()
        if (body.isNotEmpty()){
       // Do something, save SMS in DB or variable , static object or .... 
                       Log.i("Inside Receiver :" , "body =$body")
        }
    }
 }

3-如果Android 6及更高版本,则获得短信许可:

   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && 
    ActivityCompat.checkSelfPermission(context!!,
            Manifest.permission.RECEIVE_SMS
        ) != PackageManager.PERMISSION_GRANTED
    ) { // Needs permission

            requestPermissions(arrayOf(Manifest.permission.RECEIVE_SMS),
            PERMISSIONS_REQUEST_READ_SMS
        )

    } else { // Permission has already been granted

    }

4-将此请求代码添加到“活动”或“片段”中:

 companion object {
    const val PERMISSIONS_REQUEST_READ_SMS = 100
   }

5-覆盖检查许可请求结果很有趣:

 override fun onRequestPermissionsResult(
    requestCode: Int, permissions: Array<out String>,
    grantResults: IntArray
) {
    when (requestCode) {

        PERMISSIONS_REQUEST_READ_SMS -> {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.i("BroadCastReceiver", "PERMISSIONS_REQUEST_READ_SMS Granted")
            } else {
                //  toast("Permission must be granted  ")
            }
        }
    }
}

1

最简单的功能

要阅读短信,我编写了一个返回会话对象的函数:

class Conversation(val number: String, val message: List<Message>)
class Message(val number: String, val body: String, val date: Date)

fun getSmsConversation(context: Context, number: String? = null, completion: (conversations: List<Conversation>?) -> Unit) {
        val cursor = context.contentResolver.query(Telephony.Sms.CONTENT_URI, null, null, null, null)

        val numbers = ArrayList<String>()
        val messages = ArrayList<Message>()
        var results = ArrayList<Conversation>()

        while (cursor != null && cursor.moveToNext()) {
            val smsDate = cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Sms.DATE))
            val number = cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Sms.ADDRESS))
            val body = cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Sms.BODY))

            numbers.add(number)
            messages.add(Message(number, body, Date(smsDate.toLong())))
        }

        cursor?.close()

        numbers.forEach { number ->
            if (results.find { it.number == number } == null) {
                val msg = messages.filter { it.number == number }
                results.add(Conversation(number = number, message = msg))
            }
        }

        if (number != null) {
            results = results.filter { it.number == number } as ArrayList<Conversation>
        }

        completion(results)
    }

使用:

getSmsConversation(this){ conversations ->
    conversations.forEach { conversation ->
        println("Number: ${conversation.number}")
        println("Message One: ${conversation.message[0].body}")
        println("Message Two: ${conversation.message[1].body}")
    }
}

或仅获得特定号码的会话:

getSmsConversation(this, "+33666494128"){ conversations ->
    conversations.forEach { conversation ->
        println("Number: ${conversation.number}")
        println("Message One: ${conversation.message[0].body}")
        println("Message Two: ${conversation.message[1].body}")
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.