如何在Android中读取彩信数据?

时间:2010-06-10 07:00:29

标签: android mms

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

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

5 个答案:

答案 0 :(得分:264)

很难找到关于此的文档,因此我将在此收集我找到的所有信息。如果您匆忙或只是不喜欢阅读,请跳至如何从短信获取数据部分。

内容:// MMS-SMS /会话

这是Mms and SMS provider的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无需解释。
  • thread_id是对话的ID
  • body此对话中最后一条短信的内容。如果是彩信,即使它有文字部分,也会是null

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

如何区分短信和彩信

通常,您需要知道要处理的邮件类型。文档说:

  

虚拟列,   MmsSms.TYPE_DISCRIMINATOR_COLUMN,可能   在预测中要求a   查询。它的值是“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"));

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

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

因此我们猜测我们在mmsId变量中有MMS 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类型:

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类型的问题。

答案 1 :(得分:7)

基督徒的回答非常好。但是,获取发件人地址的方法对我不起作用。 Long.parseLong语句除了可能抛出异常和新的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;
}

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

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

答案 2 :(得分:4)

我一直在努力解决这个问题;但是,我终于开始工作,我认为这个主题可能会从我的经验中受益。

我可以查询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 - 因此ID为Telephony.Sms.CONTENT_URI或Telephony.Mms.CONTENT_URI。

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

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

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

我希望这会有所帮助。

答案 3 :(得分: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...
    

    消息是短信,否则是彩信。对于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 / conversation内容的“m_id”字段的空状态。

    String m_id = c.getString(c.getColumnIndex("m_id"));
    String mDirection = m_id == null? "OUT": "IN";
    
  5. 关于如何获取地址字段的最终想法。出于某种原因,地址内容不希望使用{“*”}参数进行查询,但这样可行: -

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

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

答案 4 :(得分:2)

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

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