Android短信监听实现,及Android4.4之后短信机制变更
前阵子公司有一个项目,简单的监听短信应用,功能只有如下两个:
1.监听短信并获取短信内容上传服务器;
2.从服务器获取短信内容,发送出去
按照传统的思路,监听短信我们有两种方式;第一种是使用广播方式监听短信广播到来,第二种则是通过观察者,监听数据库数据变化。
其中,指的注意的是Android4.4之后版本,新引入了默认短信应用的概念,系统可以设置由某个应用来处理短信;
本文我们将带人们分析以下几个问题:
1.监听数据库变化方式监听短信
2.通过广播监听短信内容
3.Android 4.4以上版本短信权限问题
4.Android4.4版本以上设置默认短信应用
1.监听数据库变化方式监听短信内容
既然是监听数据库变化 那我们就应该清楚短信的数据库表结构:
sms主要结构:
_id: 短信序号,如100
thread_id:对话的序号,如100,与同一个手机号互发的短信,其序号是相同的
address: 发件人地址,即手机号,如+86138138000
person: 发件人,如果发件人在通讯录中则为具体姓名,陌生人为null
date: 日期,long型,如1346988516,可以对日期显示格式进行设置
protocol: 协议0SMS_RPOTO短信,1MMS_PROTO彩信
read: 是否阅读0未读,1已读
status: 短信状态-1接收,0complete,64pending,128failed
type: 短信类型1是接收到的,2是已发出
body: 短信具体内容
service_center:短信服务中心号码编号,如+8613800755500
_id: 短信序号,如100
thread_id:对话的序号,如100,与同一个手机号互发的短信,其序号是相同的
address: 发件人地址,即手机号,如+86138138000
person: 发件人,如果发件人在通讯录中则为具体姓名,陌生人为null
date: 日期,long型,如1346988516,可以对日期显示格式进行设置
protocol: 协议0SMS_RPOTO短信,1MMS_PROTO彩信
read: 是否阅读0未读,1已读
status: 短信状态-1接收,0complete,64pending,128failed
type: 短信类型1是接收到的,2是已发出
body: 短信具体内容
service_center:短信服务中心号码编号,如+8613800755500
既然需要操作数据库,便少不了使用ContentResolver,所以我们应该还需要了解,短信的content uri :
全部短信:content://sms/
收件箱:content://sms/inbox
发件箱:content://sms/sent
草稿箱:content://sms/draft
private Uri SMS_INBOX = Uri.parse("content://sms/inbox"); public void getSmsFromPhone() { ContentResolver cr = getContentResolver(); String[] projection = new String[] { "body","address" };//"_id", "address", "person",, "date", "type String where = " date > " + (System.currentTimeMillis() - 10 * 60 * 1000); Cursor cur = cr.query(SMS_INBOX, projection, where, null, "date desc"); if (null == cur) return; if (cur.moveToFirst()) { String number = cur.getString(cur.getColumnIndex("address"));//手机号 String body = cur.getString(cur.getColumnIndex("body")); //TODO 这里是具体处理逻辑 } }
在这里我们只是写了一个方法查询数据库,但是还有一个问题就是我们应该在什么时候去查数据库,总不能起个线程去轮训,这样太耗费资源了,这里我们可以是用观察者模式;
private SmsObserver smsObserver; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.app_login); smsObserver = new SmsObserver(this, smsHandler); getContentResolver().registerContentObserver(SMS_INBOX, true, smsObserver); } public Handler smsHandler = new Handler() { //这里可以进行回调的操作 //TODO }; class SmsObserver extends ContentObserver { public SmsObserver(Context context, Handler handler) { super(handler); } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); //每当有新短信到来时,使用我们获取短消息的方法 getSmsFromPhone(); } }
在这里我们注册了一个观察者,监听收件箱的变化,一旦收件箱变化,我们就查询数据库,去除最新的一条数据
相应的权限这里就不贴出来了
至此我们就实现了通过监听数据库的方式来监听短信内容
2.通过广播监听短信内容
public class SmsReceiver extends BroadcastReceiver { public static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"; private static final String TAG = "yjj"; public SmsReceiver() { Log.i("yjj", "new SmsReceiver"); } @Override public void onReceive(final Context context, Intent intent) { Log.i(TAG, "jie shou dao"); Cursor cursor = null; try { if (SMS_RECEIVED.equals(intent.getAction())) { Log.d(TAG, "sms received!"); Bundle bundle = intent.getExtras(); if (bundle != null) { Object[] pdus = (Object[]) bundle.get("pdus"); final SmsMessage[] messages = new SmsMessage[pdus.length]; for (int i = 0; i < pdus.length; i++) { messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); } if (messages.length > 0) { String content = messages[0].getMessageBody(); String sender = messages[0].getOriginatingAddress(); long msgDate = messages[0].getTimestampMillis(); String smsToast = "New SMS received from : " + sender + "\n'" + content + "'"; Toast.makeText(context, smsToast, Toast.LENGTH_LONG) .show(); Log.d(TAG, "message from: " + sender + ", message body: " + content + ", message date: " + msgDate); //自己的逻辑 } } cursor = context.getContentResolver().query(Uri.parse("content://sms"), new String[] { "_id", "address", "read", "body", "date" }, "read = ? ", new String[] { "0" }, "date desc"); if (null == cursor){ return; } Log.i(TAG,"m cursor count is "+cursor.getCount()); Log.i(TAG,"m first is "+cursor.moveToFirst()); } } catch (Exception e) { e.printStackTrace(); Log.e(TAG, "Exception : " + e); } finally { if (cursor != null) { cursor.close(); cursor = null; } } } }
这个很简单就是定义一个广播接收者,并且在清单文件中注册(注册有两种方式,这里就不展开了)
<receiver android:name=".message.SmsReceiver" android:permission="android.permission.BROADCAST_SMS"> <intent-filter android:priority="2147483647"> <action android:name="android.provider.Telephony.SMS_DELIVER" /> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> </receiver>
3.Android 4.4以上版本短信权限问题
Android4.4版本之前,短信有着一个问题,任何应用只要想,就可以操作短信,着包括监听短信、修改短信、删除短信、拦截短信等,因而市面上有着成片的短信应用,着也使得Android系统短信的管理变得越发的混乱。
针对这个问题Google在Android4.4版本之后,引进了一个新的概念----默认短信应用。即用户可以在系统中选择由哪个应用默认对短信进行处理。
针对Android4.4版本的,Google提供了SMS_DELIVER_ACTION(sms)和 WAP_PUSH_DELIVER_ACTION(MMS)这两个intent给默认的短信使用,也就是说只有默认短信才可以收到这两个广播,也只有收到这两个广播的短信应用才可以对短信数据库机型操作,其他的短信应用可以使用SMS_RECEIVED_ACTION对短信进行监听,但仅仅只能读取(理论上可以监听,但是在一台6.0系统的三星机器上并不能监听到,具体什么原因没查出来,当然这是在我所写的应用没有成为手机默认短信应用的情况下,当设置为默认短信应用后监听也是正常的)
另外,值得一提的是,在Android4.4版本之前SMS_RECEIVED_ACTION是一个有序广播,这意味着在Android4.4版本之前,应用在接受广播之后可以对广播进行拦截;但是在Android4.4之后,这个拦截动作不会生效,这就意味着Android4.4之后,非默认短信应用对短信除了读操作外,没有更多的权限了
4.Android4.4版本以上设置默认短信应用
我们已经分析了Android4.4版本之后短信的改变----默认短信应用,但是并不是每个应用都可以被设置为默认短信应用,接下来我们来实现一下怎么让我们的短信应用可以被设置为默认短息应用。
<!-- BroadcastReceiver that listens for incoming MMS messages --> <receiver android:name=".message.MmsReceiver" android:permission="android.permission.BROADCAST_WAP_PUSH"> <intent-filter> <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" /> <data android:mimeType="application/vnd.wap.mms-message" /> </intent-filter> </receiver> <!-- Activity that allows the user to send new SMS/MMS messages --> <activity android:name=".message.ComposeSmsActivity" > <intent-filter> <action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SENDTO" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="sms" /> <data android:scheme="smsto" /> <data android:scheme="mms" /> <data android:scheme="mmsto" /> </intent-filter> </activity> <!-- Service that delivers messages from the phone "quick response" --> <service android:name=".message.HeadlessSmsSendService" android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" android:exported="true" > <intent-filter> <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="sms" /> <data android:scheme="smsto" /> <data android:scheme="mms" /> <data android:scheme="mmsto" /> </intent-filter> </service>
首先我们子在清单文件中写入如下信息,可以看到我们定义了一个MmsReceiver、一个ComposeSmsActivity、一个HeadlessSmsSendService
很显然我们需要这三个对应的java文件,即一个receiver类、一个service类、一个Activity类;这三个了都可以不需要任何内容,具体代码如下
MmsReceiver.java
public class MmsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { } }
ComposeSmsActivity.java
public class ComposeSmsActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); } }
HeadlessSmsSendService.java
public class HeadlessSmsSendService extends Service { @Override public IBinder onBind(Intent intent) { return null; } }
通过以上步骤,我们所写的应用就可以被设置为默认短信应用了
最后别忘了添加相应的权限:
<uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.WRITE_CALL_LOG" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.READ_LOGS" /> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" /> <uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.WRITE_SMS" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
这里贴出的是我整个项目的权限,世纪应该只需要SMS相关的权限,这里就不做区分了
下面贴出参考的相关博客,供大家全面了解Android短信机制:
http://blog.csdn.net/maybe_windleave/article/details/17740345
http://blog.csdn.net/liuhe688/article/details/7020612
http://blog.csdn.net/subaohao/article/details/39368961
http://blog.csdn.net/mad1989/article/details/22426415/
前阵子公司有一个项目,简单的监听短信应用,功能只有如下两个:
1.监听短信并获取短信内容上传服务器;
2.从服务器获取短信内容,发送出去
按照传统的思路,监听短信我们有两种方式;第一种是使用广播方式监听短信广播到来,第二种则是通过观察者,监听数据库数据变化。
其中,指的注意的是Android4.4之后版本,新引入了默认短信应用的概念,系统可以设置由某个应用来处理短信;
本文我们将带人们分析以下几个问题:
1.监听数据库变化方式监听短信
2.通过广播监听短信内容
3.Android 4.4以上版本短信权限问题
4.Android4.4版本以上设置默认短信应用
1.监听数据库变化方式监听短信内容
既然是监听数据库变化 那我们就应该清楚短信的数据库表结构:
sms主要结构:
_id: 短信序号,如100
thread_id:对话的序号,如100,与同一个手机号互发的短信,其序号是相同的
address: 发件人地址,即手机号,如+86138138000
person: 发件人,如果发件人在通讯录中则为具体姓名,陌生人为null
date: 日期,long型,如1346988516,可以对日期显示格式进行设置
protocol: 协议0SMS_RPOTO短信,1MMS_PROTO彩信
read: 是否阅读0未读,1已读
status: 短信状态-1接收,0complete,64pending,128failed
type: 短信类型1是接收到的,2是已发出
body: 短信具体内容
service_center:短信服务中心号码编号,如+8613800755500
_id: 短信序号,如100
thread_id:对话的序号,如100,与同一个手机号互发的短信,其序号是相同的
address: 发件人地址,即手机号,如+86138138000
person: 发件人,如果发件人在通讯录中则为具体姓名,陌生人为null
date: 日期,long型,如1346988516,可以对日期显示格式进行设置
protocol: 协议0SMS_RPOTO短信,1MMS_PROTO彩信
read: 是否阅读0未读,1已读
status: 短信状态-1接收,0complete,64pending,128failed
type: 短信类型1是接收到的,2是已发出
body: 短信具体内容
service_center:短信服务中心号码编号,如+8613800755500
既然需要操作数据库,便少不了使用ContentResolver,所以我们应该还需要了解,短信的content uri :
全部短信:content://sms/
收件箱:content://sms/inbox
发件箱:content://sms/sent
草稿箱:content://sms/draft
private Uri SMS_INBOX = Uri.parse("content://sms/inbox"); public void getSmsFromPhone() { ContentResolver cr = getContentResolver(); String[] projection = new String[] { "body","address" };//"_id", "address", "person",, "date", "type String where = " date > " + (System.currentTimeMillis() - 10 * 60 * 1000); Cursor cur = cr.query(SMS_INBOX, projection, where, null, "date desc"); if (null == cur) return; if (cur.moveToFirst()) { String number = cur.getString(cur.getColumnIndex("address"));//手机号 String body = cur.getString(cur.getColumnIndex("body")); //TODO 这里是具体处理逻辑 } }
在这里我们只是写了一个方法查询数据库,但是还有一个问题就是我们应该在什么时候去查数据库,总不能起个线程去轮训,这样太耗费资源了,这里我们可以是用观察者模式;
private SmsObserver smsObserver; protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.app_login); smsObserver = new SmsObserver(this, smsHandler); getContentResolver().registerContentObserver(SMS_INBOX, true, smsObserver); } public Handler smsHandler = new Handler() { //这里可以进行回调的操作 //TODO }; class SmsObserver extends ContentObserver { public SmsObserver(Context context, Handler handler) { super(handler); } @Override public void onChange(boolean selfChange) { super.onChange(selfChange); //每当有新短信到来时,使用我们获取短消息的方法 getSmsFromPhone(); } }
在这里我们注册了一个观察者,监听收件箱的变化,一旦收件箱变化,我们就查询数据库,去除最新的一条数据
相应的权限这里就不贴出来了
至此我们就实现了通过监听数据库的方式来监听短信内容
2.通过广播监听短信内容
public class SmsReceiver extends BroadcastReceiver { public static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"; private static final String TAG = "yjj"; public SmsReceiver() { Log.i("yjj", "new SmsReceiver"); } @Override public void onReceive(final Context context, Intent intent) { Log.i(TAG, "jie shou dao"); Cursor cursor = null; try { if (SMS_RECEIVED.equals(intent.getAction())) { Log.d(TAG, "sms received!"); Bundle bundle = intent.getExtras(); if (bundle != null) { Object[] pdus = (Object[]) bundle.get("pdus"); final SmsMessage[] messages = new SmsMessage[pdus.length]; for (int i = 0; i < pdus.length; i++) { messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); } if (messages.length > 0) { String content = messages[0].getMessageBody(); String sender = messages[0].getOriginatingAddress(); long msgDate = messages[0].getTimestampMillis(); String smsToast = "New SMS received from : " + sender + "\n'" + content + "'"; Toast.makeText(context, smsToast, Toast.LENGTH_LONG) .show(); Log.d(TAG, "message from: " + sender + ", message body: " + content + ", message date: " + msgDate); //自己的逻辑 } } cursor = context.getContentResolver().query(Uri.parse("content://sms"), new String[] { "_id", "address", "read", "body", "date" }, "read = ? ", new String[] { "0" }, "date desc"); if (null == cursor){ return; } Log.i(TAG,"m cursor count is "+cursor.getCount()); Log.i(TAG,"m first is "+cursor.moveToFirst()); } } catch (Exception e) { e.printStackTrace(); Log.e(TAG, "Exception : " + e); } finally { if (cursor != null) { cursor.close(); cursor = null; } } } }
这个很简单就是定义一个广播接收者,并且在清单文件中注册(注册有两种方式,这里就不展开了)
<receiver android:name=".message.SmsReceiver" android:permission="android.permission.BROADCAST_SMS"> <intent-filter android:priority="2147483647"> <action android:name="android.provider.Telephony.SMS_DELIVER" /> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> </receiver>
3.Android 4.4以上版本短信权限问题
Android4.4版本之前,短信有着一个问题,任何应用只要想,就可以操作短信,着包括监听短信、修改短信、删除短信、拦截短信等,因而市面上有着成片的短信应用,着也使得Android系统短信的管理变得越发的混乱。
针对这个问题Google在Android4.4版本之后,引进了一个新的概念----默认短信应用。即用户可以在系统中选择由哪个应用默认对短信进行处理。
针对Android4.4版本的,Google提供了SMS_DELIVER_ACTION(sms)和 WAP_PUSH_DELIVER_ACTION(MMS)这两个intent给默认的短信使用,也就是说只有默认短信才可以收到这两个广播,也只有收到这两个广播的短信应用才可以对短信数据库机型操作,其他的短信应用可以使用SMS_RECEIVED_ACTION对短信进行监听,但仅仅只能读取(理论上可以监听,但是在一台6.0系统的三星机器上并不能监听到,具体什么原因没查出来,当然这是在我所写的应用没有成为手机默认短信应用的情况下,当设置为默认短信应用后监听也是正常的)
另外,值得一提的是,在Android4.4版本之前SMS_RECEIVED_ACTION是一个有序广播,这意味着在Android4.4版本之前,应用在接受广播之后可以对广播进行拦截;但是在Android4.4之后,这个拦截动作不会生效,这就意味着Android4.4之后,非默认短信应用对短信除了读操作外,没有更多的权限了
4.Android4.4版本以上设置默认短信应用
我们已经分析了Android4.4版本之后短信的改变----默认短信应用,但是并不是每个应用都可以被设置为默认短信应用,接下来我们来实现一下怎么让我们的短信应用可以被设置为默认短息应用。
<!-- BroadcastReceiver that listens for incoming MMS messages --> <receiver android:name=".message.MmsReceiver" android:permission="android.permission.BROADCAST_WAP_PUSH"> <intent-filter> <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" /> <data android:mimeType="application/vnd.wap.mms-message" /> </intent-filter> </receiver> <!-- Activity that allows the user to send new SMS/MMS messages --> <activity android:name=".message.ComposeSmsActivity" > <intent-filter> <action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SENDTO" /> <category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.BROWSABLE" /> <data android:scheme="sms" /> <data android:scheme="smsto" /> <data android:scheme="mms" /> <data android:scheme="mmsto" /> </intent-filter> </activity> <!-- Service that delivers messages from the phone "quick response" --> <service android:name=".message.HeadlessSmsSendService" android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE" android:exported="true" > <intent-filter> <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" /> <category android:name="android.intent.category.DEFAULT" /> <data android:scheme="sms" /> <data android:scheme="smsto" /> <data android:scheme="mms" /> <data android:scheme="mmsto" /> </intent-filter> </service>
首先我们子在清单文件中写入如下信息,可以看到我们定义了一个MmsReceiver、一个ComposeSmsActivity、一个HeadlessSmsSendService
很显然我们需要这三个对应的java文件,即一个receiver类、一个service类、一个Activity类;这三个了都可以不需要任何内容,具体代码如下
MmsReceiver.java
public class MmsReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { } }
ComposeSmsActivity.java
public class ComposeSmsActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); } }
HeadlessSmsSendService.java
public class HeadlessSmsSendService extends Service { @Override public IBinder onBind(Intent intent) { return null; } }
通过以上步骤,我们所写的应用就可以被设置为默认短信应用了
最后别忘了添加相应的权限:
<uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.WRITE_CONTACTS" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.READ_CALL_LOG" /> <uses-permission android:name="android.permission.WRITE_CALL_LOG" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.READ_LOGS" /> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" /> <uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.RECEIVE_SMS" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.WRITE_SMS" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW" />
这里贴出的是我整个项目的权限,世纪应该只需要SMS相关的权限,这里就不做区分了
下面贴出参考的相关博客,供大家全面了解Android短信机制:
http://blog.csdn.net/maybe_windleave/article/details/17740345
http://blog.csdn.net/liuhe688/article/details/7020612
http://blog.csdn.net/subaohao/article/details/39368961
http://blog.csdn.net/mad1989/article/details/22426415/