How to Read MMS Data in Android?

我想读取的 MMS 数据,我已经看到了部分表在 mmssms.db的 MMS 条目存储; 我使用一个光标,我想知道适当的 URI; 我使用“内容://MMS-sms/对话”和 列名的“地址”(发送到) ,“文本”或“主题”和“数据”栏名称的图像。

我已经看到了 mmssms.db的模式及其部分表的列。

92388 次浏览

找到有关这方面的文档有点困难,所以我将在这里收集我找到的所有信息。如果你在赶时间或只是不喜欢阅读,跳到 如何从短讯取得资料部分。

内容://mms-sms/对话

这是 多媒体短信服务供应商的 URI... 它允许我们同时查询 MMS 和 SMS 数据库,并将它们混合在一个线程中(称为 谈话)。

为什么 URI 很重要?嗯,这是获取彩信和短信的标准方式; 例如,当你收到一条短信并点击通知栏时,它会发送一个广播意图,像这样: content://mms-sms/conversations/XXX,其中 XXX是对话的 id。

列出所有对话

你唯一需要做的就是查询 content://mms-sms/conversations Uri:

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。船长显然是来救我们的?没有。此 ID 可用于使用 content://smscontent://mms检索详细信息。
  • date no explanation needed.
  • thread_id是对话的 ID
  • 本次对话的最后一条短信内容。如果它是一个彩信,即使它有一个文本部分,这将是 null

注意: 如果你查询 content://mms-sms/conversations,它会返回一个不同会话的列表,其中 _id是每个会话中的最后一条短信或彩信。如果你查询 content://mms-sms/conversations/xxx,它会返回每个短信和/或彩信对话的 ID 是 xxx

如何区分短信和彩信

通常,你会想知道你正在处理的是哪种类型的信息:

虚拟专栏, MmsSms.TYPE_DISCRIMINATOR_COLUMN,May 在投影中要求 它的值是“ mms”或 「短讯」 ,视乎 行表示的消息是 彩信或短信, 分别。

我认为这是指 这个变量... 但我一直没有能够使它工作。如果你有,请告诉我如何或编辑这篇文章。

到目前为止,这就是我所做的,而且似乎有效,但必须有更好的方法:

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());
}

如何从短讯取得资料

所以你有了短信的 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"));

如何从彩信数据中获取数据?

MMSs are a little bit different. They can be built with different parts (text, audio, images, etc.); so here will see how to retrieve each kind of data separately.

那么让我们假设在 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);

但是,如果消息已经被读取,那么唯一有趣的列是 read,即 1

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

这里我们必须使用 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();
}

如何从彩信中获取图像

It's the same than getting the text part... the only difference is that you will be looking for a different mime-type:

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提供商,其中 xxx是彩信的 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;
}

最后的想法

  • 不能理解为什么谷歌,那些数以千万计的美元,不付钱给学生或其他人来记录这个 API。您必须检查源代码以了解它是如何工作的,更糟糕的是,它们没有公开数据库列中使用的常量,因此我们必须手动编写它们。
  • 对于 MMS 中的其他类型的数据,你可以应用上面学到的同样的想法... ... 这只是一个知道哑剧类型的问题。

克里斯蒂安的回答非常好。然而,获取发件人地址的方法对我不起作用。ParseLong 语句除了可能抛出异常和 new String (...)之外什么也不做?.

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

另外,AFAICT 选择是不必要的,因为所有行都有相同的 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;
}

I didn't care about whether it is all numeric, but I included a way to eliminate everything but numerals as a comment if that is desired. It can easily be modified to return all the recipients, as well.

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

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

这对于打开彩信的内容非常有帮助。

我必须做一些修改才能让它对我有用。

  1. 当我从 mms-sms/versations 内容(“ content://mms-sms/versations/”)检索 GetString (cursor.getColumnIndex (“ type”))时,我测试“ type”字段的值是否为 null。如果变量为 null-ie。

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

    信息是短信,否则就是彩信。对于彩信,你必须测试两种哑剧类型如下:-

    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. When you use a ContentObserver to monitor the message content for changes, it fires several notifications for the same message. I use a static variable - in my case lastMMSID - to keep track of the message.
  3. This code works well to retrieve the content of both Inbound and Outbound messages. It is important to iterate through all the records that are returned by the "content://mms/part/" uri in order to get to the content - text and/or attachments - of the MMS.
  4. The only way that I could find that works pretty well to differentiate between inbound and outbound MMS's, is to test the null status of the "m_id" field of the mms-sms/conversations content.

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

A final thought on how to get the Address Field. For some reason the Address Content does not like to be queried with a {" * "} parameter, but this works:-

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;
}

To all the brave warriors who have gone before me in this post - I thank thee from the bottom of my heart!

我一直在努力解决这个问题; 然而,我最终让它起作用了,我想这个主题可能会从我的经验中受益。

我可以查询 content://mms-sms/conversations/ (Telephony.Threads.CONTENT_URI)并得到线程中描述的地址和部分,但是我发现这个 URI 不会检索 只有中包含 MMS 消息的线程——例如,有两个以上通讯对象的线程。

在深入研究了 AOSP MMS 应用程序源代码之后,我发现它使用了 Telephony.Threads.CONTENT_URI上的一个变体来生成它的对话列表——它添加了参数“ simple”和值“ true”。当我添加这个参数时,我发现提供程序将查询一个完全不同的表,其中确实包含所有 SMS 和 MMS 线程。

此表具有与常规 Telephony 完全不同的模式。Treads.CONTENT _ URI one (? ? ?); 这是 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 消息中的日期。

Another thing I should probably mention is that in order to get a proper list of messages for a particular thread, I had to query on both the Mms and Sms providers, then combine the results into one list, then sort them by date.

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

我希望这个能帮到你。