如何在 Android 中读取彩信数据?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3012287/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to Read MMS Data in Android?
提问by user321373
I want to read MMS data I have seen the part table in the mmssms.db
where the mms entries are stored; I am using a cursor and I want to know the appropriate URI
; I am using "content://mms-sms/conversations" and the Column namesof "Address"(Sent to), "Text" or "Subject" and "Data" column name of image.
我想读取彩信数据我在mmssms.db
存储彩信条目的地方看到了部分表;我正在使用游标,我想知道合适的URI
;我使用“的内容://彩信,短信/通话”和列名“地址”(发送到),“文本”或“主题”和形象的“数据”列名的。
I have seen the schema of mmssms.db
and Their Column of part Table.
我已经看到了mmssms.db
部分表的架构和他们的列。
回答by Cristian
It's kind of difficult to find documentation about this, so I will collect here all information I have found. If you are in a rush or just don't like to read, jump to the How to get data from a SMSsection.
找到这方面的文档有点困难,所以我将在这里收集我找到的所有信息。如果您赶时间或只是不喜欢阅读,请跳至如何从 SMS 获取数据部分。
content://mms-sms/conversations
content://mms-sms/conversations
This is the URI of the Mms and SMS provider... which allows us to query the MMS and SMS databases at the same time, and mix them in a single thread (which are called conversations).
这是Mms 和 SMS 提供商的 URI ...它允许我们同时查询 MMS 和 SMS 数据库,并将它们混合在一个线程中(称为对话)。
Why is the URI important? Well, that's the standard way of getting MMS and SMS messages; for instance, when you receive a SMS and click on the notification bar, it will send a broadcast intent like this: content://mms-sms/conversations/XXX
, where XXX
is the id of the conversation.
为什么 URI 很重要?嗯,这是获取彩信和短信的标准方式;例如,当你收到一条短信并点击通知栏时,它会发送一个这样的广播意图:content://mms-sms/conversations/XXX
,XXX
对话的 id在哪里。
Get a list of all conversations
获取所有对话的列表
The only thing you have to do is to query the content://mms-sms/conversations
Uri:
您唯一需要做的就是查询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);
Note:usually, when you call query
and want to return all columns you can pass null
as the projection
parameter. However, you cannot do that with this provider, so that's why I'm using *
.
注意:通常,当您调用query
并想要返回所有列时,您可以将其null
作为projection
参数传递。但是,您不能使用此提供程序执行此操作,因此这就是我使用*
.
Now you can loop through the Cursor
as usual. These are the more important columns you would want to use:
现在你可以Cursor
像往常一样循环。这些是您想要使用的更重要的列:
_id
is the ID of the message. Captain obvious to the rescue?Not really. This ID can be used to retrieve detailed information using eithercontent://sms
orcontent://mms
.date
no explanation needed.thread_id
is the ID of the conversationbody
The content of the last SMS on this conversation. If it's an MMS, even if it has a text part, this will benull
.
_id
是消息的ID。船长显然是救命的?并不真地。此 ID 可用于使用content://sms
或检索详细信息content://mms
。date
不需要解释。thread_id
是对话的IDbody
此对话的最后一条 SMS 的内容。如果是彩信,即使它有文本部分,也将是null
.
Note:if you query content://mms-sms/conversations
it will return a list of different conversations whose _id
is the last SMS or MMS in each conversation. If you query content://mms-sms/conversations/xxx
it will return each SMS and/or MMS on the conversation whose ID is xxx
.
注意:如果您查询content://mms-sms/conversations
,它将返回一个不同对话的列表,它们_id
是每个对话中的最后一条短信或彩信。如果您查询content://mms-sms/conversations/xxx
,它将返回 ID 为 的对话中的每条短信和/或彩信xxx
。
How to differentiate between SMS and MMS
如何区分短信和彩信
Usually, you will want to know which type of message you are handling. Documentation says:
通常,您会想知道您正在处理哪种类型的消息。文档说:
A virtual column,
MmsSms.TYPE_DISCRIMINATOR_COLUMN
, may be requested in the projection for a query. Its value is either "mms" or "sms", depending on whether the message represented by the row is an MMS message or an SMS message, respectively.
MmsSms.TYPE_DISCRIMINATOR_COLUMN
可以在查询的投影中请求虚拟列 。它的值是“mms”或“sms”,分别取决于该行表示的消息是彩信消息还是短信。
I think it's referring to this variable... however I have not been able to make it work. If you have please tell me how or edit this post.
我认为它指的是这个变量......但是我无法让它工作。如果你有请告诉我如何或编辑这篇文章。
So far this is what I have done and it seems to work but there must be better ways:
到目前为止,这就是我所做的,它似乎有效,但必须有更好的方法:
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());
}
How to get data from a SMS
如何从短信中获取数据
So you have the ID of the SMS, then the only thing you have to do is:
所以你有了短信的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"));
How to get data from a MMS data?
如何从彩信数据中获取数据?
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.
MMS 有点不同。它们可以用不同的部分(文本、音频、图像等)构建;所以这里将看到如何分别检索每种数据。
So let's guess we have the MMS id in the mmsId
variable. We can get detailed information about this MMS by using the content://mms/
provider:
所以让我们猜测我们在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);
However, the only interesting column is read
which is 1
if the message has already been read.
然而,唯一感兴趣的列read
是1
如果该消息已经被阅读。
How to get text content from MMS
如何从彩信中获取文本内容
Here we have to use content://mms/part
... for instance:
这里我们必须使用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());
}
It could contain different parts of text... but usually it'd be only one. So if you want to remove the loop it will work most of the times. This is how the getMmsText
method looks like:
它可以包含文本的不同部分……但通常只有一个。因此,如果您想删除循环,它在大多数情况下都会起作用。这是该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();
}
How to get image from MMS
如何从彩信获取图像
It's the same than getting the text part... the only difference is that you will be looking for a different mime-type:
这与获取文本部分相同……唯一的区别是您将寻找不同的 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());
}
This is how the getMmsImage
method looks like:
这是该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;
}
How to get the sender address
如何获取发件人地址
You will need to use the content://mms/xxx/addr
provider, where xxx
is the id of the MMS:
您将需要使用content://mms/xxx/addr
提供程序,其中xxx
MMS 的 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;
}
Final thoughts
最后的想法
- Can't understand why Google, with those thousands of millions of dollars, don't pay a student or someone else to document this API. You have to check the source code to know how it works and, which is worse, they don't make public those constants used in the columns of the database, so we have to write them manually.
- For other kind of data inside an MMS you can apply the same idea learned above... it's just a matter of knowing the mime-type.
- 不明白为什么谷歌拥有数以百万计的美元,不付钱给学生或其他人来记录这个 API。您必须检查源代码以了解它是如何工作的,更糟糕的是,它们不会公开数据库列中使用的那些常量,因此我们必须手动编写它们。
- 对于 MMS 中的其他类型的数据,您可以应用上面学到的相同想法……这只是了解 mime 类型的问题。
回答by Kenneth Evans
The answer by Christian is excellent. However, the method for getting the sender's address did not work for me. The Long.parseLong statement doesn't do anything except possibly throw an exception and new String(...) ?.
克里斯蒂安的回答非常好。但是,获取发件人地址的方法对我不起作用。Long.parseLong 语句除了可能抛出异常和 new String(...) 之外什么都不做。
On my device the cursor count is 2 or more. The first typically has a "type" of 137 and the others have a "type" of 151. I cannot find where this is documented, but one can deduce 137 is "from" and 151 is "to". Thus, if I run the method as is, I do not get an exception, and it returns the last row, which is a recipient and only one of several in many cases.
在我的设备上,光标计数为 2 或更多。第一个通常的“类型”为 137,其他的“类型”为 151。我找不到记录在何处,但可以推断 137 是“来自”,而 151 是“到”。因此,如果我按原样运行该方法,则不会出现异常,它会返回最后一行,该行是接收者,并且在许多情况下只是多个接收者之一。
Also AFAICT the selection is not necessary as all the rows have the same msg_id. However, it doesn't hurt.
此外 AFAICT 选择不是必需的,因为所有行都具有相同的 msg_id。然而,它并不痛。
This is what works for me to get the sender's address:
这是我获取发件人地址的方法:
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.
我并不关心它是否都是数字,但是如果需要,我包含了一种消除除数字以外的所有内容的方法作为注释。它也可以轻松修改以返回所有收件人。
I assume it worked for him. It looks like it would give the right answer if the exception occurred on the first row.
我认为这对他有用。如果异常发生在第一行,看起来它会给出正确的答案。
回答by Jason Proctor
I've just been struggling with this; however, I finally got it to work and I thought this thread might benefit from my experience.
我一直在为此苦苦挣扎;然而,我终于让它工作了,我认为这个线程可能会从我的经验中受益。
I could query on content://mms-sms/conversations/ (Telephony.Threads.CONTENT_URI)
and get addresses and parts as helpfully described in the thread, but I found that this URI would not retrieve threads that onlyhad MMS messages in them - for example, threads with more than two correspondents.
我可以查询content://mms-sms/conversations/ (Telephony.Threads.CONTENT_URI)
并获取线程中有用描述的地址和部分,但我发现此 URI 不会检索其中仅包含MMS 消息的线程 - 例如,具有两个以上通信者的线程。
After doing some digging in the AOSP MMS app source, I found that it was using a variant on Telephony.Threads.CONTENT_URI
to generate its conversation list - it was adding the parameter "simple" with the value "true". when I added this parameter, I found that the provider would query a completely different table, which did indeed have all the SMS and MMS threads in it.
在对 AOSP MMS 应用程序源进行了一些挖掘之后,我发现它正在使用一个变体Telephony.Threads.CONTENT_URI
来生成其对话列表 - 它添加了值为“true”的参数“simple”。当我添加这个参数时,我发现提供者会查询一个完全不同的表,其中确实包含所有 SMS 和 MMS 线程。
This table has a completely different schema from the regular Telephony.Threads.CONTENT_URI one (???); this is the projection that the AOSP app is using --
该表与常规 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
};
The _ID here is the ID of the thread - so an ID into Telephony.Sms.CONTENT_URI or Telephony.Mms.CONTENT_URI.
这里的 _ID 是线程的 ID - 因此是 Telephony.Sms.CONTENT_URI 或 Telephony.Mms.CONTENT_URI 中的 ID。
After I discovered this bizarre detail, things started to work a lot better! Note however that the DATE column in the "simple=true" variant is not reliable, i had to use the date from the most recent Sms or Mms message instead.
在我发现这个奇怪的细节后,事情开始变得更好了!但是请注意,“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.
我可能应该提到的另一件事是,为了获得特定线程的正确消息列表,我必须同时查询 Mms 和 Sms 提供程序,然后将结果合并到一个列表中,然后按日期对它们进行排序。
I verified behaviour on Android 5.x and 7.x.
我在 Android 5.x 和 7.x 上验证了行为。
I hope this helps a bit more.
我希望这会有所帮助。
回答by Gustav
I had to make some modifications in order to get this to work for me.
我必须进行一些修改才能使其对我有用。
When I retrieve the cursor.getString(cursor.getColumnIndex("type"))from the mms-sms/conversations content, ("content://mms-sms/conversations/") I test the value of the "type" field for null. If the variable is null - i.e.
String otype = c.getString(c.getColumnIndex("type")); if(otype != null) { //this is an sms - handle it...
the message is an SMS, else it is an MMS. For MMS's you have to test for both mime types as follows:-
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...
- 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.
- 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.
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";
当我从 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...
消息是短信,否则是彩信。对于彩信,您必须按以下方式测试两种 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...
- 当您使用 ContentObserver 监视消息内容的更改时,它会针对同一消息触发多个通知。我使用一个静态变量——在我的例子中是 lastMMSID——来跟踪消息。
- 此代码可以很好地检索入站和出站消息的内容。重要的是要遍历“content://mms/part/”uri 返回的所有记录,以便访问 MMS 的内容(文本和/或附件)。
我发现可以很好地区分入站和出站 MMS 的唯一方法是测试 mms-sms/conversations 内容的“m_id”字段的空状态。
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"};
If it is an outbound message, the "type" to look for will be 151. For an inbound message, the "type" will be 137. A fully functional piece of code will look something like this:-
如果是出站消息,则要查找的“类型”将为 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!
感谢所有在这篇文章中走在我前面的勇敢的战士——我从心底里感谢你!
回答by Hassan Nabil
The answer given above for getting the getMMSAddress() should not contain the loop while (cursor.moveToNext());. It should only extract the address from the first element in the cursor. For some reason that is unknown to me, this cursor has more than one record. The first one contains the Sender's address. The other elements of the cursor beyond the first one, contain the receiver's address. Thus the code as is return the receivers address and not the sender address.
上面给出的获取 getMMSAddress() 的答案不应包含 while (cursor.moveToNext()); 循环。它应该只从光标的第一个元素中提取地址。由于某种我不知道的原因,这个游标有不止一条记录。第一个包含发件人的地址。光标的第一个元素之外的其他元素包含接收者的地址。因此,代码原样返回接收者地址而不是发送者地址。
This has been very helpful for cracking open the contents of an MMS.
这对于破解彩信的内容非常有帮助。