1. 程式人生 > >Android Wi-Fi Peer-to-Peer(Android的Wi-Fi P2P對等網路)

Android Wi-Fi Peer-to-Peer(Android的Wi-Fi P2P對等網路)


Wi-Fi peer-to-peer(P2P,對等網路),它允許具備相應硬體的Android 4.0(API level 14)或者更高版本的裝置可以直接通過wifi而不需要其它中間中轉節點就能直接通訊(Android的Wi-Fi P2P框架符合Wi-Fi聯盟的Wi-Fi Direct™直連認證標誌)。使用這些API,你可以搜尋並連線其它同樣支援Wi-Fi P2P的裝置,然後再通過一個高速的連線進行互相通訊,並且這個連線的有效距離要比藍芽連線的有效距離要長的多。這對於需要在使用者之間共享資料的應用程式非常有用,例如多玩家遊戲或者照片分享之類應用。


Android的Wi-Fi P2P API主要包含以下幾個部分:

* 使用WifiP2pManager類定義的方法搜尋、請求並連線到其它對等裝置。

* 監聽WiFiP2pManager的方法呼叫是否成功或者失敗的Listener(監聽器)。當呼叫WiFiP2pManager的方法時,每一個方法都可以接收一個指定的監聽器作為引數。

* 由Wi-Fi P2P框架識別併發送的指定事件型別的Intent,例如撤銷一個連線或者新發現了一個對等裝置。


通常你需要一起使用這三個主要部分的API。例如,你可以在呼叫discoverPeers()方法的時候提供WifiP2pManager.ActionListener監聽器物件,這樣你就能得到ActionListener.onSuccess()和ActionListener.onFailure()方法的通知。如果discoverPeers()方法發現對等裝置列表有更新,同樣也會發送WIFI_P2P_PEERS_CHANGE_ACTION

型別的Intent廣播。

1、API概覽


WifiP2pManager類提供了與裝置上的Wi-Fi硬體進行互動的方法,例如搜尋和連線對等裝置。以下是可用的操作:


表一:Wi-Fi P2P方法

方法 描述
initialize() 在Wi-Fi框架上註冊應用程式。這個方法必須在呼叫其它Wi-Fi P2P方法之前呼叫。
connect() 使用指定配置與一臺裝置開始一個P2P對等連線。
cancelConnect() 取消正在進行中的P2P對等網路組的互動。
requestConnectInfo() 請求一臺裝置連線資訊。
createGroup() 建立一個對等網路組,並將當前裝置作為群組的擁有者。
removeGroup() 移除當前的P2P對等網路組。
requestGroupInof() 請求P2P對等網路組資訊。
discoverPeers() 開始搜尋對等網路(裝置)。
requestPeers() 請求當前已發現的對等網路(裝置)。


WifiP2pManager的方法允許你傳遞一個listener監聽器,這樣Wi-Fi P2P框架才能通知你的activity所呼叫方法的狀態。可使用的listener監聽器介面和相應呼叫這些監聽器的WifiP2pManager方法如下表:


表二:Wi-Fi P2P監聽器

監聽器介面 可被使用的操作
WifiP2pManager.ActionListener connect(),cancelConnect(),createGroup(),removeGroup(),discoverPeers()
WifiP2pManager.ChannelListener  initialize()
WifiP2pManager.ConnectionInfoListener requestConnectInfo()
WifiP2pManager.GroupInfoListener requestGroupInfo()
WifiP2pManager.PeerListListener requestPeers()


Wi-Fi P2P的API定義某些Wi-Fi P2P事件發生時要廣播的Intent,例如發現了一個新的對等網路(裝置)或者一臺裝置的Wi-Fi狀態改變的事件。你可以在你的應用程式中建立並註冊一個廣播接受者來處理下面這些Intent:


表三:Wi-Fi P2P Intents

Intent 描述
WIFI_P2P_CONNECTION_CHANGED_ACTION 當裝置的Wi-Fi連線狀態改變時會發送這個廣播。
WIFI_P2P_PEERS_CHANGED_ACTION 當你呼叫discoverPeers()方法就會收到這個廣播。如果你在你的應用程式中處理這個intent,你同樣需要呼叫requestPeers()來獲取一個最新的對等網路(裝置)列表。
WIFI_P2P_STATE_CHANGED_ACTION 當裝置的Wi-Fi P2P啟用或者禁用時會發送這個廣播。當裝置的Wi-Fi P2P啟用或者禁用時會發送這個廣播。
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 當裝置的細節被修改時會發送這個廣播,例如裝置的名字。

2、為Wi-Fi P2P的Intent建立一個廣播接收器


廣播接收器(broadcase receiver)允許你接收Android系統廣播出來的Intent,這樣你的應用程式才能響應你所感興趣的時間。建立一個廣播接收器來處理Wi-Fi P2P intent的基本步驟如下:


2.1 建立一個繼承BroadcaseReceiver的類。在這個類的構造方法中,你最好將WifiP2pManager、WifiP2pManager.Channel,還有註冊了這個廣播接收器的activity作為引數傳遞進來。這樣才能讓廣播接收器向activity傳送更新的同時,還能在需要的時候訪問Wi-Fi硬體和通訊的通道。

2.2 在廣播接收器中,檢查onReceive()方法中你所感興趣的intent。基於接收到的intent完成一些必要的操作。例如,如果廣播接收器接收到一個WIFI_P2P_PEERS_CHANGE_ACTION型別的Intent,你可以呼叫requestPeers()方法來獲取當前已發現的對等網路(裝置)。


下面的程式碼展示如何建立一個典型的廣播接收器。這個廣播接收器攜帶一個WifiP2pManager物件和一個activity最為引數,並在廣播接收器接到一個intent的時候,使用這兩個物件來適當完成所需要的操作:

/**
 * 一個接收Wi-Fi P2P重要事件的廣播接收器
 */
public class WiFiDirectBroadcastReceiver extends BroadcastReceiver {

    private WifiP2pManager mManager;
    private Channel mChannel;
    private MyWiFiActivity mActivity;

    public WiFiDirectBroadcastReceiver(WifiP2pManager manager, Channel channel,
            MyWifiActivity activity) {
        super();
        this.mManager = manager;
        this.mChannel = channel;
        this.mActivity = activity;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
            // 檢查Wi-fi是否已經啟用,並通知適當的activity
        } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
            // 呼叫WifiP2pManager.requestPeers()來獲取當前的對等網路(裝置)列表
        } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
            // 響應新的連線或者斷開連線
        } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
            // 響應本臺裝置wifi狀態的改變
        }
    }
}

3、建立一個Wi-Fi P2P應用程式


建立一個Wi-Fi P2P應用程式涉及到為應用程式建立並註冊一個廣播接收器,搜尋對等網路(裝置),連線一臺對等裝置,還有向一臺對等裝置傳輸資料。下面的章節描述如何完成這些操作。

3.1 初始設定


在使用Wi-Fi P2P的API之前,你必須確認你的應用程式能夠訪問對應的硬體並且裝置支援Wi-Fi P2P協議。如果支援Wi-Fi P2P,你就可以獲取一個WifiP2pManager例項,建立並註冊你的廣播接收器,然後開始使用Wi-Fi P2P的API。


3.1.1 在Android的manifest檔案中請求使用裝置上Wi-Fi硬體的許可權,併為你的應用程式宣告正確的最小SDK版本號:

<uses-sdk android:minSdkVersion="14" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />


3.1.2 檢查Wi-Fi P2P能夠支援並且已經啟用。進行這項檢查的一個較好的地方是在你的廣播接收器中,當廣播接收器接收到WIFI_P2P_STATE_CHANGED_ACTION的時候。從而通知你的activity關於Wi-Fi P2P的狀態並做出相應的應對。

@Override
public void onReceive(Context context, Intent intent) {
    ...
    String action = intent.getAction();
    if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
        int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
        if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
            // Wifi P2P已經啟用
        } else {
            // Wi-Fi P2P沒有啟用
        }
    }
    ...
}


3.1.3 在你的activity的onCreate()方法中獲取一個WifiP2pManager例項,並通過呼叫initialize()方法將你的應用程式註冊到Wi-Fi P2P框架中。這個方法會返回一個WifiP2pManager.Channel物件,這個物件被用於連線你的應用程式和Wi-Fi P2P框架。你同樣需要使用這個WifiP2pManager和WifiP2pManager.Channel物件,還有你的activity引用建立一個你的廣播接收器例項。這樣才能允許你的廣播接收器通知你的activity所感興趣的事件並進行更新。它同樣能讓你在需要的時候操作裝置的Wi-Fi狀態。

WifiP2pManager mManager;
Channel mChannel;
BroadcastReceiver mReceiver;
...
@Override
protected void onCreate(Bundle savedInstanceState){
    ...
    mManager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
    mChannel = mManager.initialize(this, getMainLooper(), null);
    mReceiver = new WiFiDirectBroadcastReceiver(mManager, mChannel, this);
    ...
}


3.1.4 建立一個intent過濾器,並在你的廣播接收器中新增相同的Intent以進行檢查:

IntentFilter mIntentFilter;
...
@Override
protected void onCreate(Bundle savedInstanceState){
    ...
    mIntentFilter = new IntentFilter();
    mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
    mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
    mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
    mIntentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
    ...
}


3.1.5 在activity的onResume()方法中註冊你的廣播接收器,並在onPause()方法中登出:

/* 使用需要匹配的intent值註冊廣播接收器 */
@Override
protected void onResume() {
    super.onResume();
    registerReceiver(mReceiver, mIntentFilter);
}
/* 登出廣播接收器 */
@Override
protected void onPause() {
    super.onPause();
    unregisterReceiver(mReceiver);
}


當你已經獲取到WifiP2pManager.Channel物件並且設定好廣播接收器,你的應用程式就可以呼叫Wi-Fi P2P的方法和接收Wi-Fi P2P的intent。

你現在可以實現你的應用程式並且通過呼叫WifiP2pManager中的方法來使用Wi-Fi P2P的功能。下面的章節將闡述如何執行常見的操作,比如搜尋和連線對等裝置。

3.2 搜尋對等網路(裝置)


搜尋允許被連線的對等裝置,可以呼叫discoverPeers()方法來檢測附近範圍中允許被發現的對等網路(裝置)。這個方法的執行過程是非同步的,如果你有建立並傳遞一個WifiP2pManager.ActionListener物件作為引數,你可以在這個物件中的onSuccess()和onFailure()方法中接收到操作執行的結果。其中onSuccess()方法只是通知你搜索程式已經成功執行,但是不會提供任何實際搜尋到的對等裝置資訊;

mManager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
    @Override
    public void onSuccess() {
        ...
    }

    @Override
    public void onFailure(int reasonCode) {
        ...
    }
});


如果搜尋程式成功執行並且檢測到對等網路和裝置,系統將會發送WIFI_P2P_PEERS_CHANGED_ACTION型別的intent廣播,你可以在廣播接收器中監聽這個廣播以獲取對等網路裝置列表。當你的應用程式接收到WIFI_P2P_PEERS_CHANGED_ACTION型別的intent,你就可以使用requestPeers()方法來請求已被發現的對等網路裝置列表。下面的程式碼展示瞭如何設定這些步驟:

PeerListListener myPeerListListener;
...
if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

    // 從wifi p2p manager請求可使用的對等裝置。這是一個非同步呼叫的方法,
    // 呼叫的activity將會在PeerListListener.onPeersAvailable()的回撥方法中得到列表結果。
    if (mManager != null) {
        mManager.requestPeers(mChannel, myPeerListListener);
    }
}


這個requestPeers()方法同樣是非同步執行的方法,當對等裝置列表可用時,它會在WifiP2pManager.PeerListListener介面定義的onPeersAvailable()方法中將結果返回給你的activity。onPeersAvailable()方法會提供一個WifiP2pDeviceList物件,你可以通過這個物件找到你想要連線的對等裝置。

3.3 連線對等裝置


當你從獲取到的可連線對等裝置列表中找出你想要連線的對等裝置之後,你就可以呼叫connect()方法連結這個裝置。這個方法的呼叫需要一個WifiP2pConfig物件,這個物件包含了想要連線的裝置資訊。你可以通過WifiP2pManager.ActionListener得到連線成功或者失敗的結果。下面的程式碼展示瞭如何連線目標裝置:

//從WifiP2pDeviceList中獲取一個對等裝置
WifiP2pDevice device;
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = device.deviceAddress;
mManager.connect(mChannel, config, new ActionListener() {

    @Override
    public void onSuccess() {
        //連線成功的邏輯處理操作
    }

    @Override
    public void onFailure(int reason) {
        //連線失敗的邏輯處理操作
    }
});

3.4 傳輸資料


一旦連線建立成功,你就可以在裝置之間通過socket傳輸資料。傳入資料的基本步驟如下:


3.4.1 建立一個SeverSocket。這個socket會在一個埠上等待客戶端的連線,並且會一直阻塞直到連線請求出現,因此這個方法需要在後臺執行緒執行。

3.4.2 建立一個客戶端的Socket。這個客戶端使用服務端ServerSocket的IP地址和埠來連線服務端裝置。

3.4.3 從客戶端傳送資料到服務端。當客戶端的socket成功連線上服務端的socket之後,你就可以通過位元組流將資料從客戶端傳送到服務端。

3.4.4 服務端的ServerSocket等待著客戶端的連線(通過accept()方法)。這個方法會一直阻塞直到客戶端連線上,因此要在其它執行緒中呼叫這個方法。當連線上之後,服務端裝置就能夠接收到客戶端傳送過來的資料。完成對這些資料的一些操作,例如儲存為一個檔案或者顯示給使用者。


下面的例子修改於Wi-Fi P2P Demo(可以在Android SDK中找到),它展示瞭如何建立客戶端和服務端之間的socket通訊,並從通過一個service從客戶端向服務端傳送了一個JPEG圖片。完整的程式碼,可以直接檢視Wi-Fi P2P Demo。

public static class FileServerAsyncTask extends AsyncTask {

    private Context context;
    private TextView statusText;

    public FileServerAsyncTask(Context context, View statusText) {
        this.context = context;
        this.statusText = (TextView) statusText;
    }

    @Override
    protected String doInBackground(Void... params) {
        try {

            /**
             * 建立一個ServerSocket並等待客戶端的連線.這個方法會一直阻塞直到接受了一個客戶端連線
             */
            ServerSocket serverSocket = new ServerSocket(8888);
            Socket client = serverSocket.accept();

            /**
             * 如果程式碼能夠執行到這裡,說明已經連線上一個客戶端並且開始傳輸資料
             * 將來自客戶端的資料流儲存為一個JPEG檔案
             */
            final File f = new File(Environment.getExternalStorageDirectory() + "/"
                    + context.getPackageName() + "/wifip2pshared-" + System.currentTimeMillis()
                    + ".jpg");

            File dirs = new File(f.getParent());
            if (!dirs.exists())
                dirs.mkdirs();
            f.createNewFile();
            InputStream inputstream = client.getInputStream();
            copyFile(inputstream, new FileOutputStream(f));
            serverSocket.close();
            return f.getAbsolutePath();
        } catch (IOException e) {
            Log.e(WiFiDirectActivity.TAG, e.getMessage());
            return null;
        }
    }

    /**
     * 啟動能夠處理JPEG圖片的activity
     */
    @Override
    protected void onPostExecute(String result) {
        if (result != null) {
            statusText.setText("File copied - " + result);
            Intent intent = new Intent();
            intent.setAction(android.content.Intent.ACTION_VIEW);
            intent.setDataAndType(Uri.parse("file://" + result), "image/*");
            context.startActivity(intent);
        }
    }
}


在客戶端上,通過一個客戶端的socket連線服務端的socket並傳輸資料。這個例子傳輸了一個客戶端裝置檔案系統的上的JPEG檔案。

Context context = this.getApplicationContext();
String host;
int port;
int len;
Socket socket = new Socket();
byte buf[]  = new byte[1024];
...
try {
    /**
     * 使用服務端IP和埠還有連線超時時間建立一個客戶端socket
     */
    socket.bind(null);
    socket.connect((new InetSocketAddress(host, port)), 500);

    /**
     * 從一個JPEG檔案建立一個位元組流,並且傳輸到socket的輸出流上。這些資料將會在服務端裝置上接收到。
     */
    OutputStream outputStream = socket.getOutputStream();
    ContentResolver cr = context.getContentResolver();
    InputStream inputStream = null;
    inputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg"));
    while ((len = inputStream.read(buf)) != -1) {
        outputStream.write(buf, 0, len);
    }
    outputStream.close();
    inputStream.close();
} catch (FileNotFoundException e) {
    //catch logic
} catch (IOException e) {
    //catch logic
}

/**
 * 當資料傳輸完成或者發生異常時關閉已經開啟的socket。
 */
finally {
    if (socket != null) {
        if (socket.isConnected()) {
            try {
                socket.close();
            } catch (IOException e) {
                //catch logic
            }
        }
    }
}


相關推薦

Android Wi-Fi Peer-to-PeerAndroidWi-Fi P2P對等網路

Wi-Fi peer-to-peer(P2P,對等網路),它允許具備相應硬體的Android 4.0(API level 14)或者更高版本的裝置可以直接通過wifi而不需要其它中間中轉節點就能直接通訊(Android的Wi-Fi P2P框架符合Wi-Fi聯盟的Wi-Fi

建立WIFI Direct APP : android.net.wifi.p2pandroid.net.wifi.p2p.nsd + Wi-Fi peer-to-peer overview 翻譯

一 ) Provides classes to create peer-to-peer (P2P) connections with Wi-Fi Direct. 提供用於使用Wi-Fi Direct建立對等(P2P)連線的類。 二 ) Using the

Peer-to-Peer (P2P) communication across middleboxes

Internet Draft                       &nb

Numerai reveals Erasure: unstoppable, peer to peer data feeds

Information Is TrappedImagine you have done research on Apple’s hardware manufacturing. After investigating for some time, you know that iPhone sales in Ch

Android 音頻采集——MediaRecord編碼後錄影文件 、AudioRecordPCM原始數據

listen ext 根據 任務 nbsp too 影響 按鈕 red http://blog.csdn.net/java_android_c/article/details/52619737 Android 音頻簡介 常見的音頻編解碼的類型:AAC OPUS MP3

android中一些特殊字符如:←↑→↓等箭頭符號的Unicode碼值

lin gb2312 ring clu itl app lan orien lam 在項目中,有時候在一些控件(如Button、TextView)中要添加一些符號,如下圖所示: 這個時候可以使用圖片的方式來顯示,不過這

Android 雙擊退出程序實現有側滑界面

是的 tro raw idt style tap sys .com ges 大家好,今天帶來雙擊退出程序實現方法,我知道,網上也是有許多關於雙擊退出程序實現的方法,所以,聽見當然是給大家帶來不一樣的雙擊退出的實現方法。 首先帶來的便是關於onKeyDown和onKe

8. String to Integer 字符串轉整數

包括 大於 刪除 們的 它的 停止 clas integer 頭部 實現 atoi,將字符串轉為整數。 在找到第一個非空字符之前,需要移除掉字符串中的空格字符。如果第一個非空字符是正號或負號,選取該符號,並將其與後面盡可能多的連續的數字組合起來,這部分字符即為整數的值。如果

Android系統開機慢的問題升級後第一開機慢

系統開機慢的問題,採用User-Debug 的模式編譯,要編譯出來ODEX 來優化這個開機時間 那第一開機的時候為什麼會慢呢,在網上百度找到了一個大牛對此的分析,我這裡貼出來 引用自:http://ticktick.blog.51cto.com/823160/1677216/ 在

797. All Paths From Source to Target圖的搜尋、dfs

Given a directed, acyclic graph of N nodes. Find all possible paths from node 0 to node N-1, and return them in any order. The graph is given as follows:

linux驅動由淺入深系列:PBL-SBL1-(bootloader)LK-Android啟動過程詳解之一高通MSM8953啟動例項【轉】

本文轉載自:https://blog.csdn.net/radianceblau/article/details/73229005 對於嵌入式工程師瞭解晶片啟動過程是十分有必要的,在分析、除錯各種問題的時候都有可能涉及到這方面的知識。同時這部分知識也是比較複雜的,因為其中涉及到晶片內部架構,啟動各個階段軟體

Android 圖片資原始檔命名規範非官方,僅供參考

  對於Android的資原始檔的命名,Google官方並沒有提供統一的規範,民間的命名方式可謂是百花齊放、相容幷包,比較知名的有阿里巴巴開發規範,以及Blankj的AndroidStandardDevelop,以下內容是筆者參考了其他的一些命名規範,加上自己總結的一些命名方式所得。由於專

Android O Settings原始碼流程分析資料載入之二級選單

Android O Settings  靜態介面篇 介面渲染篇 資料載入篇之一級選單 資料載入篇之二級選單 資料載入篇之獲取及修改預設設定屬性值 搜尋欄篇 資料載入之二級選單 二級選單是動態載入和靜態xml佈局檔案相結合的方式。 以“

Android O Settings原始碼流程分析資料載入之一級選單

Android O Settings  資料載入之一級選單 DashboardSummary是頂級選單的容器,那麼資料的獲取和它也就有關係。 DashboardCategory的獲取是在DashboardSummary中的updateCategoryAndSuggest

Android中如何修改系統時間應用程式獲得系統許可權

在 Android 的API中有提供 SystemClock.setCurrentTimeMillis()函式來修改系統時間,可惜無論你怎麼呼叫這個函式都是沒用的,無論模擬器還是真機,在logcat中總會得到"Unable to open alarm driver: Permission denied "

轉載:Android中如何修改系統時間應用程式獲得系統許可權

在 android 的API中有提供 SystemClock.setCurrentTimeMillis()函式來修改系統時間,可惜無論你怎麼呼叫這個函式都是沒用的,無論模擬器還是真機,在logcat中總會得到"Unable to open alarm driver: Permission denied "

android 8.1 MTK 預設儲存插入sdcard時預設sdcard

1.修改獲取預設儲存路徑的方法 vendor/mediatek/proprietary/frameworks/base/core/java/com/mediatek/storage/StorageManagerEx.java method: getDefau

原始碼詳細分析Android中的引用機制ReferenceWeakReference、SoftReference、PhantomReference

1、前言 在java中,我們知道一般情況下當一個物件被其他物件引用時,該物件則不會被回收。但是有時我們雖然需要使用該物件,但又希望不影響回收。 比如在Activity中以內部類的方式建立了一個Handler,這個Handler就會隱式的持有一個activity的引用,

Java 8 – Convert List to Map將 List 轉換為 Map

   幾個Java 8的例子展示怎樣將一個 物件的集合(List)放入一個Map中,並且展示怎樣處理多個重複keys的問題。 Hosting.java package com.mkyong.java8 public class Hosting { priva

Android中的自定義註解反射實現-執行時註解

預備知識: Java註解基礎 Java反射原理 Java動態代理 一、佈局檔案的註解 我們在Android開發的時候,總是會寫到setContentView方法,為了避免每次都寫重複的程式碼,我們需要使用註解來代替我們做這個事情,只需要在類Activity上