我想读取 MMS 数据,我在mmssms.db
存储 mms 条目的位置看到了零件表;我正在使用光标,我想知道合适的URI
;我正在使用“content://mms-sms/conversations”和“地址”(发送到)、“文本”或“主题”的列名以及图像的“数据”列名。
我已经看到了mmssms.db
部分表的架构和他们的列。
找到这方面的文档有点困难,所以我将在这里收集我找到的所有信息。如果您赶时间或只是不喜欢阅读,请跳至如何从 SMS 中获取数据部分。
这是Mms 和 SMS 提供者的 URI ... 它允许我们同时查询 MMS 和 SMS 数据库,并将它们混合在一个线程中(称为会话)。
为什么 URI 很重要?嗯,这是获取 MMS 和 SMS 消息的标准方式;例如,当您收到一条短信并单击通知栏时,它会发送如下广播意图: ,对话的 id 在content://mms-sms/conversations/XXX
哪里。XXX
您唯一需要做的就是查询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://sms
或检索详细信息content://mms
。date
无需解释。thread_id
是对话的 IDbody
此对话中最后一条短信的内容。如果是彩信,即使它有文字部分,也会是null
.注意:如果您查询content://mms-sms/conversations
,它将返回一个不同对话的列表,该列表_id
是每个对话中的最后一条短信或彩信。如果您查询content://mms-sms/conversations/xxx
,它将返回 ID 为 的对话中的每条短信和/或彩信xxx
。
通常,您会想知道您正在处理哪种类型的消息。文档说:
MmsSms.TYPE_DISCRIMINATOR_COLUMN
可以在查询的投影中请求虚拟列 。其值为“mms”或“sms”,具体取决于该行表示的消息是彩信消息还是 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());
}
所以你有了短信的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
变量中有 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();
}
这与获取文本部分相同......唯一的区别是您将寻找不同的 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;
}
克里斯蒂安的回答非常好。但是,获取发件人地址的方法对我不起作用。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;
}
我不在乎它是否全是数字,但如果需要,我提供了一种方法来消除除数字之外的所有内容作为注释。它也可以很容易地修改为返回所有收件人。
我认为这对他有用。如果异常发生在第一行,它看起来会给出正确的答案。
我一直在为此苦苦挣扎;然而,我终于让它工作了,我认为这个线程可能会从我的经验中受益。
我可以查询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 上验证了行为。
我希望这会有所帮助。
我必须进行一些修改才能使它对我有用。
当我从 mms-sms/conversations 内容中检索cursor.getString(cursor.getColumnIndex("type"))时, ("content://mms-sms/conversations/") 我测试了“type”字段的值为空。如果变量为空 - 即
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...
我发现可以很好地区分入站和出站 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;
}
对于在这篇文章中走在我前面的所有勇敢的战士——我从心底里感谢你们!
上面给出的获取 getMMSAddress() 的答案不应包含循环 while (cursor.moveToNext());。它应该只从光标中的第一个元素中提取地址。出于某种我不知道的原因,这个游标有多个记录。第一个包含发件人的地址。光标的第一个元素之外的其他元素包含接收者的地址。因此,代码原样返回接收者地址而不是发送者地址。
这对于破解彩信的内容非常有帮助。