Android核心組件之BroadCastReceiver

什麼是BroadCastReceiver

          BroadcastReceiver 中文意思是“廣播接收者”,它主要是用於接收應用程序或是系統發送過來的廣播並根據廣播內容進行相關響應的一類組件。Broadcast在Android系統中應用的非常廣泛,比如電池狀態的變化、電話的接收、短信的接收、鍵盤輸入法切換和網絡連接狀態變化都會由系統發送一個廣播,然後應用程序通過BroadCastReceiver可以監聽這些廣播並作出相應的處理。此外,BroadCastReceiver可以通過監聽其它應用程序發送的廣播接收傳遞過來的信息進而實現進程間的通信,我們再查看一下BroadcastReceiver的源碼可以看到IBinder影子,就不難理解其中的原理了(Android進程通信是用IBinder實現的)。
BroadCastReceiver的靜態註冊和動態註冊
     要實現一個BroadcastReceiver,就必須和使用Android其它組件一樣(Activity、Service和ContentProvider),得在AndroidManifest.xml中進行註冊。下面舉一個示例,在AndroidManifest.xml中註冊BroadcastReceiver,代碼如下:
  1. <receiver android:name=".broadcastreceiver.NormalBroadReceiver" >  
  2.             <intent-filter >  
  3.                 <action android:name="com.fendou.NORMAL_BROADCASTRECEIVER" />  
  4.             </intent-filter>  
  5.  </receiver>  
以上代碼中<intent-filter>中的<action>主要作用是充當廣播過濾器的一個過濾標籤,可以是自定義也可以由系統提供,並且一個<intent-filter>中可以有多個<action>。BroadcastReceiver除了可以在xml文件中註冊外,還可以以代碼的形式在類中註冊,示例代碼如下:
  1. //註冊BroadcastReceiver方法  
  2.     public void filter()  
  3.     {  
  4.          IntentFilter mFilter = new IntentFilter();  
  5.          mFilter.addAction("com.fendou.NORMAL_BROADCASTRECEIVER" );  
  6.          registerReceiver( mBroadcastReceiver, mFilter);  
  7.     }  
  8.      
  9.     //實例化BroadcastReceiver  
  10.     BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {  
  11.            
  12.           @Override  
  13.           public void onReceive(Context context, Intent intent) {  
  14.               // TODO Auto-generated method stub  
  15.               if(intent.getAction().equals("com.fendou.NORMAL_BROADCASTRECEIVER" ))  
  16.              {  
  17.                   //dosomething  
  18.              }  
  19.          }  
  20.     };  
以代碼方式註冊,首先我們得實例化一個Intentfilter和一個BroadcastReceiver對象,然後使用registerReceiver()方法將BroadcastReceiver註冊到系統中,很簡單吧。對於這兩種註冊BroadcastReceiver的方式,我們一般將前者稱爲靜態註冊,後者則稱爲動態註冊,兩者的區別就是前者註冊是屬於常駐型,就是在應用程序一開啓,以該種類型註冊的BroadcastReceiver就可以一直接收到系統或其它應用發送過來的廣播,即使它的宿主應用退出系統不再中運行,該類型的BroadcastReceiver也能照常工作。而後者由於在類中註冊的,所以它的生命週期是跟隨類對象的,若該類對象被停止銷燬了,在該類中註冊的BroadcastReceiver也會隨之銷燬。兩種註冊方式都有各自的特點,我們可以根據具體情況來選擇需要使用哪種註冊方式。

BroadCast的分類

前面講解了BroadcastReceiver的註冊,那麼我們可以自定義廣播來向註冊的BroadcastReceiver發送廣播,發送廣播的方式一般分爲以下四種:

<1>普通廣播

     發送普通廣播是最常見也是使用最多的一種廣播發送方式,它的特點就是當有多個BroadcastReceiver同時接收一個廣播的時候,它們都是異步接收的的,換句話說就是各個BroadcastReceiver之間接收廣播的時候互不干擾,互不影響,由此便可知該廣播對於每個BroadcastReceiver來說,各自都無法阻止其它BroadcastReceiver的接收動作。

<2>有序廣播

     有序廣播相對於普通廣播的區別就是當有多個BroadcastReceiver同時接收同一個廣播時,它們之間會根據註冊時在<intent-filter>中設置的android:priority=""優先級屬性來排列接收廣播的順序,該屬性的數值越大,優先級越高,取值範圍是在-1000到1000之間。當沒有設置優先級時,接收順序是隨機的。
     爲了比較普通廣播和有序廣播的區別,我們接下來使用一個實例來實驗一下,以便我們理解得更加深刻。我們打算分別使用(sendBroadcastReceiver())發送普通廣播和發送有序廣播(sendOrderBroadcastReceiver())的方式同時發送給三個BroadcastReceiver,然後檢驗兩種廣播發送方式的特點和不同點。首先定義三個BroadcastReceiver,分別是
OrderBroadcastReceiverFirst、OrderBroadcastReceiverSecond和OrderBroadcastReceiverThird,併爲它們設置優先級,如下是它們的註冊代碼:
  1. <!-- android:priority屬性是爲每個BroadcastReceiver設置優先級,值越大優先級越大 -->  
  2.          <receiver android:name=".broadcastreceiver.OrderBroadCastReceiverFirst" >  
  3.             <intent-filter android:priority="500" >  
  4.                 <action android:name="com.fendou.ORDER_NORMALBROADCASTRECEIVER" />  
  5.             </intent-filter>  
  6.         </receiver>  
  7.         <receiver android:name=".broadcastreceiver.OrderBroadCastReceiverSecond" >  
  8.             <intent-filter android:priority="400" >  
  9.                 <action android:name="com.fendou.ORDER_NORMALBROADCASTRECEIVER" />  
  10.             </intent-filter>  
  11.         </receiver>  
  12.          <receiver android:name=".broadcastreceiver.OrderBroadCastReceiverThird" >  
  13.             <intent-filter android:priority="300" >  
  14.                 <action android:name="com.fendou.ORDER_NORMALBROADCASTRECEIVER" />  
  15.             </intent-filter>  
  16.         </receiver>  
然後再看三個BroadcastReceiver的接收代碼,如下:
OrderBroadcastReceiverFirst.java:
  1. public class OrderBroadCastReceiverFirst extends BroadcastReceiver {  
  2.   
  3.     private static final String TAG = "ygh";  
  4.     @Override  
  5.     public void onReceive(Context context, Intent intent) {  
  6.           // TODO Auto-generated method stub  
  7.          if(intent.getAction().equals(StaticContens.ORDER_NORMAL_BROADCASTRECEIVER))  
  8.          {  
  9.              String info = intent.getStringExtra("info" );  
  10.              Log. i(TAG, "OrderBroadCastReceiverFirst  "+ info);  
  11.              info += " First;";  
  12.              Log. i(TAG, "OrderBroadCastReceiverFirst  "+ info);  
  13.               //設置結果數據  
  14.              setResultData(info);                 
  15.          }  
  16.     }  
  17. }  
OrderBroadcastReceiverSecond.java:
  1. public class OrderBroadCastReceiverSecond extends BroadcastReceiver {  
  2.   
  3.     private static final String TAG = "ygh";  
  4.     @Override  
  5.     public void onReceive(Context context, Intent intent) {  
  6.           // TODO Auto-generated method stub  
  7.          if(intent.getAction().equals(StaticContens.ORDER_NORMAL_BROADCASTRECEIVER))  
  8.          {  
  9.                
  10.               //得到從OrderBroadCastReceiverFirst設置的結果值  
  11.              String info = getResultData();  
  12.                
  13.              info += "Second";  
  14.              Log. i(TAG, "OrderBroadCastReceiverSecond  " + info);  
  15.               //設置結果數據  
  16.              setResultData(info);                 
  17.          }  
  18.     }  
  19. }  
OrderBroadcastReceiverThird.java:
  1. public class OrderBroadCastReceiverThird extends BroadcastReceiver {  
  2.   
  3.     private static final String TAG = "ygh";  
  4.     @Override  
  5.     public void onReceive(Context context, Intent intent) {  
  6.           // TODO Auto-generated method stub  
  7.          if(intent.getAction().equals(StaticContens.ORDER_NORMAL_BROADCASTRECEIVER))  
  8.          {     
  9.               //得到從OrderBroadCastReceiverFirst設置的結果值  
  10.              String info = getResultData();  
  11.              info += "; Third";  
  12.              Log. i(TAG, "OrderBroadCastReceiverThird  "+ info);          
  13.               //show一個Notification  
  14.              StaticContens. showNotification(context,"新廣播""您接收了一條新廣播" );  
  15.          }  
  16.     }  
  17. }  
然後再看兩者發送廣播的方法,代碼如下:
  1. //發送一個普通廣播  
  2.     public void sendBroadcastReceiverMethod()  
  3.     {  
  4.     //實例化一個Intent對象  
  5.     Intent mIntent = new Intent();  
  6.         //mIntent.setAction(StaticContens.NORMAL_BROADCASTRECEIVER);  
  7.         mIntent.setAction(StaticContens.ORDER_NORMAL_BROADCASTRECEIVER);  
  8.     mIntent.putExtra( "info""Hello" );  
  9.     //發送普通廣播  
  10.     this.sendBroadcast(mIntent);  
  11.     }  
  12.     //發送一個有序廣播  
  13.     public void sendOrderBroadcastReceiverMethod()  
  14.     {  
  15.     //實例化一個Intent對象  
  16.     Intent mIntent = new Intent();  
  17.         mIntent.setAction(StaticContens.ORDER_NORMAL_BROADCASTRECEIVER);  
  18.     mIntent.putExtra( "info""Hello" );  
  19.     //發送有序廣播  
  20.     this.sendOrderedBroadcast(mIntent, "com.fendou.order_broadcastreceiver_permission" );  
  21.     }  
界面有兩個按鈕,點擊分別調用以上兩個方法,界面截圖如下:

好,我們下面開始做實驗,首先點擊“NormalBroadcastReceiver”按鈕,觀察控制檯打印的Log信息如下:

然後我們再點擊"OrderBroadcastReceiver"按鈕,觀察控制檯打印的Log信息如下:

咱們仔細分析下,我們分別在三個BroadcastReceiver中設置了setResultData()目的就是爲了將結果傳給排在後面的BroadcastReceiver,排在後面的BroadcastReceiver使用getResultData()方法得到上一個BroadcastReceiver傳遞過來的結果。我們發現,發送普通廣播接受到的值都是null,可知發送普通廣播,三個BroadcastReceiver接受廣播都是無序的,互不干涉。而發送有序廣播都成功接收到了上一個BroadcastReceiver設置的結果,所以我們確定發送有序廣播時,三個BroadcastReceiver都是有序進行接收的。

<3>普通粘性廣播

    發送普通粘性廣播其實和發送普通廣播效果是差不多的,唯一一個區別就是發送粘性廣播後,其發送的intent一直在應用程序中保存着,甚至Broadcast已經停止,而其它的BroadcastReceiver可以通過registerReceiver()方法的返回值得到intent。可能這樣解釋比較難以理解,我們就通過一個實例來驗證一下吧。我們實驗的內容是分別發送一個普通廣播和一個普通粘性廣播給同一個BroadcastReceiver,但是發送的時候該BroadcastReceiver還沒有進行註冊,發送完畢後再跳轉至一個新的Activity,在該Activity中動態註冊了能夠接收剛剛發送的普通廣播和普通粘性廣播的BroadcastReceiver,該BroadcastReceiver得到接收的廣播的Action,觀察是接收到了普通廣播還是普通粘性廣播。首先,創建一個新的Activity類(ReceiveActivity),然後該Activity類中動態註冊一個BroadcastReceiver,具體代碼如下:
  1. package com.fendou.activity;  
  2.   
  3. import com.fendou.R;  
  4. import com.fendou.utils.StaticContens;  
  5.   
  6. import android.app.Activity;  
  7. import android.content.BroadcastReceiver;  
  8. import android.content.Context;  
  9. import android.content.Intent;  
  10. import android.content.IntentFilter;  
  11. import android.os.Bundle;  
  12. import android.util.Log;  
  13. import android.widget.TextView;  
  14.   
  15. public class ReceiveActivity extends Activity {  
  16.       
  17.     //聲明控件對象  
  18.     private TextView mTextView;  
  19.       
  20.     private static final String TAG = "ygh";  
  21.     @Override  
  22.     protected void onCreate(Bundle savedInstanceState) {  
  23.           // TODO Auto-generated method stub  
  24.           super.onCreate(savedInstanceState);  
  25.          setContentView(R.layout. receive);  
  26.           //根據ID得到代表該控件的對象  
  27.           mTextView = (TextView)findViewById(R.id.show_action_textview );  
  28.     }  
  29.       
  30.       
  31.     @Override  
  32.     protected void onResume() {  
  33.           // TODO Auto-generated method stub  
  34.           super.onResume();  
  35.           //實例化過濾器對象  
  36.          IntentFilter mFilter = new IntentFilter();  
  37.           //添加Action  
  38.         mFilter.addAction(StaticContens. SEND_USE_NORMAL_RECEIVER);  
  39.         mFilter.addAction(StaticContens. SEND_USE_STICKY_RECEIVER);  
  40.           //註冊廣播  
  41.          registerReceiver( mBroadcastReceiver, mFilter);  
  42.     }  
  43.       
  44.     @Override  
  45.     protected void onDestroy() {  
  46.           // TODO Auto-generated method stub  
  47.           super.onDestroy();  
  48.           //解除註冊  
  49.          unregisterReceiver( mBroadcastReceiver);  
  50.     }  
  51.     //實例化BroadcastReceiver對象  
  52.     BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {  
  53.            
  54.           @Override  
  55.           public void onReceive(Context context, Intent intent) {  
  56.               // TODO Auto-generated method stub  
  57.               //獲取發送過來的Action  
  58.              String action = intent.getAction();  
  59.                
  60.              Log. i(TAG, "The Receive Action is: "+ action);  
  61.               mTextView.setText("The Receive Action is: "+ action);  
  62.                
  63.          }  
  64.     };  
  65. }  
然後,在MainActivity類中創建三個按鈕,分別爲“Send Use NormalReceiver”、“Send Use StickyNormalReceiver”和“Start ReceiveActivity”按鈕,並分別爲它們設置監聽並分別調用以下方法,代碼如下:
  1. //發送粘性廣播  
  2.     public void sendUseStickyBroadcastReceiverMethod()  
  3.     {  
  4.     //實例化Intent對象  
  5.     Intent mIntent = new Intent();  
  6.     //設置Action  
  7.         mIntent.setAction(StaticContens.SEND_USE_STICKY_RECEIVER);  
  8.     //發送廣播  
  9.     this.sendStickyBroadcast(mIntent);  
  10.     }  
  11.     //發送普通廣播  
  12.     public void sendUseNormalBroadcastReceiverMethod()  
  13.     {  
  14.     //實例化Intent對象  
  15.     Intent mIntent = new Intent();  
  16.     //設置Action  
  17.         mIntent.setAction(StaticContens.SEND_USE_NORMAL_RECEIVER);  
  18.     //發送廣播  
  19.     this.sendBroadcast(mIntent);  
  20.     }  
  21.     //開啓ReceiveActivity  
  22.     public void startReceiveActivity()  
  23.     {  
  24.     //實例化Intent對象  
  25.     Intent mIntent = new Intent();  
  26.     mIntent.setClass(getApplicationContext(), ReceiveActivity.class );  
  27.     //開啓Activity  
  28.     this.startActivity(mIntent);  
  29.     }  
最後注意使用粘性廣播的時候注意要在AndroidManifest.xml中加入使用權限,加上如下權限:
  1. <!-- 使用BroadcastReceiver Sticky權限 -->  
  2.  <uses-permission android:name="android.permission.BROADCAST_STICKY" />  
界面有三個按鈕,分別調用以上三個方法,界面截圖如下:

接下來我們開始實驗,依照上面的圖按順序依次點擊這三個按鈕,前兩個是分別發送了一個普通廣播和一個普通粘性廣播,最後一個按鈕是跳轉至ReceiveActivity。然後,我們看一下效果圖

我們看到ReceiveActivity顯示的是“com.fendou.SEND_USE_STICKY_RECEIVE”,說明動態註冊的BroadcastReceiver可以接收到的是普通粘性廣播而普通廣播無法接收到,那麼由此我們就能很好的理解普通粘性廣播的與普通廣播的區別了:普通粘性廣播發送後其持有的intent對象一直保存在程序中,直到後面某些時候動態註冊了BroadcastReceiver用來接收,也可以成功接收到此之前發送的粘性廣播,而發送的普通廣播卻不具有這樣的特性。

<4>有序粘性廣播

     有序粘性廣播其實也和有序廣播的特點是差不多的,只不過添加了粘性的特點,該特點在上面已經講了,這裏就不再舉實例來講解了。

接收系統BroadCast實例講解

     之前詳細講解了BroadcastReceiver的使用方法和原理,也使用實例來講解了如何自定義發送廣播和創建BroadcastReceiver用於接收廣播。接下來我們開始學習如何使用我們創建的BroadcastReceiver來監聽並接收系統發送的各種各樣的廣播,我們的應用通過接收各類型的系統廣播在特定時刻執行相關操作。Android在很多情況下都會使用廣播的方式向外界發送信息,比如電量過低、電話的接收、短信的接收和網絡連接狀態的變化等。以下列舉了Android常見的系統廣播Action常量(詳細內容請參考Android API文檔中關於Intent的詳細用法):

1、ACTION_TIME_CHANGED:系統時間被改變。

2、ACTION_DATE_CHANGED:系統日期被改變。

3、ACTION_TIMEZONE_CHANGED:系統時區被改變。

4、ACTION_BOOT_COMPLETED:系統啓動完成。

5、ACTION_PACKAGE_ADDED:系統中添加安裝包。

6、ACTION_PACKAGE_CHANGED:系統的包被改變了。

7、ACTION_PACKAGE_REMOVED:系統的包被刪除了。

8、ACTION_PACKAGE_RESTARTED:系統包被重啓。

9、ACTION_PACKAGE_DATA_CLEARED:系統的包數據被清空。

10、ACTION_BATTERY_CHANGED:電池電量改變。

11、ACTION_SHUTDOWN:系統被關閉。

12、ACTION_BATTRY_LOW:電池電量低。
13、ACTION_AIRPLANE_MODE_CHANGE:飛行模式的狀態改變。
14、CONNECTIVITY_SERVICE:網絡連接狀態改變。
系統廣播常量非常的多,在這裏就不一一詳細介紹了,我們舉兩個例子來示範下就行:

<1>網絡連接的狀態改變

當我們開發一些網絡應用的時候,我們的應用需要時刻監聽手機網絡連接的狀態變化,當網絡處於連接時,應用可以訪問服務器獲取所需要的信息資源,而當網絡處於斷開的狀態時,則需要立即提示用戶“網絡連接中斷”,訪問服務器工作終止。那麼要實現該項功能,我們可以在該應用中創建一個BroadcastReceiver用於時刻監聽並接收系統網絡狀態變化的廣播消息,然後向用戶提示“網絡連接中斷”信息或者執行其它相關操作。下面我們來實現該項功能,首先在AndroidManifest.xml文件中註冊一個BroadcastReceiver,具體代碼如下:
  1. <receiver android:name=".broadcastreceiver.NetworkConnectBroadCastReceiver" >  
  2.        <intent-filter >  
  3.             <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />  
  4.       </intent-filter>  
  5.  </receiver>  
注意,<action>裏面的值是系統提供的,其實就是Context.CONNECTIVITY_CHANGE所對應的常量值,具體可以參考Android API文檔中關於Intent的詳細用法。
而由於要使用網絡連接的類庫,因此需要聲明相關的權限才行,下面是對應的權限聲明:
  1. <!-- 讀取網絡狀態 權限-->  
  2.   <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />  
接下來在應用程序中自定義一個BroadcastReceiver,代碼如下:
  1. package com.fendou.broadcastreceiver;  
  2.   
  3. import android.content.BroadcastReceiver;  
  4. import android.content.Context;  
  5. import android.content.Intent;  
  6. import android.net.ConnectivityManager;  
  7. import android.net.NetworkInfo;  
  8. import android.net.NetworkInfo.State;  
  9. import android.util.Log;  
  10. import android.widget.Toast;  
  11.   
  12. public class NetworkConnectBroadCastReceiver extends BroadcastReceiver {  
  13.   
  14.     private static final String TAG = "ygh";  
  15.     @Override  
  16.     public void onReceive(Context context, Intent intent) {  
  17.           // TODO Auto-generated method stub  
  18.           //Log.i(TAG, "NetworkConnectBroadCastReceiver");  
  19.          doSomething(context);   
  20.     }  
  21.     public void doSomething(Context mContext)  
  22.     {  
  23.           if(isNetworkConnect(mContext))  
  24.          {  
  25.              Toast. makeText(mContext, "網絡連接成功!!", Toast.LENGTH_LONG).show();  
  26.              Log. i(TAG, "網絡連接成功!!" );  
  27.          }  
  28.           else  
  29.          {  
  30.              Toast. makeText(mContext, "網絡已斷開連接!!", Toast.LENGTH_LONG).show();  
  31.              Log. i(TAG, "網絡已斷開連接!!" );  
  32.          }  
  33.     }  
  34.     //判斷網絡是否連接  
  35.     public boolean isNetworkConnect(Context mContext)  
  36.     {  
  37.           boolean flag = false;  
  38.           //獲取網絡連接管理對象  
  39.          ConnectivityManager manager = (ConnectivityManager) mContext.getSystemService(mContext.CONNECTIVITY_SERVICE);  
  40.           //得到所有網絡連接的信息  
  41.          NetworkInfo[] mInfo = manager.getAllNetworkInfo();  
  42.           if(mInfo != null){  
  43.           for(int i = 0 ; i < mInfo.length;i++)  
  44.          {  
  45.               //一一判斷是否有已經連接的網絡  
  46.              State mState = mInfo[i].getState();  
  47.               if(mState == NetworkInfo.State.CONNECTED )  
  48.              {  
  49.                  flag = true;  
  50.                   return flag;  
  51.              }  
  52.          }  
  53.          }  
  54.           return flag;  
  55.     }  
  56. }  
接下來我們進行實驗,首先運行該程序,然後再模擬器中依次點擊settings ->Wireless&networks-> Mobile netWorks後進入界面如下:

我們來模擬下網絡斷開和連接時的操作,首先點擊選中Data enabled選項,連接網絡,界面彈出一個“網絡連接成功”的Toast信息,說明BroadcastReceiver接收到了系統發出的網絡狀態改變的廣播,示例圖如下:

然後再點擊Data enabled選項,斷開網絡,彈出一個“網絡已斷開連接”的Toast信息,示例圖如下:

<2>飛行模式狀態改變


我們接着再做一個實驗,就是創建一個BroadcastReceiver對象監聽並接收當飛行模式狀態改變後系統發送的Broadcast。首先列出註冊代碼:
  1. <receiver android:name=".broadcastreceiver.AirPlaneModeChangeBroadCastReceiver" >  
  2.      <intent-filter >  
  3.           <action android:name="android.intent.action.AIRPLANE_MODE" />  
  4.      </intent-filter>  
  5.  </receiver>  

然後,創建一個BroadcastReceiver,具體代碼如下:
  1. package com.fendou.broadcastreceiver;  
  2.   
  3. import com.fendou.utils.StaticContens;  
  4.   
  5. import android.content.BroadcastReceiver;  
  6. import android.content.Context;  
  7. import android.content.Intent;  
  8.   
  9. public class AirPlaneModeChangeBroadCastReceiver extends BroadcastReceiver {  
  10.   
  11.     @Override  
  12.     public void onReceive(Context context, Intent intent) {  
  13.           // TODO Auto-generated method stub  
  14.          StaticContens. showNotification(context, "通知","飛行模式狀態改變了!!" );  
  15.     }  
  16.   
  17. }  
代碼比較簡單,就接收系統發送過來的廣播,接收成功後show一個通知。那麼下面我們進行實驗,首先運行該程序,然後再模擬器中依次點擊settings ->Wireless&networks後進入界面如下:

我們點擊Airplane mode 選項,改變手機模擬器的飛行模式狀態,隨後彈出一個Notification通知,如下圖示例:

由此我們可知Broadcast接收成功。
總結:BroadcastReceiver作爲四大組件之一,應用的非常廣泛,我們可以通過自定義發送Broadcast和使用BroadcastReceiver接收Broadcast來完成很多事情,例如接收系統Broadcast來執行相關邏輯操作(實現與系統的交互)和完成進程間的通信等等。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章