摘要:通過多種方法來獲取最新短信(靜態廣播,動態廣播,監測數據庫變動),保證短信的穩定獲取,為了保證應用正常運行,代碼中使用開機啟動 service的方法,并且能夠保證程序不被用戶意外關閉出現無法提醒的問題,收到命令后使用內容提供者刪除命令短信
=============================================================================================================================
正文:
試想:你會不會有這樣一個時候,因為某些原因,你的朋友喜歡將手機設置為震動、甚至是靜音,而且全天都是這樣。然后當你有急事找你的朋友需要打電話時,發現他(她)死活不接聽,其實不是他(她)不想接,很多時候是因為他沒有聽到而已。我就遇到過這樣的問題,那天我站在寒冷的女生宿舍樓下給女友打電話,結果女友睡過頭了,愣是打了半個小時電話沒人接,因為她喜歡手機靜音,這樣的場景發生過多次,本人很是受傷,于是,我開始尋找方法能夠快速準確的叫醒女朋友,最終這款小應用產生了,取名為《LoveRing》。
為了簡單有效,我首先想到了使用短信來控制對方手機(安卓),給對方手機上裝上定時炸彈(LoveRing),當對方手機接收到對應的代號后,對方的手機將會想起音樂,并彈出對話框,直至對方醒來點擊按鈕方可終止音樂。總體設計思想就是這樣。
為了監控短信消息,我首先想到了監聽短信廣播(優先級問題,可能會無效,后邊使用其他方法),經過簡單設計后,我開始風風火火的編碼:
首先想到了最簡單的方法:靜態廣播,然而靜態廣播似乎不太好用,我發現靜態廣播的優先級雖然也可以設置,但是經過測試發現,靜態廣播接收器的優先級低于動態廣播接收器的優先級。下邊把靜態廣播接收的方法寫下來(我嘗試過,大家也可以嘗試下):
首先添加接收,發送短信等權限:
<uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.READ_SMS" /> <uses-permission android:name="android.permission.WRITE_SMS" />
注冊廣播接收器,接收短信廣播:
<receiver android:name="yxxrui.com.wakeup.MyBroadcastReceiver" android:enabled="true"> <intent-filter android:priority="2147483647"> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver>
然后開始編寫廣播接收器類(為了方便,我在代碼中寫注釋來幫助理解):
1 public class MyBroadcastReceiver extends BroadcastReceiver { 2 private static final String strRes = "android.provider.Telephony.SMS_RECEIVED"; 3 private static MediaPlayer music = null;//用來播放聲音 4 private static int status = 1;//當前狀態,是否已經處理過,由于后邊將動態廣播與靜態廣播放到了一起,故可能接收到兩次廣播,此處做處理 5 private static AudioManager am = null; 6 private static int musicVolume = 0; 7 private int maxVolum = 50; 8 @Override 9 public void onReceive(Context context, Intent intent) { 10 if(strRes.equals(intent.getAction())){ 11 //收到短信 12 Bundle bundle = intent.getExtras(); 13 if(bundle!=null){ 14 Object[] pdus = (Object[])bundle.get("pdus"); 15 SmsMessage[] msg = new SmsMessage[pdus.length]; 16 for(int i = 0 ;i<pdus.length;i++){ 17 msg[i] = SmsMessage.createFromPdu((byte[])pdus[i]); 18 } 19 for(SmsMessage curMsg:msg){ 20 String address = curMsg.getDisplayOriginatingAddress(); 21 long time = curMsg.getTimestampMillis(); 22 String content = curMsg.getDisplayMessageBody(); 23 dealMsg(context, address, time, content);//處理短信 24 } 25 } 26 }else if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)){ 27 //開機完畢 28 Intent smsIntent=new Intent(context, SmsBindService.class); 29 context.startService(smsIntent); 30 31 Intent socketIntent=new Intent(context, SocketPortBindService.class); 32 context.startService(socketIntent); 33 } 34 } 35 36 /** 37 * 為了安全,防止壞人任意喚起音樂,此處簡單設計了密碼,大家可以對此處做優化,加密處理 38 */ 39 private String pswStr = null;//密碼 40 private String hintStr = null;//彈框中顯示的內容 41 private String codeStr = null;//代號,如:#GetUp# 42 private String numberStr = null;//發短信手機號碼 43 private String feedbackStr = null;//自動回復的短信內容,方便接收到信息后快速回復信息 44 private int volume = 70;//音樂聲音大小 45 46 /** 47 * 處理短信,驗證是否為命令 48 * @param context 上下文 49 * @param address 手機號碼 50 * @param time 發送時間 51 * @param content 發送的內容 52 */ 53 public void dealMsg(Context context, String address, long time, String content){ 54 try{ 55 final String msgContent = content; 56 //基本的參數是允許用戶自己修改的,保存到SharedPreferences中 57 SharedPreferences prefer = context.getSharedPreferences("yxxrui", 58 context.MODE_PRIVATE); 59 pswStr = prefer.getString("psw", 60 g(context,R.string.psw_content_default)); 61 codeStr = prefer.getString("code", 62 g(context,R.string.code_content_default)); 63 feedbackStr = prefer.getString("feedback", 64 g(context,R.string.feedback_content_default)); 65 hintStr = prefer.getString("hint", 66 g(context,R.string.hint_content_default)); 67 double percent = prefer.getInt("volume", 70); 68 if(am==null){ 69 am = (AudioManager)context.getSystemService(context.AUDIO_SERVICE); 70 maxVolum = am.getStreamMaxVolume(AudioManager.STREAM_SYSTEM); 71 } 72 //此處需要注意:一開始我認為最大音量就是100,然后可以隨意設置大小,結果翻閱文檔和測試發現音量大小各手機不同 73 //其實相當于你點幾次音量+鍵,就共有多少個檔位,我的手機為7,故此處需要計算百分比 74 volume = (int) Math.floor(maxVolum * (percent/100)); 75 //此處暗藏一個命令,將對方手機開啟標準模式,方便你打電話聯系,哈哈哈(應該不算犯規吧!) 76 if(content.equals("#RingCall#456852")){ 77 am.setRingerMode(AudioManager.RINGER_MODE_NORMAL); 78 am.setStreamVolume(AudioManager.STREAM_RING, 50, 0); 79 deleteSMS(context, msgContent); 80 }else if(content.startsWith(codeStr)){ 81 //若短信的代號與本機所設置的代號相同的話,初步判定是命令,那么繼續操作 82 content = content.replace(codeStr, ""); 83 //切分出密碼,使用的是兩個英文逗號分割 84 String[] result = content.split(",,"); 85 //SimpleDateFormat formatter = new SimpleDateFormat("MMddHHmm"); 86 //Date curDate = new Date(time); 87 //int qustion = Math.abs(12240214 - Integer.parseInt(formatter.format(curDate))); 88 String answer = ""; 89 if(result.length>=1){ 90 answer = result[0]; 91 } 92 if(answer.equals(pswStr)/*Math.abs(qustion - answer) < 5*/){ 93 //驗證通過后,可以進行真正工作了 94 if(status == 2){//播放過了已經 95 return; 96 } 97 try{ 98 this.abortBroadcast(); 99 }catch(Exception ex){ 100 101 } 102 numberStr = address; 103 //此處將收到的命令刪除(放心,絕對不刪除其他短信) 104 deleteSMS(context, msgContent); 105 //播放音樂 106 PlayMusic(context); 107 status = 2; 108 //彈出對話框,顯示溫馨提示,溫馨提示可以有發送者定義,默認為本機設置的提示語 109 String str = result.length >= 2 ? result[1] : hintStr; 110 showDialog(context,str); 111 } 112 } 113 }catch(Exception e){ 114 Toast.makeText(context,e.toString(),Toast.LENGTH_LONG).show(); 115 } 116 } 117 protected String g(Context context, int id){ 118 return context.getResources().getString(id); 119 } 120 private void showDialog(Context context,String content){ 121 AlertDialog.Builder builder = new AlertDialog.Builder(context); 122 builder.setMessage(content) 123 .setTitle(g(context,R.string.hint_title)) 124 .setCancelable(false) 125 .setPositiveButton(g(context,R.string.hint_btn_not_reply), new DialogInterface.OnClickListener() { 126 public void onClick(DialogInterface dialog, int id) { 127 if(music!=null){ 128 music.pause(); 129 music.stop(); 130 } 131 recover(); 132 dialog.cancel(); 133 } 134 }) 135 .setNegativeButton(g(context,R.string.hint_btn_reply), new DialogInterface.OnClickListener() { 136 public void onClick(DialogInterface dialog, int id) { 137 //暫時這么處理,之后發送短信回復已起床 138 if(music!=null){ 139 music.pause(); 140 music.stop(); 141 } 142 recover(); 143 sendMessage(numberStr, feedbackStr); 144 dialog.cancel(); 145 } 146 }); 147 AlertDialog alert = builder.create(); 148 alert.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); 149 alert.show(); 150 } 151 private void sendMessage(String number, String msg){ 152 SmsManager smsManager = SmsManager.getDefault(); 153 if(msg.length() > 70) { 154 List<String> contents = smsManager.divideMessage(msg); 155 for(String sms : contents) { 156 smsManager.sendTextMessage(number, null, sms, null, null); 157 } 158 } else { 159 smsManager.sendTextMessage(number, null, msg, null, null); 160 } 161 } 162 private Vibrator vibrator = null; 163 protected void PlayMusic(final Context context) { 164 vibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE); 165 final long [] pattern = {2000,500,150,500}; // 停止 開啟 停止 開啟 166 /*vibrator.vibrate(pattern,2); //重復兩次上面的pattern 如果只想震動一次,index設為-1 167 */ Thread th = new Thread(new Runnable(){ 168 @Override 169 public void run() { 170 int MusicId = R.raw.wakeup1; 171 //保存當前音量,播放完后恢復回去 172 //systemVolume = am.getStreamVolume(AudioManager.STREAM_SYSTEM); 173 musicVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC); 174 //am.setStreamVolume(AudioManager.STREAM_SYSTEM, volume, AudioManager.FLAG_PLAY_SOUND); 175 am.setStreamVolume(AudioManager.STREAM_MUSIC, volume, AudioManager.FLAG_PLAY_SOUND); 176 if(music!=null&&music.isPlaying()){ 177 return; 178 } 179 music = MediaPlayer.create(context, MusicId); 180 music.setLooping(true); 181 music.setOnCompletionListener(new OnCompletionListener(){ 182 @Override 183 public void onCompletion(MediaPlayer mp) { 184 recover(); 185 } 186 }); 187 vibrator.vibrate(pattern,0); 188 music.start(); 189 } 190 }); 191 th.start(); 192 /*Toast.makeText(context, systemVolume+"", Toast.LENGTH_LONG).show(); 193 Toast.makeText(context, musicVolume+"", Toast.LENGTH_LONG).show();*/ 194 } 195 private void recover(){ 196 am.setStreamVolume(AudioManager.STREAM_MUSIC, musicVolume, 0); 197 //am.setStreamVolume(AudioManager.STREAM_SYSTEM, systemVolume, 0); 198 status = 1;//恢復原狀 199 vibrator.cancel(); 200 } 201 public void deleteSMS(Context context, String smscontent) 202 { 203 /*所有文件夾:content://sms/all 204 收件箱:content://sms/inbox 205 已發送:content://sms/sent 206 草稿:content://sms/draft 207 發件箱:content://sms/outbox 208 發送失敗:content://sms/failed 209 排隊消息:content://sms/queued 210 未送達:content://sms/undelivered 211 對話:content://sms/conversations*/ 212 try{ 213 // 準備系統短信收信箱的uri地址 214 Uri uri = Uri.parse("content://sms");// 全部,只要符合條件,全部刪除 215 // 查詢收信箱里所有的短信 216 ContentResolver cR = context.getContentResolver(); 217 Cursor isRead = cR.query(uri, null, "read<=" + 1,null, null); 218 while (isRead.moveToNext()){ 219 // String phone = 220 // isRead.getString(isRead.getColumnIndex("address")).trim();//獲取發信人 221 String body = 222 isRead.getString(isRead.getColumnIndex("body")).trim();// 獲取信息內容 223 if (body.equals(smscontent)){ 224 int id = isRead.getInt(isRead.getColumnIndex("_id")); 225 cR.delete(Uri.parse("content://sms"), "_id=" + id, null); 226 } 227 } 228 }catch (Exception e){ 229 Toast.makeText(context, "delete Failed! "+e.toString(), Toast.LENGTH_SHORT); 230 } 231 } 232 }
優先級不高,經常不響,于是改成了動態廣播,為了更加有效,需要設置開機監聽,開機啟動service,然后在service中監聽廣播,收到廣播后依舊發送至上邊寫的廣播接收器中,方便:
添加開機啟動廣播權限
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
修改上邊的廣播注冊器:在filter中添加一條開機事件:
<receiver android:name="yxxrui.com.wakeup.MyBroadcastReceiver" android:enabled="true"> <intent-filter android:priority="2147483647"> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver>
其實上邊的廣播接收類中已經寫好了開機事件,只是上邊的事件中需要啟動一個service,而service還沒有寫上去:這里貼上:
首先,注冊服務:
<service android:name="yxxrui.com.wakeup.SmsBindService" android:priority="1000"> </service>
然后,簡單粗暴,貼service代碼:
1 public class SmsBindService extends Service{ 2 private MyBroadcastReceiver receiver; 3 @Override 4 public IBinder onBind(Intent intent) { 5 return null; 6 } 7 8 @Override 9 public void onCreate(){ 10 super.onCreate(); 11 IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED"); 12 filter.setPriority(2147483647); 13 receiver =new MyBroadcastReceiver(); 14 registerReceiver(receiver, filter); 15 } 16 @Override 17 public int onStartCommand(Intent intent,int flags, int startId){ 18 flags = START_STICKY; 19 return super.onStartCommand(intent, flags, startId); 20 } 21 22 @Override 23 public void onDestroy(){ 24 super.onDestroy(); 25 //Toast.makeText(this, "WakeUp service has been stoped", Toast.LENGTH_SHORT).show(); 26 Intent localIntent = new Intent(); 27 localIntent.setClass(this, SmsBindService.class); //銷毀時重新啟動Service 28 this.startService(localIntent); 29 unregisterReceiver(receiver); 30 } 31 }
當然,主界面 很水,代碼就不再粘貼,這里只是對一些參數進行設置而已,沒有什么技術含量:
到此,動態,靜態均已完成,但是這樣寫好以后真的可以實現所有短信都能檢測到嗎?不,不可能,因為有一些大佬的短信廣播永遠比你厲害,這樣的應用會經常失效,如何才能解決這個問題呢?其實就是題目中說的內容提供者,我這里直接疊加到上邊的代碼中,也就是一個應用使用三種檢測短信的方式:靜態廣播,動態廣播,內容提供者。
現在在SmsBindService 類中添加一個內部類,用于檢測內容提供者的數據庫改變事件,當數據庫的內容發生改變時,系統會自動回調這個onChange() 方法,讓后再onChange()方法中獲取未讀信息,并驗證是否為命令,再做處理,我在這里的處理方法仍然是使用上邊寫好的短信處理方法
class MyContentObserver extends ContentObserver{ public MyContentObserver(Handler handler) { super(handler); } //當被監聽的內容發生改變時回調 @Override public void onChange(boolean selfChange) { if(selfChange){ return; } Uri uri = Uri.parse("content://sms/inbox"); //收件箱uri //查看發件箱內容 ContentResolver resolver = getContentResolver(); Cursor cursor = resolver.query(uri, new String[]{"address","date","body"}, "read <=" + 1, null, "date desc"); if(cursor!=null && cursor.getCount()>0){ long now = System.currentTimeMillis(); String address; long time; String content; //只看前5條,2分鐘內的短信 int n = 0; while(n<5 && cursor.moveToNext()){ address = cursor.getString(0); time = cursor.getInt(1); content = cursor.getString(2); if(Math.abs(now-time) > 5*60*1000){ receiver.dealMsg(SmsBindService.this, address, time, content); } n++; } cursor.close(); } } }
最后修改上邊SmsBindService 的代碼,在onCreate()方法末尾添加以下代碼即可:
ContentResolver resolver = getContentResolver(); //注冊一個內容觀察者觀察短信數據庫
resolver.registerContentObserver( Uri.parse("content://sms/"), true, new MyContentObserver(new Handler()));
有問題可以聯系我,我的郵箱是:yxxrui@163.com,我的網址是:http://www.yxxrui.cn
文章列表