標籤:
提起BroadcastReceiver大家都很熟悉,它和Activity,Service以及ContentProvider並稱為Android的四大組件(四大金剛),可見BroadcastReceiver的重要性,今天我們主要從安全的角度來講解稱為四大組件之一的BroadcastReceiver。可能有的童靴看到這裡會有疑問,BroadcastReceiver有啥好講的,不就是先定義自己的廣播接收器然後在manifest.xml檔案中註冊,在需要發送廣播的地方調用Context的sendBroadcast()方法或者是sendOrderBroadcast(),最後在我們自訂的的廣播接收器的onReceive()方法中做相應邏輯嗎?恩,這樣使用BroadcastReceiver的總體流程是非常OK的,也說明你對廣播這塊的使用掌握的是非常熟悉的,但今天是從安全的角度來講解BroadcastReveiver的,我相信你閱讀完本文後會有所收穫(*^__^*) ……
BroadcastReceiver的使用很廣泛也很簡單,它的使用可以概括為三步走:
- 定義自己的BroadcastReceiver並實現onReceive()方法
- 在AndroidManifest.xml中靜態註冊或者在代碼中動態註冊
- 調用Context的sendBroadcast()方法發送廣播
這裡先對廣播的兩種註冊方式做一下說明,廣播註冊分為動態註冊和靜態註冊兩種方式。靜態註冊指的是常駐型廣播,無論我們的應用程式在不在運行,只要有合格廣播發來我們的應用程式都可以接受的到,動態註冊指的是只有在我們的應用程式在啟動並執行時候才可以接受到合格廣播。所以當我們要使用廣播時要做一下區分:是用靜態還是用動態。
按照以上步驟我們先定義自己的廣播接收器CustomBroadcastReceiver,定義自己的廣播接收器需要繼承自BroadcastReceiver類,該類是abstract類型的,所以我們需要實現onReceive()方法,代碼如下:
public class CustomBroadcastReceiver extends BroadcastReceiver {public CustomBroadcastReceiver() {}@Overridepublic void onReceive(Context context, Intent intent) {Log.e(this.getClass().getSimpleName(), "current time is :" + System.currentTimeMillis());}} 我們自訂的廣播接收器比較簡單,僅僅在onReceive()方法中列印了一句話而已。接下來我們在AndroidManifest.xml檔案中配置CustomBroadcastReceiver,我們讓該Receiver接受指定action的廣播,代碼如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.llew.seetao.a" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.llew.seetao.a.MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <receiver android:name="com.llew.seetao.a.CustomBroadcastReceiver"> <intent-filter> <action android:name="com.llew.seetao.customaction"/> </intent-filter> </receiver> </application></manifest>
好了,在AndroidManifest.xml檔案配置完我們的廣播接收器後,我們可以發送一個廣播了。先在布局檔案activity_main.xml加入一個可以響應事件的Button,代碼如下:
<?xml version="1.0" encoding="utf-8"?><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#aabbcc" > <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:onClick="sendBroadcast" android:text="測試廣播A程式" /></FrameLayout>
在布局檔案中我們使用了View的onClick屬性(若你對該屬性的用法不太清楚請自行查閱),MainActivity的代碼如下:
public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public void sendBroadcast(View v) {Intent intent = new Intent(MainActivity.this, CustomBroadcastReceiver.class);this.sendBroadcast(intent);}} 以上步驟完成之後就可以運行項目了,開啟模擬器或者連結手機,運行一下,效果如所示:
點擊按鈕,會發現在logcat下有一行日誌輸出,如所示:
到這裡我們可以說只是掌握了對廣播的使用,如果在項目中我們也是按照同樣的方式使用了廣播,那麼你的項目可能會存在風險。為什麼這麼說,下面我們來假設一個情境,假如別人反編譯了我們的APK包,看到了我們在設定檔中的這個廣播,那他就可以在自己的應用中也發送一個廣播,因為我們配置的廣播含有intent-filter,所以可以隱式的調起這個廣播,那麼我們的APK會不會做出反應呢?為了做這個測試,我們建立一個工程B(把剛剛的稱為工程A),只要B包名不和A包名相同就行,這樣這倆工程就可以同時跑在一個裝置上了,所以在B工程中通過隱式發送廣播的方式,代碼如下:
public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public void sendBroadcast(View v) {Intent intent = new Intent("com.llew.e.customaction");this.sendBroadcast(intent);}} 我們運行運行一下B程式,點擊按鈕,通過查看logcat的輸出,oh,my god,果真工程A中的log列印出來了,如所示:
從logcat的列印結果可以看出,我們通過隱式的發送廣播確實可以讓A工程的廣播接收器做出響應,這樣的APP如果發版了確實是存在安全隱患的,那有沒有解決方式呢?答案是肯定的,我們接下來就看一下3種解決方式:
- 給廣播接收器添加export屬性
<receiver android:name="com.llew.seetao.a.CustomBroadcastReceiver" android:exported="false"> <intent-filter> <action android:name="com.llew.seetao.customaction"/> </intent-filter></receiver>
在上邊的代碼中我們只是在註冊我們的CustomBroadReceiver的時候添加了export屬性,並把該屬性的值設定為false,難道僅僅就添加一個屬性就OK啦?可能有的童靴還是會產生疑問,為了打消童靴們的疑慮,我們再運行一下代碼,查看一下結果,運行結果如所示:
通過分別運行工程A和工程B下的logcat輸出日誌可以看出,給我們自訂的receiver添加了export屬性後確實不能通過這種方式來使我們的廣播接收器發生響應了。那這個屬性到底是幹嘛使的了?可能有的童靴又會有這樣的疑問了,我們來看一下官方文檔對這個export屬性是怎麼解釋的。
英語水平有限,大致翻譯一下官方文檔:
當前的廣播接收器是否可以從外部應用接受訊息,如果為true表示可以從外部應用接受訊息,如果為false,表示當前BroadcastReceiver只能從當前應用或者是擁有相同userID的應用接收廣播。預設值是根據當前BroadcastReceiver是否包含有intent-filter來決定的,如果沒有任何intent-filter的話,只能通過類名來調起,此時預設值為false,如果包含有intent-filter,這預設值為true。不僅這個屬性可以指定BroadcastReceiver是否暴露給其他使用者,你也可以使用permission來限制外部應用給當前應用的receiver發送訊息。
在官方文檔結尾又說了除了使用export屬性外,我們還可以以添加許可權的方式來限制外部參考給我們的receiver發送訊息,那我們就接著往下看是如何使用許可權的。
- 給廣播接收器添加自訂許可權
a)首先自訂許可權,代碼如下:
<permission android:name="com.llew.seetao.permission.customaction" android:protectionLevel="normal"></permission>
b)其次給receiver添加permission許可權,代碼如下:
<receiver android:name="com.llew.seetao.a.CustomBroadcastReceiver" android:permission="com.llew.seetao.permission.customaction"> <intent-filter> <action android:name="com.llew.seetao.customaction"/> </intent-filter></receiver>
c)運行代碼,查看結果如下:
通過觀察確實發現了通過添加自訂許可權的方式那麼B程式確實是調用不起來A程式中的廣播的,呵呵,看到這裡我們高興的小心臟撲通撲通直跳。如果是這樣,那你就錯了,因為在A程式中我們確實給我們自訂的廣播添加了自訂許可權,就是說擁有該許可權的應用才可以,那假如我們在B程式中也同樣定義了同樣的許可權,那結果會是神馬樣子呢?我們繼續來做實驗驗證一下,這次我們也把B程式添加同樣的許可權,然後再使用許可權,代碼如下:<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.llew.seetao.b" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8"/> <permission android:name="com.llew.seetao.permission.customaction" android:protectionLevel="normal"></permission> <uses-permission android:name="com.llew.seetao.permission.customaction"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name="com.llew.seetao.b.MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application></manifest>
我們通過這樣方式後再分別運行一下程式,分別運行一下,運行結果如下:
唉,fuck,B程式又可以調起我們的廣播,這時候你可能心中有一萬匹草泥馬在奔騰了,這廣播還讓人不讓人用了,呵呵,我們這片文章就是從安全的角度來講解BroadcastReceiver的,肯定是有對策啦,還記得我們在A程式中定義的許可權嗎?如果你不記得了,代碼如下:<permission android:name="com.llew.seetao.permission.customaction" android:protectionLevel="normal"></permission>
定義我們自己的許可權時我們給許可權使用的層級是normal,如果你對有關許可權的層級不是太理解或不屬性,可以去官網查看,這裡不再詳述了,通過查閱官網可知,權限等級有個為signature的,也就是說如果我們在自訂許可權的時候把權限等級設定為signature時,只有擁有相同簽名的APP才能調起我們的應用,這就相對的保證了安全,因為我們發版的時候肯定用的是自己的簽名,別人一般情況下是無法擷取到我們的簽名的,所以這種方法是可行的。這時候你肯定心裡樂開了花,真是魔高一尺道高一丈呀,別著急,我們還有另外一種更可靠高效的解決方式,這也是我開發中一直用到的,請繼續往閱讀(*^__^*) ……
- 使用官方推薦的LocalBroadcastManager
LocalBroadcastManager是Android v4包中的(如果你不想引入v4包,可以直接從源碼中把這個類拷貝出來),它比起全域廣播有以下優勢:
(1).廣播只會在你的應用內發送,無需擔心資料泄露,更加安全
(2).其他應用無法發送廣播給你的應用,不用擔心你的應用存在安全性漏洞
(3).相比全域廣播,它不需要發送給整個系統,所以更加高效
好了,說了這麼多,我們趕緊看看這個神一般的LocalBroadcastManager到底怎麼用吧?還是使用上文定義的CustomBroadcastReceiver,在onReceive()方法中我們僅僅列印了目前時間,代碼如下:public class CustomBroadcastReceiver extends BroadcastReceiver {public static final String LOCAL_CUSTOM_ACTION = "com.llew.seetao.customaction";public CustomBroadcastReceiver() {}@Overridepublic void onReceive(Context context, Intent intent) {Log.e(this.getClass().getSimpleName(), "current time is :" + System.currentTimeMillis());}} 然後我們看一下在MainActivity中的代碼,如下所示:public class MainActivity extends Activity {private LocalBroadcastManager mBroadcastManager;private CustomBroadcastReceiver mLocalReceiver;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);registerBroadcastReceiver();}private void registerBroadcastReceiver() {mBroadcastManager = LocalBroadcastManager.getInstance(MainActivity.this);mLocalReceiver = new CustomBroadcastReceiver();IntentFilter filter = new IntentFilter(CustomBroadcastReceiver.LOCAL_CUSTOM_ACTION);mBroadcastManager.registerReceiver(mLocalReceiver, filter);}public void sendBroadcast(View v) {Intent intent = new Intent(CustomBroadcastReceiver.LOCAL_CUSTOM_ACTION);mBroadcastManager.sendBroadcast(intent);}@Overrideprotected void onDestroy() {super.onDestroy();if(null != mBroadcastManager) {mBroadcastManager.unregisterReceiver(mLocalReceiver);}}} 我們在MainActivity的onCreate()方法中通過LocalBroadcastManger.getInstance()的方式執行個體化了mBroadcastManager對象,然後通過mBroadcastManager對象調用register()方法來註冊我們的廣播接收器,最後發送廣播的代碼是調用了mBroadcastManger的sendBroadcast()方法,在onDestroy()方法中又調用了mBroadcastManager的unregisterReceiver()方法,我們來運行一下代碼,運行結果如所示:
通過運行結果可知使用LocalBroadcastManager是正常的,那究竟為什麼說使用這種方式是安全高效的了?由於本篇博文篇幅有點長,在下一篇文章中我會從源碼的角度來分析為什麼說使用LocalBroadcastManager是安全和高效的,敬請期待......
-
Android 源碼系列之<二>從安全的角度深入理解BroadcastReceiver(上)