MyException - 我的异常网
当前位置:我的异常网» Android » Android_透过ContentObserver监听短信数据变化

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

www.MyException.Cn  网友分享于:2014-08-05  浏览:0次
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

检索数据方法很简单:  
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();
		}
	}
}

文章评论

2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
程序员必看的十大电影
程序员必看的十大电影
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
每天工作4小时的程序员
每天工作4小时的程序员
旅行,写作,编程
旅行,写作,编程
漫画:程序员的工作
漫画:程序员的工作
总结2014中国互联网十大段子
总结2014中国互联网十大段子
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
编程语言是女人
编程语言是女人
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
为什么程序员都是夜猫子
为什么程序员都是夜猫子
代码女神横空出世
代码女神横空出世
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
那些争议最大的编程观点
那些争议最大的编程观点
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
Java程序员必看电影
Java程序员必看电影
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
一个程序员的时间管理
一个程序员的时间管理
老程序员的下场
老程序员的下场
如何成为一名黑客
如何成为一名黑客
程序员的鄙视链
程序员的鄙视链
程序员应该关注的一些事儿
程序员应该关注的一些事儿
 程序员的样子
程序员的样子
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
程序猿的崛起——Growth Hacker
程序猿的崛起——Growth Hacker
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
我的丈夫是个程序员
我的丈夫是个程序员
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
10个调试和排错的小建议
10个调试和排错的小建议
鲜为人知的编程真相
鲜为人知的编程真相
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
中美印日四国程序员比较
中美印日四国程序员比较
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有