使用 Wi-Fi Direct 建立 P2P 連線

Wi-Fi Direct (也稱為點對點或 P2P) 可讓應用程式快速尋找鄰近裝置並進行互動 裝置的藍牙功能

Wi-Fi Direct (P2P) API 可讓應用程式在沒有 Wi-Fi Direct (P2P) API 的情況下與附近的裝置連線。 需要連線至網路或無線基地台。 如果您的應用程式是專為安全、鄰近範圍的網路或 Wi-Fi 設計 比起傳統 Wi-Fi 臨時檔案,直接流量是更合適的選項 網路的原因如下:

  • Wi-Fi Direct 支援 WPA2 加密。(部分臨時聯播網僅支援 WEP 加密)。
  • 裝置可以廣播自身提供的服務, 讓裝置更容易找出合適的同類應用程式
  • 決定哪部裝置應為網路的群組擁有者時 Wi-Fi Direct 會檢查每部裝置的電源管理、UI 和服務 並根據這些資訊選擇可處理資料的裝置 最有效率
  • Android 不支援 Wi-Fi 臨時模式。

本課程將說明如何使用 Wi-Fi P2P 尋找鄰近裝置並連線。

設定應用程式權限

如要使用 Wi-Fi Direct,請在 ACCESS_FINE_LOCATION, CHANGE_WIFI_STATE, ACCESS_WIFI_STATEINTERNET 權限的權限。 如果應用程式指定 Android 13 (API 級別 33) 以上版本,請一併新增 NEARBY_WIFI_DEVICES 授予資訊清單的權限。Wi-Fi 直接連結不需要網際網路連線,但採用標準 Java 通訊端,需要 INTERNET 權限。因此,您必須具備下列權限,才能使用 Wi-Fi Direct:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.nsdchat"
    ...
    <!-- If your app targets Android 13 (API level 33)
         or higher, you must declare the NEARBY_WIFI_DEVICES permission. -->
        <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
        <!-- If your app derives location information from Wi-Fi APIs,
             don't include the "usesPermissionFlags" attribute. -->
        android:usesPermissionFlags="neverForLocation" />
        
    <uses-permission
        android:required="true"
        android:name="android.permission.ACCESS_FINE_LOCATION"
        <!-- If any feature in your app relies on precise location information,
             don't include the "maxSdkVersion" attribute. -->
        android:maxSdkVersion="32" />
    <uses-permission
        android:required="true"
        android:name="android.permission.ACCESS_WIFI_STATE"/>
    <uses-permission
        android:required="true"
        android:name="android.permission.CHANGE_WIFI_STATE"/>
    <uses-permission
        android:required="true"
        android:name="android.permission.INTERNET"/>
    ...

除了上述權限,下列 API 也需啟用定位模式:

設定廣播接收器和點對點管理員

如要使用 Wi-Fi Direct,您必須監聽廣播意圖,透過語音告知 來回應應用程式在您的應用程式中,將執行個體化 IntentFilter,並將其設定為監聽下列內容:

WIFI_P2P_STATE_CHANGED_ACTION
指出 Wi-Fi Direct 是否已啟用
WIFI_P2P_PEERS_CHANGED_ACTION
表示可用的同類群組清單有所變更。
WIFI_P2P_CONNECTION_CHANGED_ACTION
指出 Wi-Fi Direct 連線狀態已變更。開頭是 Android 10 版本則非固定式。如果您的應用程式仰賴這些 於註冊時播送get 方法才能取得資訊。
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
表示這部裝置的設定詳細資料已變更。開頭是 Android 10 版本則非固定式。如果您的應用程式仰賴這些 於註冊時播送get 方法才能取得資訊。

Kotlin

private val intentFilter = IntentFilter()
...
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main)

    // Indicates a change in the Wi-Fi Direct status.
    intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)

    // Indicates a change in the list of available peers.
    intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)

    // Indicates the state of Wi-Fi Direct connectivity has changed.
    intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)

    // Indicates this device's details have changed.
    intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
    ...
}

Java

private final IntentFilter intentFilter = new IntentFilter();
...
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    // Indicates a change in the Wi-Fi Direct status.
    intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);

    // Indicates a change in the list of available peers.
    intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);

    // Indicates the state of Wi-Fi Direct connectivity has changed.
    intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);

    // Indicates this device's details have changed.
    intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
    ...
}

onCreate() 方法的結尾,取得 WifiP2pManager 的例項,並呼叫其 initialize() 方法。這個方法會傳回 WifiP2pManager.Channel 物件,稍後您將用到該物件 將應用程式連上 Wi-Fi Direct 架構。

Kotlin

private lateinit var channel: WifiP2pManager.Channel
private lateinit var manager: WifiP2pManager

override fun onCreate(savedInstanceState: Bundle?) {
    ...
    manager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
    channel = manager.initialize(this, mainLooper, null)
}

Java

Channel channel;
WifiP2pManager manager;

@Override
public void onCreate(Bundle savedInstanceState) {
    ...
    manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
    channel = manager.initialize(this, getMainLooper(), null);
}

現在請建立新的 BroadcastReceiver 類別,以便監聽變更 切換系統的 Wi-Fi 狀態在「onReceive()」中 方法,請新增條件來處理上述各項狀態變更。

Kotlin

override fun onReceive(context: Context, intent: Intent) {
    when(intent.action) {
        WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
            // Determine if Wi-Fi Direct mode is enabled or not, alert
            // the Activity.
            val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)
            activity.isWifiP2pEnabled = state == WifiP2pManager.WIFI_P2P_STATE_ENABLED
        }
        WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {

            // The peer list has changed! We should probably do something about
            // that.

        }
        WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {

            // Connection state changed! We should probably do something about
            // that.

        }
        WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {
            (activity.supportFragmentManager.findFragmentById(R.id.frag_list) as DeviceListFragment)
                    .apply {
                        updateThisDevice(
                                intent.getParcelableExtra(
                                        WifiP2pManager.EXTRA_WIFI_P2P_DEVICE) as WifiP2pDevice
                        )
                    }
        }
    }
}

Java

@Override
public void onReceive(Context context, Intent intent) {
    String action = intent.getAction();
    if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
        // Determine if Wi-Fi Direct mode is enabled or not, alert
        // the Activity.
        int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
        if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
            activity.setIsWifiP2pEnabled(true);
        } else {
            activity.setIsWifiP2pEnabled(false);
        }
    } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

        // The peer list has changed! We should probably do something about
        // that.

    } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {

        // Connection state changed! We should probably do something about
        // that.

    } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
        DeviceListFragment fragment = (DeviceListFragment) activity.getFragmentManager()
                .findFragmentById(R.id.frag_list);
        fragment.updateThisDevice((WifiP2pDevice) intent.getParcelableExtra(
                WifiP2pManager.EXTRA_WIFI_P2P_DEVICE));

    }
}

最後,請新增程式碼,註冊意圖篩選器和廣播接收器 主要活動,並在活動暫停時取消註冊。 最好是在 onResume()onPause() 方法。

Kotlin

/** register the BroadcastReceiver with the intent values to be matched  */
public override fun onResume() {
    super.onResume()
    receiver = WiFiDirectBroadcastReceiver(manager, channel, this)
    registerReceiver(receiver, intentFilter)
}

public override fun onPause() {
    super.onPause()
    unregisterReceiver(receiver)
}

Java

/** register the BroadcastReceiver with the intent values to be matched */
@Override
public void onResume() {
    super.onResume();
    receiver = new WiFiDirectBroadcastReceiver(manager, channel, this);
    registerReceiver(receiver, intentFilter);
}

@Override
public void onPause() {
    super.onPause();
    unregisterReceiver(receiver);
}

啟動同業探索功能

如要開始透過 Wi-Fi P2P 搜尋鄰近裝置,請呼叫 discoverPeers()。這個方法會使用

Kotlin

manager.discoverPeers(channel, object : WifiP2pManager.ActionListener {

    override fun onSuccess() {
        // Code for when the discovery initiation is successful goes here.
        // No services have actually been discovered yet, so this method
        // can often be left blank. Code for peer discovery goes in the
        // onReceive method, detailed below.
    }

    override fun onFailure(reasonCode: Int) {
        // Code for when the discovery initiation fails goes here.
        // Alert the user that something went wrong.
    }
})

Java

manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {

    @Override
    public void onSuccess() {
        // Code for when the discovery initiation is successful goes here.
        // No services have actually been discovered yet, so this method
        // can often be left blank. Code for peer discovery goes in the
        // onReceive method, detailed below.
    }

    @Override
    public void onFailure(int reasonCode) {
        // Code for when the discovery initiation fails goes here.
        // Alert the user that something went wrong.
    }
});

請注意,這項操作只會「啟動」同業探索功能。 discoverPeers() 方法會啟動探索程序,然後 會立即傳回值系統會在以下情況下,通知您對等互連探索程序: 透過在提供的動作事件監聽器中呼叫方法,成功啟動該事件。 此外,探索功能仍然有效,直到啟動連線或 P2P 群組為止

擷取同類應用程式清單

現在請編寫用於擷取及處理同業清單的程式碼。頭等艙 實作 WifiP2pManager.PeerListListener 介面,提供 Wi-Fi Direct 的對等連線相關資訊 。應用程式也可利用這項資訊,判斷同類應用程式何時加入或 離開網路下列程式碼片段說明這些作業 與同類應用程式有關:

Kotlin

private val peers = mutableListOf<WifiP2pDevice>()
...

private val peerListListener = WifiP2pManager.PeerListListener { peerList ->
    val refreshedPeers = peerList.deviceList
    if (refreshedPeers != peers) {
        peers.clear()
        peers.addAll(refreshedPeers)

        // If an AdapterView is backed by this data, notify it
        // of the change. For instance, if you have a ListView of
        // available peers, trigger an update.
        (listAdapter as WiFiPeerListAdapter).notifyDataSetChanged()

        // Perform any other updates needed based on the new list of
        // peers connected to the Wi-Fi P2P network.
    }

    if (peers.isEmpty()) {
        Log.d(TAG, "No devices found")
        return@PeerListListener
    }
}

Java

private List<WifiP2pDevice> peers = new ArrayList<WifiP2pDevice>();
...

private PeerListListener peerListListener = new PeerListListener() {
    @Override
    public void onPeersAvailable(WifiP2pDeviceList peerList) {

        List<WifiP2pDevice> refreshedPeers = peerList.getDeviceList();
        if (!refreshedPeers.equals(peers)) {
            peers.clear();
            peers.addAll(refreshedPeers);

            // If an AdapterView is backed by this data, notify it
            // of the change. For instance, if you have a ListView of
            // available peers, trigger an update.
            ((WiFiPeerListAdapter) getListAdapter()).notifyDataSetChanged();

            // Perform any other updates needed based on the new list of
            // peers connected to the Wi-Fi P2P network.
        }

        if (peers.size() == 0) {
            Log.d(WiFiDirectActivity.TAG, "No devices found");
            return;
        }
    }
}

現在修改廣播接收器的 onReceive() 接收含有 WIFI_P2P_PEERS_CHANGED_ACTION 動作的意圖時,呼叫 requestPeers() 的方法。個人中心 就需要將此事件監聽器傳遞至接收器其中一個方法是直接傳送 做為廣播接收器建構函式的引數。

Kotlin

fun onReceive(context: Context, intent: Intent) {
    when (intent.action) {
        ...
        WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {

            // Request available peers from the wifi p2p manager. This is an
            // asynchronous call and the calling activity is notified with a
            // callback on PeerListListener.onPeersAvailable()
            mManager?.requestPeers(channel, peerListListener)
            Log.d(TAG, "P2P peers changed")


        }
        ...
    }
}

Java

public void onReceive(Context context, Intent intent) {
    ...
    else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {

        // Request available peers from the wifi p2p manager. This is an
        // asynchronous call and the calling activity is notified with a
        // callback on PeerListListener.onPeersAvailable()
        if (mManager != null) {
            mManager.requestPeers(channel, peerListListener);
        }
        Log.d(WiFiDirectActivity.TAG, "P2P peers changed");
    }...
}

現在,意圖包含動作 WIFI_P2P_PEERS_CHANGED_ACTION 的意圖 觸發更新同類群組清單的要求。

與同業交流

如要連線至對等點,請建立新的 WifiP2pConfig 物件,並將資料從 WifiP2pDevice 代表您想要的裝置 物件。然後呼叫 connect() 方法。

Kotlin

override fun connect() {
    // Picking the first device found on the network.
    val device = peers[0]

    val config = WifiP2pConfig().apply {
        deviceAddress = device.deviceAddress
        wps.setup = WpsInfo.PBC
    }

    manager.connect(channel, config, object : WifiP2pManager.ActionListener {

        override fun onSuccess() {
            // WiFiDirectBroadcastReceiver notifies us. Ignore for now.
        }

        override fun onFailure(reason: Int) {
            Toast.makeText(
                    this@WiFiDirectActivity,
                    "Connect failed. Retry.",
                    Toast.LENGTH_SHORT
            ).show()
        }
    })
}

Java

@Override
public void connect() {
    // Picking the first device found on the network.
    WifiP2pDevice device = peers.get(0);

    WifiP2pConfig config = new WifiP2pConfig();
    config.deviceAddress = device.deviceAddress;
    config.wps.setup = WpsInfo.PBC;

    manager.connect(channel, config, new ActionListener() {

        @Override
        public void onSuccess() {
            // WiFiDirectBroadcastReceiver notifies us. Ignore for now.
        }

        @Override
        public void onFailure(int reason) {
            Toast.makeText(WiFiDirectActivity.this, "Connect failed. Retry.",
                    Toast.LENGTH_SHORT).show();
        }
    });
}

如果群組中的每部裝置都支援 Wi-Fi Direct,您就不需要 ,在連線時明確要求群組的密碼。如何允許裝置 但不支援 Wi-Fi Direct 加入群組,但是您需要 請呼叫 requestGroupInfo(),如 如以下程式碼片段所示:

Kotlin

manager.requestGroupInfo(channel) { group ->
    val groupPassword = group.passphrase
}

Java

manager.requestGroupInfo(channel, new GroupInfoListener() {
  @Override
  public void onGroupInfoAvailable(WifiP2pGroup group) {
      String groupPassword = group.getPassphrase();
  }
});

請注意,WifiP2pManager.ActionListener connect() 方法只會在啟動時通知您 無論成功還是失敗如要監聽連線狀態的變更,請實作 WifiP2pManager.ConnectionInfoListener 介面。 且 onConnectionInfoAvailable() 回呼會在 連線變更。需要同時連上多部裝置時 在單一裝置上 (例如有三名以上玩家的遊戲或即時通訊應用程式) 時,一部裝置就大功告成 即為「群組擁有者」您可以指定將特定裝置 確認網路的群組擁有者 「建立群組」部分。

Kotlin

private val connectionListener = WifiP2pManager.ConnectionInfoListener { info ->

    // String from WifiP2pInfo struct
    val groupOwnerAddress: String = info.groupOwnerAddress.hostAddress

    // After the group negotiation, we can determine the group owner
    // (server).
    if (info.groupFormed && info.isGroupOwner) {
        // Do whatever tasks are specific to the group owner.
        // One common case is creating a group owner thread and accepting
        // incoming connections.
    } else if (info.groupFormed) {
        // The other device acts as the peer (client). In this case,
        // you'll want to create a peer thread that connects
        // to the group owner.
    }
}

Java

@Override
public void onConnectionInfoAvailable(final WifiP2pInfo info) {

    // String from WifiP2pInfo struct
    String groupOwnerAddress = info.groupOwnerAddress.getHostAddress();

    // After the group negotiation, we can determine the group owner
    // (server).
    if (info.groupFormed && info.isGroupOwner) {
        // Do whatever tasks are specific to the group owner.
        // One common case is creating a group owner thread and accepting
        // incoming connections.
    } else if (info.groupFormed) {
        // The other device acts as the peer (client). In this case,
        // you'll want to create a peer thread that connects
        // to the group owner.
    }
}

現在,返回廣播接收器的 onReceive() 方法,然後修改區段 監聽 WIFI_P2P_CONNECTION_CHANGED_ACTION 意圖 收到這項意圖時,請呼叫 requestConnectionInfo()。這是非同步呼叫 因此,結果會由您提供的連線資訊事件監聽器接收 參數。

Kotlin

when (intent.action) {
    ...
    WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {

        // Connection state changed! We should probably do something about
        // that.

        mManager?.let { manager ->

            val networkInfo: NetworkInfo? = intent
                    .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO) as NetworkInfo

            if (networkInfo?.isConnected == true) {

                // We are connected with the other device, request connection
                // info to find group owner IP

                manager.requestConnectionInfo(channel, connectionListener)
            }
        }
    }
    ...
}

Java

    ...
    } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {

        if (manager == null) {
            return;
        }

        NetworkInfo networkInfo = (NetworkInfo) intent
                .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);

        if (networkInfo.isConnected()) {

            // We are connected with the other device, request connection
            // info to find group owner IP

            manager.requestConnectionInfo(channel, connectionListener);
        }
        ...

建立群組

如果您希望執行應用程式的裝置擔任以下應用程式的群組擁有者: 支援網路,也就是不支援 Wi-Fi Direct 功能,請按照 連結至同類應用程式部分,除非您建立新的 WifiP2pManager.ActionListener 使用 createGroup(),而不是 connect()。在 WifiP2pManager.ActionListener 相同,如以下程式碼片段所示:

Kotlin

manager.createGroup(channel, object : WifiP2pManager.ActionListener {
    override fun onSuccess() {
        // Device is ready to accept incoming connections from peers.
    }

    override fun onFailure(reason: Int) {
        Toast.makeText(
                this@WiFiDirectActivity,
                "P2P group creation failed. Retry.",
                Toast.LENGTH_SHORT
        ).show()
    }
})

Java

manager.createGroup(channel, new WifiP2pManager.ActionListener() {
    @Override
    public void onSuccess() {
        // Device is ready to accept incoming connections from peers.
    }

    @Override
    public void onFailure(int reason) {
        Toast.makeText(WiFiDirectActivity.this, "P2P group creation failed. Retry.",
                Toast.LENGTH_SHORT).show();
    }
});

注意: 如果網路中的所有裝置都支援 Wi-Fi 您可以直接在裝置上使用 connect() 方法,因為 方法建立群組並自動選取版主。

建立群組後,你可以撥打 requestGroupInfo():擷取以下資料夾中的對等項目詳細資料: 包括裝置名稱和連線狀態