文章出處

  摘要:通過多種方法來獲取最新短信(靜態廣播,動態廣播,監測數據庫變動),保證短信的穩定獲取,為了保證應用正常運行,代碼中使用開機啟動 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


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()