Android_通过ContentObserver监听短信数据变化

1.简单介绍

在小米等一些机型,无法接收系统发出的短信广播。

仅仅能通过观察者ContentObserver,去监听短信数据的变化

2.SMS数据介绍

content://sms/inbox        收件箱 
content://sms/sent        已发送 
content://sms/draft        草稿 
content://sms/outbox        发件箱 
content://sms/failed        发送失败 
content://sms/queued        待发送列表

在模拟器上Outbox没有查询到数据,在模拟器上找了老半天也没找到发件箱。非常郁闷。    
数据库中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

检索数据方法非常easy:  
Uri uri = Uri.parse("content://sms/inbox");         
Cursor cur = this.managedQuery(uri, null, null, null, null);         
if (cur.moveToFirst()) {         
    do{     
    for(int j = 0; j < cur.getColumnCount(); j++){     
            info = "name:" + cur.getColumnName(j) + "=" + cur.getString(j); 
            Log.i("====>", info); 
        } 
    }while(cur.moveToNext());      
}

managedQuery终于也要将參数转换为SQL语句向SQLite发送消息,因此參数跟SQL语句非常类似,所以能够在查询字段中增加SQL函数,
比方new String[] projection = new String[]{"count(*) as count"}等等。       
managedQuery中的參数依次为uri。        
查询字段          查询字段数组,也能够将全部须要查询的字段放入一个字符内    
                      比方new projection[]{"_id", "thread_id"}和new projection[]{"_id,thread_id"}是一致的。

   
                      跟SQL一样,字段名不区分大写和小写    
条件                不带Where的SQL 条件字符,假设有參数则用?替代,比方"_id=?

And thread_id = ?

Or type = '1'"    
条件中的參数   參数字符数组。跟上述的条件一一相应    
排序                不带Order by排序字符串,比方_id desc, type    
假设參数为null,SQL中查询字段为“*”,相关的条件为空白

还能够用getContentResolver()获得一个ContentResolver。    
getContentResolver().query()相同返回一个Cursor对象,參数跟managedQuery一致。    
只是用ContentResolver对象去更新、删除和插入一条数据时报SecurityException。

看来没有权限,在Manifest.xml中增加权限: 
<uses-permission android:name="android.permission.WRITE_SMS"></uses-permission> 
然后删除短信: 
this.getContentResolver().delete(Uri.parse("content://sms"), "_id=?", new String[]{"3"}); 
删除成功。
Url中content://sms 替换成content://sms/ 也成功,可是其他url时程序报错,比方content://sms/inbox

看了一下android的源码,sms支持的协议有:
sURLMatcher.addURI("sms", null, SMS_ALL); 
sURLMatcher.addURI("sms", "#", SMS_ALL_ID); 
sURLMatcher.addURI("sms", "inbox", SMS_INBOX); 
sURLMatcher.addURI("sms", "inbox/#", SMS_INBOX_ID); 
sURLMatcher.addURI("sms", "sent", SMS_SENT); 
sURLMatcher.addURI("sms", "sent/#", SMS_SENT_ID); 
sURLMatcher.addURI("sms", "draft", SMS_DRAFT); 
sURLMatcher.addURI("sms", "draft/#", SMS_DRAFT_ID); 
sURLMatcher.addURI("sms", "outbox", SMS_OUTBOX); 
sURLMatcher.addURI("sms", "outbox/#", SMS_OUTBOX_ID); 
sURLMatcher.addURI("sms", "undelivered", SMS_UNDELIVERED); 
sURLMatcher.addURI("sms", "failed", SMS_FAILED); 
sURLMatcher.addURI("sms", "failed/#", SMS_FAILED_ID); 
sURLMatcher.addURI("sms", "queued", SMS_QUEUED); 
sURLMatcher.addURI("sms", "conversations", SMS_CONVERSATIONS); 
sURLMatcher.addURI("sms", "conversations/*", SMS_CONVERSATIONS_ID); 
sURLMatcher.addURI("sms", "raw", SMS_RAW_MESSAGE); 
sURLMatcher.addURI("sms", "attachments", SMS_ATTACHMENT); 
sURLMatcher.addURI("sms", "attachments/#", SMS_ATTACHMENT_ID); 
sURLMatcher.addURI("sms", "threadID", SMS_NEW_THREAD_ID); 
sURLMatcher.addURI("sms", "threadID/*", SMS_QUERY_THREAD_ID); 
sURLMatcher.addURI("sms", "status/#", SMS_STATUS_ID); 
sURLMatcher.addURI("sms", "sr_pending", SMS_STATUS_PENDING); 
sURLMatcher.addURI("sms", "sim", SMS_ALL_SIM); 
sURLMatcher.addURI("sms", "sim/#", SMS_SIM);

当中,delete方法中支持的协议为:
SMS_ALL               依据參数中的条件删除sms表数据 
SMS_ALL_ID         依据_id删除sms表数据 
SMS_CONVERSATIONS_ID     依据thread_id删除sms表数据,能够带其他条件 
SMS_RAW_MESSAGE              依据參数中的条件删除 raw表 
SMS_STATUS_PENDING         依据參数中的条件删除 sr_pending表 
SMS_SIM                                 从Sim卡上删除数据

试一下SMS_CONVERSATIONS_ID:"content://sms/conversations/3 "。删除thread_id="3", _id="5"的数据        
在eclipse中的Emulator Control中,以13800给模拟器发送三条数据,然后以13900发送一条        
this.getContentResolver().delete(Uri.parse("content://sms/conversations/3"), "_id=?", new String[]{"5"});  
成功删除一条数据。

  
在数据库中每一个发送者的thread_id尽管一样,但不是固定的。假设把一个发送者的所有数据删除掉。        
然后换一个新号码发送短信时。thread_id是以数据库中最大的id+1赋值的。   

update支持的协议有非常多:
SMS_RAW_MESSAGE    
SMS_STATUS_PENDING    
SMS_ALL    
SMS_FAILED    
SMS_QUEUED    
SMS_INBOX    
SMS_SENT    
SMS_DRAFT    
SMS_OUTBOX    
SMS_CONVERSATIONS    
SMS_ALL_ID    
SMS_INBOX_ID    
SMS_FAILED_ID    
SMS_SENT_ID    
SMS_DRAFT_ID    
SMS_OUTBOX_ID    
SMS_CONVERSATIONS_ID    
SMS_STATUS_ID

以SMS_INBOX_ID測试一下:    
ContentValues cv = new ContentValues();    
cv.put("thread_id", "2");    
cv.put("address", "00000");    
cv.put("person", "11");    
cv.put("date", "11111111");    
this.getContentResolver().update(Uri.parse("content://sms/inbox/4"), cv, null, null);    
太强了,连thread_id都能够改动。   

insert支持的协议:
SMS_ALL    
SMS_INBOX    
SMS_FAILED    
SMS_QUEUED    
SMS_SENT    
SMS_DRAFT    
SMS_OUTBOX    
SMS_RAW_MESSAGE    
SMS_STATUS_PENDING    
SMS_ATTACHMENT    
SMS_NEW_THREAD_ID 

向sms表插入数据时。type是依据协议来自己主动设置,    
假设传入的数据中没有设置date时,自己主动设置为当前系统时间;非SMS_INBOX协议时,read标志设置为1    
SMS_INBOX协议时,系统会自己主动查询并设置PERSON    
threadId为null或者0时,系统也会自己主动设置   

一直为造不了"发送失败"的邮件而发愁。如今来做一个:    
content://sms/failed   
ContentValues cv = new ContentValues();    
cv.put("_id", "99");    
cv.put("thread_id", "0");    
cv.put("address", "9999");    
cv.put("person", "888");    
cv.put("date", "9999"); 
cv.put("protocol", "0"); 
cv.put("read", "1"); 
cv.put("status", "-1"); 
//cv.put("type", "0"); 
cv.put("body", "@@@@@@@@@"); 
this.getContentResolver().insert(Uri.parse("content://sms/failed"), cv); 
type被设置成了5。thread_id设置为1

看看能不能再挖掘一下sms的功能。先来做一个错误的查询:
getContentResolver().query( Uri.parse("content://sms/") , new String[]{"a"}, "b", null, null);
log输出错误的SQL语句:
SELECT a FROM sms WHERE (b) ORDER BY date DESC
query方法中没有Group by,假设想对短信做统计,对Cursor进行遍历再统计也太慢了。
在SQL语言中group by在Where后面,那就在条件參数中想想办法:
Android组织SQL语句时将条件两端加(),那就拼一个group by出来吧:
getContentResolver().query( Uri.parse("content://sms/") , new String[]{"count(*) as count, thread_id"}, "1=1) group by (thread_id", null, null);
那么输出的SQL= SELECT count(*) as count, thread_id FROM sms WHERE ( 1=1) group by (thread_id ) ORDER BY date DESC
假设想查询URI没有相应的表怎么办呢。比方想知道 mmssms.db数据库中有哪些表,
查询的表是URI定的,再在条件參数中拼凑肯定是不行。


那我们把目光往前移,看看在字段參数中能不能凑出来。
要查询其他表,关键要去掉系统固定加入的FROM sms。
用用SQL中的凝视吧,
getContentResolver().query(Uri.parse("content://sms/"), new String[]{" * from sqlite_master WHERE type = 'table' -- "}, null, null, null);
那么输出的SQL=SELECT * from sqlite_master WHERE type = 'table' -- FROM sms ORDER BY date DESC 竟然可以执行。
得寸进尺。再进一步。假设增加“;”也能执行的话,哈哈,那么建表、删除表、更新表也能为所欲为咯。
getContentResolver().query(Uri.parse("content://sms/"), new String[]{" * from sms;select * from thrreads;-- "}, null, null, null);

3.实例

public class SMSContentObserver extends ContentObserver {
	private Context mContext;
	String[] projection = new String[] { "address", "body", "date", "type", "read" };

	public SMSContentObserver(Context context, Handler handler) {
		super(handler);
		mContext = context;
	}

	@Override
	public void onChange(boolean selfChange) {
		Uri uri = Uri.parse("content://sms/inbox");
		Cursor c = mContext.getContentResolver().query(uri, null, null, null, "date desc");
		if (c != null) {
			while (c.moveToNext()) {
				SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
				Date d = new Date(c.getLong(c.getColumnIndex("date")));
				String date = dateFormat.format(d);
				StringBuilder sb = new StringBuilder();
				sb.append("发件人手机号码: " + c.getString(c.getColumnIndex("address")))
						.append("信息内容: " + c.getString(c.getColumnIndex("body")))
						.append(" 是否查看: " + c.getInt(c.getColumnIndex("read")))
						.append(" 类型: " + c.getInt(c.getColumnIndex("type"))).append(date);
				Log.i("xxx", sb.toString());
			}
			c.close();
		}
	}
}

posted @ 2016-02-25 18:08  zfyouxi  阅读(403)  评论(0编辑  收藏  举报