【问题标题】:How to Read MMS Data in Android?如何在 Android 中读取彩信数据?
【发布时间】:2011-03-02 00:51:12
【问题描述】:

我想读取 MMS 数据我在 mmssms.db 中看到了存储 mms 条目的零件表;我正在使用光标,我想知道合适的URI;我正在使用“content://mms-sms/conversations”和“地址”(发送到)、“文本”或“主题”的列名以及图像的“数据”列名。

我已经看到mmssms.db 的架构和它们的部分表列。

【问题讨论】:

  • mmssms.db 数据库是固件的一部分,Android 应用程序无法访问。 content://mms-sms/conversations 内容提供程序不是 SDK 的一部分,Android 应用程序不应访问。
  • 我在这里做类似的事情! stackoverflow.com/questions/11556633/…

标签: android mms


【解决方案1】:

很难找到这方面的文档,所以我将在这里收集我找到的所有信息。如果您赶时间或只是不喜欢阅读,请跳至如何从 SMS 中获取数据部分。

content://mms-sms/conversations

这是 Mms and SMS provider... 的 URI,它允许我们同时查询 MMS 和 SMS 数据库,并将它们混合在一个线程中(称为 conversations) .

为什么 URI 很重要?嗯,这是获取 MMS 和 SMS 消息的标准方式;例如,当您收到一条短信并单击通知栏时,它会发送一个广播意图,如下所示: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 无需解释。
  • thread_id 是对话的 ID
  • body此对话的最后一条短信内容。如果是彩信,即使有文字部分,也是null

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

如何区分短信和彩信

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

一个虚拟列, MmsSms.TYPE_DISCRIMINATOR_COLUMN,可能 被要求在投影中 询问。其值为“mms”或 “短信”,取决于是否 该行表示的消息是 彩信或短信, 分别。

我认为它指的是this variable...但是我无法让它工作。如果你有请告诉我如何或编辑这篇文章。

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

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"));

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

彩信有点不同。它们可以用不同的部分(文本、音频、图像等)构建;所以这里将看看如何分别检索每种数据。

假设我们在mmsId 变量中有彩信ID。我们可以使用content://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();
}

如何从彩信中获取图片

这与获取文本部分相同...唯一的区别是您将寻找不同的 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;
}

最后的想法

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

【讨论】:

  • 关注内容://mms-sms/conversations。此 url 包含所有线程的列表。但不是单独的消息(短信或彩信)。因此,了解短信或彩信是没有意义的,而它们都不是。
  • 我所有的 MMS mime 类型都以 application/smil 的形式返回是否有原因?
  • 贾斯汀,因为彩信是使用 SMIL 以幻灯片形式存储在数据库中的。
  • content://mms-sms/conversations 不适用于所有手机(我有 Galaxy S6,但它没有)。我必须使用content://mms/ 来完成所有这些操作。
  • 除非我弄错了,MessageFormat.format("content://mms/{0}/addr", id); 仅适用于小于 1,000 的 ID。不应该是MessageFormat.format("content://mms/{0,number,#}/addr", id);吗?
【解决方案2】:

Christian 的回答非常好。但是,获取发件人地址的方法对我不起作用。 Long.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;
}

我并不关心它是否全是数字,但如果需要,我提供了一种方法来消除除数字之外的所有内容作为注释。也可以轻松修改它以返回所有收件人。

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

【讨论】:

  • 我不会尝试在 contact_id 字段上调用 ​​parseLong;把它当作一个字符串。事实上,在电子邮件到彩信网关的情况下,它很可能是电子邮件地址或其他东西。
  • 为了澄清type 常量,它们来自PduHeaders 类:0x97 / 151 是PduHeaders.TO0x89 / 137 是PduHeaders.FROM。其他可供参考的有效值是:0x81 / 129 是 PduHeaders.BCC0x82 / 130 是 PduHeaders.CC。另请参阅Telephony.Mms.Addr
【解决方案3】:

我一直在为此苦苦挣扎;然而,我终于让它工作了,我认为这个线程可能会从我的经验中受益。

我可以在 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 上验证了行为。

我希望这会有所帮助。

【讨论】:

    【解决方案4】:

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

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

      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”字段的 null 状态。

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

    感谢在这篇文章中走在我前面的所有勇敢的战士——我从心底里感谢你们!

    【讨论】:

      【解决方案5】:

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

      这对于破解彩信内容非常有帮助。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-04-21
        • 2013-02-24
        • 2014-03-11
        • 1970-01-01
        相关资源
        最近更新 更多