如何在Android中读取MMS数据?


74

我想读取MMS数据,我已经mmssms.db在mms条目的存储位置看到了零件表。我正在使用光标,我想知道适当的方法URI;我使用“的内容://彩信,短信/通话”和列名“地址”(发送到),“文本”或“主题”和形象的“数据”列名的。

我已经看到了mmssms.dbPart Table的架构及其列。


mmssms.db数据库是固件的一部分,不能由Android应用程序访问。该content://mms-sms/conversations内容提供商不是SDK的一部分,不应由Android应用程序进行访问。
CommonsWare 2010年

我在这里做类似的事情!stackoverflow.com/questions/11556633/...
toobsco42

Answers:


279

很难找到与此相关的文档,因此我将在此处收集我发现的所有信息。如果您急于或者不喜欢阅读,请跳至“如何从短信中获取数据”部分。

内容:// mms-sms / conversations

这是Mms和SMS提供程序的URI,它使我们可以同时查询MMS和SMS数据库,并将它们混合在一个线程中(称为对话)。

URI为什么重要?嗯,这是获取MMS和SMS消息的标准方法。例如,当您收到一条短信并单击通知栏时,它将发送如下的广播意图:content://mms-sms/conversations/XXXXXX对话的ID。

获取所有对话的列表

您唯一要做的就是查询content://mms-sms/conversationsUri:

ContentResolver contentResolver = getContentResolver();
final String[] projection = new String[]{"*"};
Uri uri = Uri.parse("content://mms-sms/conversations/");
Cursor query = contentResolver.query(uri, projection, null, null, null);

注意:通常,当您调用query并想返回所有列时,可以将其null作为projection参数传递。但是,您不能使用此提供程序执行此操作,因此这就是我使用的原因*

现在您可以Cursor像往常一样遍历。这些是您想使用的更重要的列:

  • _id是消息的ID。船长显然要营救吗?并不是的。可以使用content://sms或使用此ID检索详细信息content://mms
  • date 无需任何解释。
  • thread_id 是对话的ID
  • body此对话中最后一条短信的内容。如果是彩信,则即使有文字部分,也将是null

注意:如果查询content://mms-sms/conversations,它将返回不同对话的列表,这些对话_id是每个对话中的最后一个SMS或MMS。如果您查询content://mms-sms/conversations/xxx,它将返回ID为的对话中的每个SMS和/或MMS xxx

如何区分短信和彩信

通常,您将想知道您正在处理哪种类型的消息。文档说:

MmsSms.TYPE_DISCRIMINATOR_COLUMN可以在投影中请求虚拟列 进行查询。根据行所代表的消息是MMS消息还是SMS消息,其值为“ mms”或“ sms”。

我认为它是指此变量...但是我无法使其工作。如果有,请告诉我如何进行编辑。

到目前为止,这是我所做的,并且似乎可以正常运行,但是必须有更好的方法:

ContentResolver contentResolver = getContentResolver();
final String[] projection = new String[]{"_id", "ct_t"};
Uri uri = Uri.parse("content://mms-sms/conversations/");
Cursor query = contentResolver.query(uri, projection, null, null, null);
if (query.moveToFirst()) {
    do {
        String string = query.getString(query.getColumnIndex("ct_t"));
        if ("application/vnd.wap.multipart.related".equals(string)) {
            // it's MMS
        } else {
            // it's SMS
        }
    } while (query.moveToNext());
}

如何从短信中获取数据

因此,您具有SMS的ID,那么您唯一要做的就是:

String selection = "_id = "+id;
Uri uri = Uri.parse("content://sms");
Cursor cursor = contentResolver.query(uri, null, selection, null, null);
String phone = cursor.getString(cursor.getColumnIndex("address"));
int type = cursor.getInt(cursor.getColumnIndex("type"));// 2 = sent, etc.
String date = cursor.getString(cursor.getColumnIndex("date"));
String body = cursor.getString(cursor.getColumnIndex("body"));

如何从MMS数据中获取数据?

MMS有点不同。它们可以用不同的部分(文本,音频,图像等)构建;因此,这里将介绍如何分别检索每种数据。

因此,让我们猜测mmsId变量中有MMS ID 。我们可以使用content://mms/提供程序来获取有关此MMS的详细信息:

Uri uri = Uri.parse("content://mms/");
String selection = "_id = " + mmsId;
Cursor cursor = getContentResolver().query(uri, null, selection, null, null);

然而,唯一感兴趣的列read1如果该消息已经被阅读。

如何从彩信中获取文本内容

在这里,我们必须使用content://mms/part...例如:

String selectionPart = "mid=" + mmsId;
Uri uri = Uri.parse("content://mms/part");
Cursor cursor = getContentResolver().query(uri, null,
    selectionPart, null, null);
if (cursor.moveToFirst()) {
    do {
        String partId = cursor.getString(cursor.getColumnIndex("_id"));
        String type = cursor.getString(cursor.getColumnIndex("ct"));
        if ("text/plain".equals(type)) {
            String data = cursor.getString(cursor.getColumnIndex("_data"));
            String body;
            if (data != null) {
                // implementation of this method below
                body = getMmsText(partId);
            } else {
                body = cursor.getString(cursor.getColumnIndex("text"));
            }
        }
    } while (cursor.moveToNext());
}

它可能包含文本的不同部分……但通常只有一个。因此,如果要删除循环,它将在大多数情况下有效。该getMmsText方法如下所示:

private String getMmsText(String id) {
    Uri partURI = Uri.parse("content://mms/part/" + id);
    InputStream is = null;
    StringBuilder sb = new StringBuilder();
    try {
        is = getContentResolver().openInputStream(partURI);
        if (is != null) {
            InputStreamReader isr = new InputStreamReader(is, "UTF-8");
            BufferedReader reader = new BufferedReader(isr);
            String temp = reader.readLine();
            while (temp != null) {
                sb.append(temp);
                temp = reader.readLine();
            }
        }
    } catch (IOException e) {}
    finally {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {}
        }
    }
    return sb.toString();
}

如何从彩信中获取图像

这与获取文本部分相同...唯一的不同是,您将寻找其他的mime类型:

String selectionPart = "mid=" + mmsId;
Uri uri = Uri.parse("content://mms/part");
Cursor cPart = getContentResolver().query(uri, null,
    selectionPart, null, null);
if (cPart.moveToFirst()) {
    do {
        String partId = cPart.getString(cPart.getColumnIndex("_id"));
        String type = cPart.getString(cPart.getColumnIndex("ct"));
        if ("image/jpeg".equals(type) || "image/bmp".equals(type) ||
                "image/gif".equals(type) || "image/jpg".equals(type) ||
                "image/png".equals(type)) {
            Bitmap bitmap = getMmsImage(partId);
        }
    } while (cPart.moveToNext());
}

getMmsImage方法如下所示:

private Bitmap getMmsImage(String _id) {
    Uri partURI = Uri.parse("content://mms/part/" + _id);
    InputStream is = null;
    Bitmap bitmap = null;
    try {
        is = getContentResolver().openInputStream(partURI);
        bitmap = BitmapFactory.decodeStream(is);
    } catch (IOException e) {}
    finally {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {}
        }
    }
    return bitmap;
}

如何获得发件人地址

您将需要使用content://mms/xxx/addr提供程序,其中xxxMMS的ID是:

private String getAddressNumber(int id) {
    String selectionAdd = new String("msg_id=" + id);
    String uriStr = MessageFormat.format("content://mms/{0}/addr", id);
    Uri uriAddress = Uri.parse(uriStr);
    Cursor cAdd = getContentResolver().query(uriAddress, null,
        selectionAdd, null, null);
    String name = null;
    if (cAdd.moveToFirst()) {
        do {
            String number = cAdd.getString(cAdd.getColumnIndex("address"));
            if (number != null) {
                try {
                    Long.parseLong(number.replace("-", ""));
                    name = number;
                } catch (NumberFormatException nfe) {
                    if (name == null) {
                        name = number;
                    }
                }
            }
        } while (cAdd.moveToNext());
    }
    if (cAdd != null) {
        cAdd.close();
    }
    return name;
}

最后的想法

  • 无法理解为什么Google拥有数十亿美元,却不付钱给学生或其他人来记录此API。您必须检查源代码以了解其工作原理,更糟糕的是,它们不会公开数据库列中使用的那些常量,因此我们必须手动编写它们。
  • 对于MMS内的其他类型的数据,您可以应用上面学到的相同思想……这只是知道mime类型的问题。

2
令人关注的内容:// mms-sms / conversations。该URL包含所有线程的列表。但不要单独发送消息(短信或彩信)。因此,没有必要知道短信或彩信,而没有短信。
Maxim

为什么我所有的MMS MIME类型都以application / smil的形式返回?
贾斯汀

2
贾斯汀,因为MMS使用SMIL以幻灯片形式存储在数据库中。
Naba 2012年

3
content://mms-sms/conversations不适用于所有手机(我有Galaxy S6,但不是)。我不得不用content://mms/全部。
KVISH '16

1
除非我弄错了,MessageFormat.format("content://mms/{0}/addr", id);否则仅适用于小于1000的ID。不是MessageFormat.format("content://mms/{0,number,#}/addr", id);吗?
尼克

9

克里斯蒂安的答案是极好的。但是,获取发件人地址的方法对我不起作用。Long.parseLong语句不执行任何操作,除非可能引发异常和新的String(...)?。

在我的设备上,光标计数为2或更多。第一个通常具有137的“类型”,其他通常具有151的“类型”。我无法找到记录在哪里,但是可以推论137是“从”而151则是“到”。因此,如果我按原样运行该方法,则不会得到异常,它会返回最后一行,该行是收件人,并且在许多情况下只是其中的几行。

同样,由于所有行都具有相同的msg_id,因此不必进行选择。但是,它没有伤害。

这是我获取发件人地址的有效方法:

public static String getMMSAddress(Context context, String id) {
    String addrSelection = "type=137 AND msg_id=" + id;
    String uriStr = MessageFormat.format("content://mms/{0}/addr", id);
    Uri uriAddress = Uri.parse(uriStr);
    String[] columns = { "address" };
    Cursor cursor = context.getContentResolver().query(uriAddress, columns,
            addrSelection, null, null);
    String address = "";
    String val;
    if (cursor.moveToFirst()) {
        do {
            val = cursor.getString(cursor.getColumnIndex("address"));
            if (val != null) {
                address = val;
                // Use the first one found if more than one
                break;
            }
        } while (cursor.moveToNext());
    }
    if (cursor != null) {
        cursor.close();
    }
    // return address.replaceAll("[^0-9]", "");
    return address;
}

我不在乎是否全部为数字,但是如果需要的话,我提供了一种消除数字以外的所有内容的方法。也可以轻松修改它以返回所有收件人。

我认为这对他有用。如果异常发生在第一行,它看起来会给出正确的答案。


1
我不会尝试在contact_id字段上调用parseLong;将其视为字符串。实际上,就电子邮件到mms网关而言,很有可能是电子邮件地址或其他内容。
Edward Falk 2012年

4
为了阐明type常量,它们来自以下PduHeaders类:0x97/ 151是PduHeaders.TO0x89/ 137是PduHeaders.FROM。供参考的其他有效值为:0x81/ 129是PduHeaders.BCC0x82/ 130是PduHeaders.CC。另请参见Telephony.Mms.Addr
zelanix

5

我一直在为此苦苦挣扎;但是,我最终使它起作用,我认为该线程可能会从我的经验中受益。

我可以查询content://mms-sms/conversations/ (Telephony.Threads.CONTENT_URI)并获取该线程中有帮助的描述的地址和部分,但是我发现此URI不会检索仅包含MMS消息的线程-例如,具有两个以上通讯者的线程。

在对AOSP MMS应用程序源进行了一些挖掘之后,我发现它正在使用一个变体Telephony.Threads.CONTENT_URI来生成其对话列表-它在参数“ simple”中添加了值“ true”。当我添加此参数时,我发现提供程序将查询完全不同的表,该表的确包含所有SMS和MMS线程。

该表与常规Telephony.Threads.CONTENT_URI一个完全不同的架构(???); 这是AOSP应用正在使用的预测-

public static final String[] ALL_THREADS_PROJECTION = {
    Threads._ID, Threads.DATE, Threads.MESSAGE_COUNT, Threads.RECIPIENT_IDS,
    Threads.SNIPPET, Threads.SNIPPET_CHARSET, Threads.READ, Threads.ERROR,
    Threads.HAS_ATTACHMENT
};

_ID是线程的ID,因此是Telephony.Sms.CONTENT_URI或Telephony.Mms.CONTENT_URI的ID。

当我发现这个奇怪的细节之后,事情开始变得好多了!但是请注意,“ simple = true”变体中的DATE列不可靠,我不得不改用最新的Sms或Mms消息中的日期。

我可能应该提到的另一件事是,为了获得特定线程的正确消息列表,我必须同时查询Mms和Sms提供程序,然后将结果合并为一个列表,然后按日期对它们进行排序。

我验证了Android 5.x和7.x的行为。

我希望这会有所帮助。


3

为了使它对我有用,我必须进行一些修改。

  1. 当我从mms-sms / conversations内容中检索cursor.getString(cursor.getColumnIndex(“ type”))时,(“ content:// mms-sms / conversations /”)我会测试“ type”字段的值为空。如果变量为null-即

    String otype = c.getString(c.getColumnIndex("type"));
    if(otype != null) {
        //this is an sms - handle it...
    

    该消息是SMS,否则是MMS。对于MMS,您必须测试两种MIME类型,如下所示:

    if (("application/vnd.wap.multipart.related".equalsIgnoreCase(msg_type)
        ||"application/vnd.wap.multipart.mixed".equalsIgnoreCase(msg_type))
        && !id.equalsIgnoreCase(lastMMSID)) {
             //this is a MMS - handle it...
    
  2. 当您使用ContentObserver监视邮件内容的更改时,它将为同一邮件触发多个通知。我使用静态变量(在我的情况下为lastMMSID)来跟踪消息。
  3. 此代码可以很好地检索入站和出站消息的内容。重要的是要遍历“ content:// mms / part /” uri返回的所有记录,以便获得MMS的内容(文本和/或附件)。
  4. 我发现唯一能够很好地区分入站和出站MMS的方法是测试mms-sms / conversations内容的“ m_id”字段的空状态。

    String m_id = c.getString(c.getColumnIndex("m_id"));
    String mDirection = m_id == null? "OUT": "IN";
    

关于如何获取地址字段的最终思考。由于某些原因,不希望使用{“ *”}参数来查询地址内容,但这可以:

final String[] projection = new String[] {"address", "contact_id", "charset", "type"};

如果是出站消息,则要查找的“类型”将为151。对于入站消息,“类型”将为137。功能齐全的代码如下:

private String getANumber(int id) {
    String add = "";
    final String[] projection = new String[] {"address","contact_id","charset","type"};
    final String selection = "type=137 or type=151"; // PduHeaders
    Uri.Builder builder = Uri.parse("content://mms").buildUpon();
    builder.appendPath(String.valueOf(id)).appendPath("addr");

    Cursor cursor = context.getContentResolver().query(
        builder.build(),
        projection,
        selection,
        null, null);

if (cursor.moveToFirst()) {
          do {
              String add = cursor.getString(cursor.getColumnIndex("address"));
              String type: cursor.getString(cursor.getColumnIndex("type"));
          } while(cursor.moveToNext());
      }
      // Outbound messages address type=137 and the value will be 'insert-address-token'
      // Outbound messages address type=151 and the value will be the address
      // Additional checking can be done here to return the correct address.
      return add;
}

对于在这篇文章中出现在我之前的所有勇敢的战士们-我衷心感谢你!


1

上面给出的获取getMMSAddress()的答案不应包含while(cursor.moveToNext());循环。它仅应从游标中的第一个元素中提取地址。由于某种我不知道的原因,该光标具有多个记录。第一个包含发件人的地址。光标中除第一个以外的其他元素包含接收者的地址。因此,代码按原样返回接收方地址而不是发送方地址。

这对于破解MMS的内容非常有帮助。

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.