Создавайте P2P-соединения с помощью Wi-Fi Direct

Wi-Fi Direct (также известный как одноранговая сеть или P2P) позволяет вашему приложению быстро находить и взаимодействовать с близлежащими устройствами на расстоянии, превышающем возможности Bluetooth.

API Wi-Fi Direct (P2P) позволяют приложениям подключаться к близлежащим устройствам без необходимости подключения к сети или точке доступа. Если ваше приложение предназначено для работы в безопасной сети ближнего действия, Wi-Fi Direct является более подходящим вариантом, чем традиционная сеть Wi-Fi ad-hoc, по следующим причинам:

  • Wi-Fi Direct поддерживает шифрование WPA2. (Некоторые сети ad hoc поддерживают только шифрование WEP.)
  • Устройства могут транслировать предоставляемые ими услуги, что помогает другим устройствам легче находить подходящих партнеров.
  • При определении того, какое устройство должно стать владельцем группы для сети, Wi-Fi Direct проверяет управление питанием, пользовательский интерфейс и возможности обслуживания каждого устройства и использует эту информацию для выбора устройства, которое может наиболее эффективно выполнять функции сервера.
  • Android не поддерживает режим Wi-Fi ad-hoc.

В этом уроке вы узнаете, как находить близлежащие устройства и подключаться к ним с помощью Wi-Fi P2P.

Настройте разрешения приложений

Чтобы использовать Wi-Fi Direct, добавьте разрешения ACCESS_FINE_LOCATION , CHANGE_WIFI_STATE , ACCESS_WIFI_STATE и INTERNET в свой манифест. Если ваше приложение предназначено для Android 13 (уровень API 33) или выше, также добавьте разрешение NEARBY_WIFI_DEVICES в свой манифест. Wi-Fi Direct не требует подключения к Интернету, но использует стандартные сокеты 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 при инициализации, чтобы получить информацию.

Котлин

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)
    ...
}

Ява

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.

Котлин

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)
}

Ява

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() добавьте условие для обработки каждого изменения состояния, перечисленного выше.

Котлин

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
                        )
                    }
        }
    }
}

Ява

@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() .

Котлин

/** 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)
}

Ява

/** 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() . Этот метод принимает следующие аргументы:

  • WifiP2pManager.Channel , который вы получили при инициализации однорангового mManager
  • Реализация WifiP2pManager.ActionListener с методами, которые система вызывает для успешного и неуспешного обнаружения.

Котлин

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.
    }
})

Ява

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. Эта информация также позволяет вашему приложению определять, когда пиры присоединяются к сети или покидают ее. Следующий фрагмент кода иллюстрирует эти операции, связанные с пирами:

Котлин

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
    }
}

Ява

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() вашего приемника вещания, чтобы он вызывал requestPeers() при получении намерения с действием WIFI_P2P_PEERS_CHANGED_ACTION . Вам нужно каким-то образом передать этот слушатель в приемник. Один из способов — отправить его в качестве аргумента конструктору приемника вещания.

Котлин

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")


        }
        ...
    }
}

Ява

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() .

Котлин

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()
        }
    })
}

Ява

@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() , как показано в следующем фрагменте кода:

Котлин

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

Ява

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

Обратите внимание, что WifiP2pManager.ActionListener , реализованный в методе connect() уведомляет вас только об успешном или неудачном инициировании . Чтобы прослушивать изменения состояния соединения, реализуйте интерфейс WifiP2pManager.ConnectionInfoListener . Его обратный вызов onConnectionInfoAvailable() уведомляет вас об изменении состояния соединения. В случаях, когда несколько устройств будут подключены к одному устройству (например, игра с тремя или более игроками или приложение для чата), одно устройство назначается «владельцем группы». Вы можете назначить определенное устройство владельцем группы сети, выполнив шаги в разделе «Создание группы» .

Котлин

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.
    }
}

Ява

@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() . Это асинхронный вызов, поэтому результаты получаются прослушивателем информации о подключении, который вы предоставляете в качестве параметра.

Котлин

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)
            }
        }
    }
    ...
}

Ява

    ...
    } 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 такая же, как показано в следующем фрагменте кода:

Котлин

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()
    }
})

Ява

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 Direct, вы можете использовать метод connect() на каждом устройстве, поскольку этот метод затем создает группу и автоматически выбирает владельца группы.

После создания группы вы можете вызвать requestGroupInfo() чтобы получить сведения об участниках сети, включая имена устройств и статусы подключения.

,

Wi-Fi Direct (также известный как одноранговая сеть или P2P) позволяет вашему приложению быстро находить и взаимодействовать с близлежащими устройствами на расстоянии, превышающем возможности Bluetooth.

API Wi-Fi Direct (P2P) позволяют приложениям подключаться к близлежащим устройствам без необходимости подключения к сети или точке доступа. Если ваше приложение предназначено для работы в безопасной сети ближнего действия, Wi-Fi Direct является более подходящим вариантом, чем традиционная сеть Wi-Fi ad-hoc, по следующим причинам:

  • Wi-Fi Direct поддерживает шифрование WPA2. (Некоторые сети ad hoc поддерживают только шифрование WEP.)
  • Устройства могут транслировать предоставляемые ими услуги, что помогает другим устройствам легче находить подходящих партнеров.
  • При определении того, какое устройство должно стать владельцем группы для сети, Wi-Fi Direct проверяет управление питанием, пользовательский интерфейс и возможности обслуживания каждого устройства и использует эту информацию для выбора устройства, которое может наиболее эффективно выполнять функции сервера.
  • Android не поддерживает режим Wi-Fi ad-hoc.

В этом уроке вы узнаете, как находить близлежащие устройства и подключаться к ним с помощью Wi-Fi P2P.

Настройте разрешения приложений

Чтобы использовать Wi-Fi Direct, добавьте разрешения ACCESS_FINE_LOCATION , CHANGE_WIFI_STATE , ACCESS_WIFI_STATE и INTERNET в свой манифест. Если ваше приложение предназначено для Android 13 (уровень API 33) или выше, также добавьте разрешение NEARBY_WIFI_DEVICES в свой манифест. Wi-Fi Direct не требует подключения к Интернету, но использует стандартные сокеты 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 при инициализации, чтобы получить информацию.

Котлин

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)
    ...
}

Ява

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.

Котлин

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)
}

Ява

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() добавьте условие для обработки каждого изменения состояния, перечисленного выше.

Котлин

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
                        )
                    }
        }
    }
}

Ява

@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() .

Котлин

/** 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)
}

Ява

/** 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() . Этот метод принимает следующие аргументы:

  • WifiP2pManager.Channel , который вы получили при инициализации однорангового mManager
  • Реализация WifiP2pManager.ActionListener с методами, которые система вызывает для успешного и неуспешного обнаружения.

Котлин

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.
    }
})

Ява

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. Эта информация также позволяет вашему приложению определять, когда пиры присоединяются к сети или покидают ее. Следующий фрагмент кода иллюстрирует эти операции, связанные с пирами:

Котлин

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
    }
}

Ява

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() вашего приемника вещания, чтобы он вызывал requestPeers() при получении намерения с действием WIFI_P2P_PEERS_CHANGED_ACTION . Вам нужно каким-то образом передать этот слушатель в приемник. Один из способов — отправить его в качестве аргумента конструктору приемника вещания.

Котлин

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")


        }
        ...
    }
}

Ява

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() .

Котлин

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()
        }
    })
}

Ява

@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() , как показано в следующем фрагменте кода:

Котлин

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

Ява

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

Обратите внимание, что WifiP2pManager.ActionListener , реализованный в методе connect() уведомляет вас только об успешном или неудачном инициировании . Чтобы прослушивать изменения состояния соединения, реализуйте интерфейс WifiP2pManager.ConnectionInfoListener . Его обратный вызов onConnectionInfoAvailable() уведомляет вас об изменении состояния соединения. В случаях, когда несколько устройств будут подключены к одному устройству (например, игра с тремя или более игроками или приложение для чата), одно устройство назначается «владельцем группы». Вы можете назначить определенное устройство владельцем группы сети, выполнив шаги в разделе «Создание группы» .

Котлин

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.
    }
}

Ява

@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() . Это асинхронный вызов, поэтому результаты получаются прослушивателем информации о подключении, который вы предоставляете в качестве параметра.

Котлин

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)
            }
        }
    }
    ...
}

Ява

    ...
    } 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 такая же, как показано в следующем фрагменте кода:

Котлин

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()
    }
})

Ява

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 Direct, вы можете использовать метод connect() на каждом устройстве, поскольку этот метод затем создает группу и автоматически выбирает владельца группы.

После создания группы вы можете вызвать requestGroupInfo() чтобы получить сведения об участниках сети, включая имена устройств и статусы подключения.