最近做项目要解析手机中短信,获取短信有两种方式:一种是通过广播另一种就是通过数据库,由于项目需要我选择的是第二种方式,检索数据库来获取短信信息并且进行解析指定内容的短信。
在做任何一个新的功能的时候,我们首先查阅一下相关的知识,然后看看有没有相关的demo,最后建议反过来看Android应用层源码,看看它里面的一些常量的定义,建议使用高版本的api,你不一定能够使用它,但是它会给你指出一些思路,就拿短信这个功能为例:
既然我们要获取短信的信息,那就要只要它的各个组成部分,我们进入到android.jar里面中的android.provider.Telephony,我们可以看到Sms的相关的属性。如果你调用这里面的内容的话,会提示Field requires API level 19 (current min is ),但是这并不妨碍我们去模仿他, 我的需要是要检索收件箱,所以搜索数据库的时候,我只需要搜索收件箱里面的短信,在Telephony中TextBasedSmsColumns接口里面Message type根据需要我选择的是MESSAGE_TYPE_INBOX,接下来我们看一下Sms这个接口:
/** * Contains all text-based SMS messages. */ public static final class Sms implements BaseColumns, TextBasedSmsColumns { /** * Not instantiable. * @hide */ private Sms() { } /** * Used to determine the currently configured default SMS package. * @param context context of the requesting application * @return package name for the default SMS package or null */ public static String getDefaultSmsPackage(Context context) { ComponentName component = SmsApplication.getDefaultSmsApplication(context, false); if (component != null) { return component.getPackageName(); } return null; } /** * Return cursor for table query. * @hide */ public static Cursor query(ContentResolver cr, String[] projection) { return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER); } /** * Return cursor for table query. * @hide */ public static Cursor query(ContentResolver cr, String[] projection, String where, String orderBy) { return cr.query(CONTENT_URI, projection, where, null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy); } /** * The {@code content://} style URL for this table. */ public static final Uri CONTENT_URI = Uri.parse("content://sms"); /** * The default sort order for this table. */ public static final String DEFAULT_SORT_ORDER = "date DESC"; /** * Add an SMS to the given URI. * * @param resolver the content resolver to use * @param uri the URI to add the message to * @param address the address of the sender * @param body the body of the message * @param subject the pseudo-subject of the message * @param date the timestamp for the message * @param read true if the message has been read, false if not * @param deliveryReport true if a delivery report was requested, false if not * @return the URI for the new message * @hide */ public static Uri addMessageToUri(ContentResolver resolver, Uri uri, String address, String body, String subject, Long date, boolean read, boolean deliveryReport) { return addMessageToUri(resolver, uri, address, body, subject, date, read, deliveryReport, -1L); } /** * Add an SMS to the given URI with the specified thread ID. * * @param resolver the content resolver to use * @param uri the URI to add the message to * @param address the address of the sender * @param body the body of the message * @param subject the pseudo-subject of the message * @param date the timestamp for the message * @param read true if the message has been read, false if not * @param deliveryReport true if a delivery report was requested, false if not * @param threadId the thread_id of the message * @return the URI for the new message * @hide */ public static Uri addMessageToUri(ContentResolver resolver, Uri uri, String address, String body, String subject, Long date, boolean read, boolean deliveryReport, long threadId) { ContentValues values = new ContentValues(7); values.put(ADDRESS, address); if (date != null) { values.put(DATE, date); } values.put(READ, read ? Integer.valueOf(1) : Integer.valueOf(0)); values.put(SUBJECT, subject); values.put(BODY, body); if (deliveryReport) { values.put(STATUS, STATUS_PENDING); } if (threadId != -1L) { values.put(THREAD_ID, threadId); } return resolver.insert(uri, values); } /** * Move a message to the given folder. * * @param context the context to use * @param uri the message to move * @param folder the folder to move to * @return true if the operation succeeded * @hide */ public static boolean moveMessageToFolder(Context context, Uri uri, int folder, int error) { if (uri == null) { return false; } boolean markAsUnread = false; boolean markAsRead = false; switch(folder) { case MESSAGE_TYPE_INBOX: case MESSAGE_TYPE_DRAFT: break; case MESSAGE_TYPE_OUTBOX: case MESSAGE_TYPE_SENT: markAsRead = true; break; case MESSAGE_TYPE_FAILED: case MESSAGE_TYPE_QUEUED: markAsUnread = true; break; default: return false; } ContentValues values = new ContentValues(3); values.put(TYPE, folder); if (markAsUnread) { values.put(READ, 0); } else if (markAsRead) { values.put(READ, 1); } values.put(ERROR_CODE, error); return 1 == SqliteWrapper.update(context, context.getContentResolver(), uri, values, null, null); } /** * Returns true iff the folder (message type) identifies an * outgoing message. * @hide */ public static boolean isOutgoingFolder(int messageType) { return (messageType == MESSAGE_TYPE_FAILED) || (messageType == MESSAGE_TYPE_OUTBOX) || (messageType == MESSAGE_TYPE_SENT) || (messageType == MESSAGE_TYPE_QUEUED); }
里面给出了查询功能和添加功能,由于我只需要查询功能,但是里面public static final Uri CONTENT_URI = Uri.parse("content://sms");说明查询的是所有的短信,而我只要查询收件箱里面的,所以上面这就话改成public static final Uri CONTENT_URI = Uri.parse("content://sms/inbox");把Sms和TextBasedSmsColumns接口的内容拷贝出来分别放在两个接口里面命名自己设置,而Sms里面会有一些报错,有些我们引用了系统不开放的方法,把不相关的代码进行删除,整理后的代码如下:
package com.jwzhangjie.smarttv_client.support.sms1; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.provider.BaseColumns; public final class Sms implements BaseColumns, TextBasedSmsColumns{ /** * Not instantiable. * @hide */ private Sms() { } /** * Return cursor for table query. * @hide */ public static Cursor query(ContentResolver cr, String[] projection) { return cr.query(CONTENT_URI, projection, null, null, DEFAULT_SORT_ORDER); } /** * Return cursor for table query. * @hide */ public static Cursor query(ContentResolver cr, String[] projection, String where, String orderBy) { return cr.query(CONTENT_URI, projection, where, null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy); } /** * The {@code content://} style URL for this table. */ // public static final Uri CONTENT_URI = Uri.parse("content://sms"); public static final Uri CONTENT_URI = Uri.parse("content://sms/inbox"); /** * The default sort order for this table. */ public static final String DEFAULT_SORT_ORDER = "date DESC"; /** * Add an SMS to the given URI. * * @param resolver the content resolver to use * @param uri the URI to add the message to * @param address the address of the sender * @param body the body of the message * @param subject the pseudo-subject of the message * @param date the timestamp for the message * @param read true if the message has been read, false if not * @param deliveryReport true if a delivery report was requested, false if not * @return the URI for the new message * @hide */ public static Uri addMessageToUri(ContentResolver resolver, Uri uri, String address, String body, String subject, Long date, boolean read, boolean deliveryReport) { return addMessageToUri(resolver, uri, address, body, subject, date, read, deliveryReport, -1L); } /** * Add an SMS to the given URI with the specified thread ID. * * @param resolver the content resolver to use * @param uri the URI to add the message to * @param address the address of the sender * @param body the body of the message * @param subject the pseudo-subject of the message * @param date the timestamp for the message * @param read true if the message has been read, false if not * @param deliveryReport true if a delivery report was requested, false if not * @param threadId the thread_id of the message * @return the URI for the new message * @hide */ public static Uri addMessageToUri(ContentResolver resolver, Uri uri, String address, String body, String subject, Long date, boolean read, boolean deliveryReport, long threadId) { ContentValues values = new ContentValues(7); values.put(ADDRESS, address); if (date != null) { values.put(DATE, date); } values.put(READ, read ? Integer.valueOf(1) : Integer.valueOf(0)); values.put(SUBJECT, subject); values.put(BODY, body); if (deliveryReport) { values.put(STATUS, STATUS_PENDING); } if (threadId != -1L) { values.put(THREAD_ID, threadId); } return resolver.insert(uri, values); } /** * Returns true iff the folder (message type) identifies an * outgoing message. * @hide */ public static boolean isOutgoingFolder(int messageType) { return (messageType == MESSAGE_TYPE_FAILED) || (messageType == MESSAGE_TYPE_OUTBOX) || (messageType == MESSAGE_TYPE_SENT) || (messageType == MESSAGE_TYPE_QUEUED); } }
上面的代码不会有错误,查询功能有了之后,我们还要写一个保存查询出来的内容的类,根据自己的项目编写,例如:
package com.jwzhangjie.smarttv_client.support.sms1; /** * 数据库中sms相关的字段如下: _id 一个自增字段,从1开始 thread_id 序号,同一发信人的id相同 address 发件人手机号码 person 联系人列表里的序号,陌生人为null date 发件日期 protocol 协议,分为: 0 SMS_RPOTO, 1 MMS_PROTO read 是否阅读 0未读, 1已读 status 状态 -1接收,0 complete, 64 pending, 128 failed type ALL = 0; INBOX = 1; SENT = 2; DRAFT = 3; OUTBOX = 4; FAILED = 5; QUEUED = 6; body 短信内容 service_center 短信服务中心号码编号 subject 短信的主题 reply_path_present TP-Reply-Path locked */ import android.os.Parcel; import android.os.Parcelable; /** * 保存短息信息 * @author jwzhangjie * */ public class SMSInfo implements Parcelable{ @Override public int describeContents() { return 0; } /** * 短信序号 */ private long id; /** * 协议 0:SMS_PROTO 短信 1:MMS_PROTO彩信 */ private int protocol; /** * 短信内容 */ private String smsBody; /** * 发送短息的电话号码 */ private String smsPhoneNum; /** * 发送短信的日期和时间 */ private String smsDateTime; /** * 发送短信人 */ private String smsSender; /** * 短信类型:1是接收到的 2是已发送的 */ private int smsType; public SMSInfo(){ } private SMSInfo(Parcel source){ readFromParcel(source); } public void readFromParcel(Parcel source){ id = source.readLong(); protocol = source.readInt(); smsBody = source.readString(); smsPhoneNum = source.readString(); smsDateTime = source.readString(); smsSender = source.readString(); smsType = source.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeLong(id); dest.writeInt(protocol); dest.writeString(smsBody); dest.writeString(smsPhoneNum); dest.writeString(smsDateTime); dest.writeString(smsSender); dest.writeInt(smsType); } public static Creator<SMSInfo> CREATOR = new Creator<SMSInfo>() { @Override public SMSInfo createFromParcel(Parcel source) { return new SMSInfo(source); } @Override public SMSInfo[] newArray(int size) { return new SMSInfo[size]; } }; @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("id :"+id+" 协议:"+protocol+" 内容:"+smsBody); return builder.toString(); } public long getId() { return id; } public void setId(long id) { this.id = id; } public int getProtocol() { return protocol; } public void setProtocol(int protocol) { this.protocol = protocol; } public String getSmsBody() { return smsBody; } public void setSmsBody(String smsBody) { this.smsBody = smsBody; } public String getSmsPhoneNum() { return smsPhoneNum; } public void setSmsPhoneNum(String smsPhoneNum) { this.smsPhoneNum = smsPhoneNum; } public String getSmsDateTime() { return smsDateTime; } public void setSmsDateTime(String smsDateTime) { this.smsDateTime = smsDateTime; } public String getSmsSender() { return smsSender; } public void setSmsSender(String smsSender) { this.smsSender = smsSender; } public int getSmsType() { return smsType; } public void setSmsType(int smsType) { this.smsType = smsType; } }
接下来就是获取数据库内容,我们先看一下查询数据库的方法:
/** * Return cursor for table query. * @hide */ public static Cursor query(ContentResolver cr, String[] projection, String where, String orderBy) { return cr.query(CONTENT_URI, projection, where, null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy); }
CONTENT_URI:就是我们上面定义的public static final Uri CONTENT_URI = Uri.parse("content://sms/inbox");
projection:这个可以是null,那就是把所有的列都获取出来,不过不必,你需要什么就获取什么,不然既浪费时间又浪费内存
where:查询条件
orderBy:排序
package com.jwzhangjie.smarttv_client.support.sms1; import java.util.ArrayList; import java.util.List; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; public class GetSMSInfo { private ContentResolver mResolver; private Context context; private List<SMSInfo> infos; private static final String[] PROJECTION = new String[]{ Sms._ID,//0:作为一个标志位,记录最后一个,作为下一次扫描的第一个 Sms.TYPE,//1:收还是发 Sms.ADDRESS,//2手机号 Sms.BODY,//3内容 Sms.DATE,//4日期 Sms.PROTOCOL//5协议 }; public static final int COLUMN_INDEX_ID = 0; public static final int COLUMN_INDEX_TYPE = 1; public static final int COLUMN_INDEX_PHONE = 2; public static final int COLUMN_INDEX_BODY = 3; public static final int COLUMN_INDEX_DATE = 4; public static final int COLUMN_INDEX_PROTOCOL = 5; public GetSMSInfo(Context context){ this.infos = new ArrayList<SMSInfo>(); this.mResolver = context.getContentResolver(); this.context = context; } /** * 获取短信各种信息 */ public List<SMSInfo> getSmsInfo(){ //获取上一次检索的最后一个ID long last_sms_id = UPSharedPreferences.getLong(context, "card", "last_sms_id", 0); Cursor cursor = Sms.query(mResolver, PROJECTION, "_id >" + last_sms_id, "_id"); int protocol; String body, date; if (cursor != null) { while (cursor.moveToNext()) { last_sms_id = cursor.getLong(COLUMN_INDEX_ID); body = cursor.getString(COLUMN_INDEX_BODY); date = cursor.getString(COLUMN_INDEX_DATE); protocol = cursor.getInt(COLUMN_INDEX_PROTOCOL); if (protocol == 0) {//收信息 SMSInfo info = new SMSInfo(); info.setId(last_sms_id); info.setSmsBody(body); info.setSmsDateTime(date); infos.add(info); } } cursor.close(); } //保存当前最新的ID UPSharedPreferences.setLong(context, "card", "last_sms_id", last_sms_id); return infos; } }读取得所有符合条件的短信都放在List<SMSInfo>里面,然后我们最后解析条件一个一个解析,这个涉及一些私人信息,就不显示效果了。