編輯:關於Android編程
Android中的每個應用程序都可以對自己感興趣的廣播進行注冊,這樣該程序就只會接收到自己所關心的廣播內容,這些廣播可能是來自於系統的,也可能是來自於其他應用程序的。Android提供了一套完整的API,允許應用程序自由地發送和接收廣播。發送廣播的方法就是借助Intent,而接收廣播的方法則借助廣播接收器(Broadcast Receiver)。
Android中的廣播主要可以分為兩種類型,標准廣播和有序廣播。
標准廣播(Normal broadcasts)是一種完全異步執行的廣播,在廣播發出之後,所有的廣播接收器幾乎都會在同一時刻接收到這條廣播消息,因此它們之間沒有任何先後順序可言。這種廣播的效率會比較高,但同時也意味著它是無法被截斷的。標准廣播的工作流程如圖5.1所示。
有序廣播(Ordered broadcasts)則是一種同步執行的廣播,在廣播發出之後,同一時刻只會有一個廣播接收器能夠收到這條廣播消息,當這個廣播接收器中的邏輯執行完畢後,廣播才會繼續傳遞。所以此時的廣播接收器是有先後順序的,優先級高的廣播接收器就可以先收到廣播消息,並且前面的廣播接收器還可以截斷正在傳遞的廣播,這樣後面的廣播接收器就無法收到廣播消息了。有序廣播的工作流程如圖5.2所示。
廣播接收器可以自由地對自己感興趣的廣播進行注冊,這樣當有相應的廣播發出時,廣播接收器就能夠收到該廣播,並在內部處理相應的邏輯。注冊廣播的方式一般有兩種,在代碼中注冊和在AndroidManifest.xml中注冊,其中前者也被稱為動態注冊,後者也被稱為靜態注冊。
那麼該如何創建一個廣播接收器呢?其實只需要新建一個類,讓它繼承自BroadcastReceiver,並重寫父類的onReceive()方法就行了。這樣當有廣播到來時,onReceive()方法就會得到執行,具體的邏輯就可以在這個方法中處理。
那我們就先通過動態注冊的方式編寫一個能夠監聽網絡變化的程序,借此學習一下廣播接收器的基本用法吧。新建一個 BroadcastTest項目,然後修改MainActivity中的代碼,如下所示:
public class MainActivity extends Activity { private IntentFilter intentFilter; private NetworkChangeReceiver networkChangeReceiver; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); intentFilter = new IntentFilter();//創建了一個IntentFilter的實例 intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");//給這個IntentFilter添加了一個值為android.net.conn.CONNECTIVITY_CHANGE的action networkChangeReceiver = new NetworkChangeReceiver();//創建了一個NetworkChangeReceiver的實例 registerReceiver(networkChangeReceiver, intentFilter);//調用registerReceiver() 方法進行注冊,將 NetworkChangeReceiver的實例和IntentFilter的實例都傳了進去 } //動態注冊的廣播接收器一定都要取消注冊才行,這裡我們是在onDestroy()方法中通過調用unregisterReceiver()方法來實現的。 @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(networkChangeReceiver); } class NetworkChangeReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) {//當網絡狀態發生變化時,onReceive()方法就會得到執行 //通過getSystemService()方法得到了ConnectivityManager的實例,這是一個系統服務類,專門用於管理網絡連接的。 ConnectivityManager connectionManager = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); //調用它的getActiveNetworkInfo() 方法可以得到 NetworkInfo的實例 NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo(); //調用NetworkInfo 的isAvailable()方法,就可以判斷出當前是否有網絡了 if (networkInfo != null && networkInfo.isAvailable()) { Toast.makeText(context, "network is available",Toast.LENGTH_SHORT).show(); } else { Toast.makeText(context, "network is unavailable",Toast.LENGTH_SHORT).show(); } } } }
另外,這裡有非常重要的一點需要說明,Android系統為了保證應用程序的安全性做了規定,如果程序需要訪問一些系統的關鍵性信息,必須在配置文件中聲明權限才可以,否則程序將會直接崩潰,比如這裡查詢系統的網絡狀態就是需要聲明權限的。打開AndroidManifest.xml文件,在裡面加入如下權限就可以查詢系統網絡狀態了:
動態注冊的廣播接收器可以自由地控制注冊與注銷,在靈活性方面有很大的優勢,但是它也存在著一個缺點,即必須要在程序啟動之後才能接收到廣播,因為注冊的邏輯是寫在onCreate()方法中的。那麼有沒有什麼辦法可以讓程序在未啟動的情況下就能接收到廣播呢?這就需要使用靜態注冊的方式了。
這裡我們准備讓程序接收一條開機廣播,當收到這條廣播時就可以在onReceive()方法裡執行相應的邏輯,從而實現開機啟動的功能。新建一個BootCompleteReceiver繼承自BroadcastReceiver,代碼如下所示:
public class BootCompleteReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "Boot Complete", Toast.LENGTH_LONG).show(); } }
可以看到,這裡不再使用內部類的方式來定義廣播接收器,因為稍後我們需要在AndroidManifest.xml中將這個廣播接收器的類名注冊進去。在onReceive()方法中,還是簡單地使用Toast彈出一段提示信息。
然後修改AndroidManifest.xml文件,代碼如下所示:
…… …… //標簽內出現了一個新的標簽,所有靜態注冊的廣播接收器都是在這裡進行注冊的。 //通過android:name來指定具體注冊哪一個廣播接收器 //在 標簽裡加入想要接收的廣播就行了
另外,監聽系統開機廣播也是需要聲明權限的,可以看到,我們使用標簽又加入了一條android.permission.RECEIVE_BOOT_COMPLETED權限。
需要注意的是,不要在onReceive()方法中添加過多的邏輯或者進行任何的耗時操作,因為在廣播接收器中是不允許開啟線程的,當onReceive()方法運行了較長時間而沒有結束時,程序就會報錯。因此廣播接收器更多的是扮演一種打開程序其他組件的角色,比如創建一條狀態欄通知,或者啟動一個服務等。
例子:Intent發送標准廣播
public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "received in MyBroadcastReceiver", Toast.LENGTH_SHORT).show(); } }Intent intent = new Intent("com.example.broadcasttest. MY_BROADCAST"); sendBroadcast(intent);
廣播是一種可以跨進程的通信方式,這一點從前面接收系統廣播的時候就可以看出來了。因此在我們應用程序內發出的廣播,其他的應用程序應該也是可以收到的。
修改尚一個例子:
Intent intent = new Intent("com.example.broadcasttest. MY_BROADCAST"); sendOrderedBroadcast(intent, null);//sendOrderedBroadcast()方法接收兩個參數,第一個參數仍然是Intent,第二個參數是一個與權限相關的字符串,這裡傳入null就行了。 那麼該如何設定廣播接收器的先後順序呢?當然是在注冊的時候進行設定的了,修改AndroidManifest.xml中的代碼,如下所示://通過android:priority 屬性給廣播接收器設置了優先級,優先級比較高的廣播接收器就可以先收到廣播。
既然已經獲得了接收廣播的優先權,那麼 MyBroadcastReceiver就可以選擇是否允許廣播繼續傳遞了。修改 MyBroadcastReceiver中的代碼,如下所示:
public class MyBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "received in MyBroadcastReceive", Toast.LENGTH_SHORT).show(); abortBroadcast(); } }
如果在onReceive() 方法中調用了 abortBroadcast()方法,就表示將這條廣播截斷,後面的廣播接收器將無法再接收到這條廣播。
前面我們發送和接收的廣播全部都是屬於系統全局廣播,即發出的廣播可以被其他任何的任何應用程序接收到,並且我們也可以接收來自於其他任何應用程序的廣播。這樣就很容易會引起安全性的問題,比如說我們發送的一些攜帶關鍵性數據的廣播有可能被其他的應用程序截獲,或者其他的程序不停地向我們的廣播接收器裡發送各種垃圾廣播。
為了能夠簡單地解決廣播的安全性問題,Android引入了一套本地廣播機制,使用這個機制發出的廣播只能夠在應用程序的內部進行傳遞,並且廣播接收器也只能接收來自本應用程序發出的廣播,這樣所有的安全性問題就都不存在了。
本地廣播的用法並不復雜,主要就是使用了一個 LocalBroadcastManager來對廣播進行管理,並提供了發送廣播和注冊廣播接收器的方法。下面我們就通過具體的實例來嘗試一下它的用法,修改 MainActivity中的代碼,如下所示:
public class MainActivity extends Activity { private IntentFilter intentFilter; private LocalReceiver localReceiver; private LocalBroadcastManager localBroadcastManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); localBroadcastManager = LocalBroadcastManager.getInstance(this); // 獲取實例 Button button = (Button) findViewById(R.id.button); button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent("com.example.broadcasttest. LOCAL_BROADCAST"); localBroadcastManager.sendBroadcast(intent); // 發送本地廣播 } }); intentFilter = new IntentFilter(); intentFilter.addAction("com.example.broadcasttest.LOCAL_BROADCAST"); localReceiver = new LocalReceiver(); localBroadcastManager.registerReceiver(localReceiver, intentFilter); // 注冊本地廣播監聽器 } @Override protected void onDestroy() { super.onDestroy(); localBroadcastManager.unregisterReceiver(localReceiver); } class LocalReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Toast.makeText(context, "received local broadcast", Toast.LENGTH_SHORT).show(); } } }
另外還有一點需要說明,本地廣播是無法通過靜態注冊的方式來接收的。其實這也完全可以理解,因為靜態注冊主要就是為了讓程序在未啟動的情況下也能收到廣播,而發送本地廣播時,我們的程序肯定是已經啟動了,因此也完全不需要使用靜態注冊的功能。
使用本地廣播的幾點優勢吧。
1.可以明確地知道正在發送的廣播不會離開我們的程序,因此不需要擔心機密數據洩漏的問題。
2.其他的程序無法將廣播發送到我們程序的內部,因此不需要擔心會有安全漏洞的隱患。
3.發送本地廣播比起發送系統全局廣播將會更加高效。
廣播的最佳實踐——實現強制下線功能
例子:
省略部分代碼……
創建一個廣播接收器了,新建 ForceOfflineReceiver繼承自BroadcastReceiver ,代碼如下所示:
public class ForceOfflineReceiver extends BroadcastReceiver { @Override public void onReceive(final Context context, Intent intent) { AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); dialogBuilder.setTitle("Warning"); dialogBuilder.setMessage("You are forced to be offline. Please try to login again."); dialogBuilder.setCancelable(false); dialogBuilder.setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ActivityCollector.finishAll(); // 銷毀所有活動 Intent intent = new Intent(context, LoginActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); // 重新啟動 LoginActivity } }); AlertDialog alertDialog = dialogBuilder.create(); // 需要設置 AlertDialog的類型,保證在廣播接收器中可以正常彈出 alertDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); alertDialog.show(); } }
由於我們是在廣播接收器裡啟動活動的,因此一定要給Intent加入 FLAG_ACTIVITY_NEW_TASK這個標志。最後,還需要把對話框的類型設為 TYPE_SYSTEM_ALERT,不然它將無法在廣播接收器裡彈出。
對AndroidManifest.xml 文件進行配置,代碼如下所示:
一、問題描述 使用BordercastReceiver和Service組件實現下述功能:1.當手機處於來電狀態,啟動監聽服務,對來電進行監聽錄音。2.設置電話黑名單,當
本文由阿裡巴巴移動安全客戶端、YunOS資深工程師Hao(嵌入式企鵝圈原創團隊成員)撰寫,是Hao在嵌入式企鵝圈發表的第一篇原創文章,對Android無線開發
android 的多線程實際上就是java的多線程。android的UI線程又稱為主線程。 首先是Thread 和 Runnable: Thread才是一個線程,而Run
上一篇博客中我們已經繪制出了一個直角三角形,雖然我們相對於坐標,我們設置的直角三角形的兩腰是相等的,但是實際上展示出來的卻並不是這樣,雖然通過計算,我們可以把三角形的兩腰