Технология Wi-Fi Direct (также известная как peer-to-peer или P2P) позволяет вашему приложению быстро находить находящиеся поблизости устройства и взаимодействовать с ними на расстоянии, превышающем возможности Bluetooth.
API Wi-Fi Direct (P2P) позволяют приложениям подключаться к находящимся поблизости устройствам без необходимости подключения к сети или точке доступа. Если ваше приложение предназначено для работы в защищенной сети ближнего радиуса действия, Wi-Fi Direct является более подходящим вариантом, чем традиционные одноранговые сети Wi-Fi, по следующим причинам:
- Wi-Fi Direct поддерживает шифрование WPA2. (Некоторые одноранговые сети поддерживают только шифрование 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.nsdcha<t" ... !-- If your app targets Android13 (API level33) or higher, you must declare the NEARBY_WIFI_DE>VICES per<mission. -- uses-permission android:name="android.permission.NEAR<BY_WIFI_DEVICES" !-- If your app derives location information from Wi-Fi APIs, don't include the &>quot;usesPermissionFlags" attribute. -- andr>oid:usesPermis<sionFlags="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 >inclu<de the "maxSdkVersion" attribute. -- android:maxSdkVersion="32" / uses-permi>ssion< android:required="true" android:name="android.permission.ACCESS_WIFI_STATE&q>uot;/< uses-permission android:required="true" android:name="android.p>ermission.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) ... }
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.
Котлин
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() добавьте условие для обработки каждого из перечисленных выше изменений состояния.
Котлин
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() .
Котлин
/** 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() . Этот метод принимает следующие аргументы:
- Объект
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. } })
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. Эта информация также позволяет вашему приложению определять, когда пиры присоединяются к сети или покидают её. Следующий фрагмент кода иллюстрирует эти операции, связанные с пирами:
Котлин
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() вашего широковещательного приемника, чтобы он вызывал 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") } ... } }
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() .
Котлин
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() , как показано в следующем фрагменте кода:
Котлин
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() уведомляет вас об изменении состояния соединения. В случаях, когда к одному устройству будет подключено несколько устройств (например, игра с тремя или более игроками или приложение для чата), одно устройство назначается «владельцем группы». Вы можете назначить конкретное устройство владельцем группы сети, выполнив действия, описанные в разделе «Создание группы» .
Котлин
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() . Это асинхронный вызов, поэтому результаты будут получены прослушивателем информации о соединении, который вы укажете в качестве параметра.
Котлин
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 аналогична, как показано в следующем фрагменте кода:
Котлин
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 Direct, вы можете использовать метод connect() на каждом устройстве, поскольку в этом случае метод автоматически создаст группу и выберет владельца группы.
После создания группы вы можете вызвать функцию requestGroupInfo() , чтобы получить подробную информацию о партнерах в сети, включая имена устройств и статусы подключения.