Wi-Fi Direct (P2P) позволяет устройствам с соответствующим оборудованием подключаться друг к другу напрямую по Wi-Fi без промежуточной точки доступа. Используя эти API, можно обнаруживать другие устройства и подключаться к ним, если каждое из них поддерживает Wi-Fi P2P, а затем обмениваться данными по высокоскоростному соединению на расстояниях, значительно превышающих расстояния по Bluetooth. Это полезно для приложений, обменивающихся данными между пользователями, например, многопользовательских игр или приложений для обмена фотографиями.
API Wi-Fi P2P состоят из следующих основных частей:
- Методы, позволяющие обнаруживать, запрашивать и подключаться к одноранговым узлам, которые определены в классе
WifiP2pManager
. - Прослушиватели, позволяющие получать уведомления об успешности или неудаче вызовов методов
WifiP2pManager
. При вызове методовWifiP2pManager
каждый метод может получать определенный прослушиватель, передаваемый в качестве параметра. - Намерения, уведомляющие вас о конкретных событиях, обнаруженных инфраструктурой Wi-Fi P2P, таких как разрыв соединения или обнаружение нового однорангового узла.
Эти три основных компонента API часто будут использоваться вместе. Например, вы можете предоставить WifiP2pManager.ActionListener
вызову discoverPeers()
, чтобы методы ActionListener.onSuccess()
и ActionListener.onFailure()
могли уведомлять вас. Намерение WIFI_P2P_PEERS_CHANGED_ACTION
также транслируется, если метод discoverPeers()
обнаруживает изменение списка пиров.
Обзор API
Класс WifiP2pManager
предоставляет методы, позволяющие взаимодействовать с оборудованием Wi-Fi на вашем устройстве, например, обнаруживать одноранговые устройства и подключаться к ним. Доступны следующие действия:
Таблица 1. Методы Wi-Fi P2P
Метод | Описание |
initialize() | Регистрирует приложение во фреймворке Wi-Fi. Вызывайте этот метод перед вызовом любого другого метода Wi-Fi P2P. |
connect() | Запускает одноранговое соединение с устройством с указанной конфигурацией. |
cancelConnect() | Отменяет любые текущие одноранговые групповые переговоры. |
requestConnectInfo() | Запрашивает информацию о подключении устройства. |
createGroup() | Создает одноранговую группу, где текущее устройство является владельцем группы. |
removeGroup() | Удаляет текущую одноранговую группу. |
requestGroupInfo() | Запрашивает информацию о одноранговой группе. |
discoverPeers() | Инициирует обнаружение сверстников. |
requestPeers() | Запрашивает текущий список обнаруженных одноранговых узлов. |
Методы WifiP2pManager
позволяют передавать прослушиватель, чтобы фреймворк Wi-Fi P2P мог уведомлять вас о состоянии вызова. Доступные интерфейсы прослушивателей и соответствующие вызовы методов WifiP2pManager
, использующие эти прослушиватели, описаны в таблице 2.
Таблица 2. Слушатели Wi-Fi P2P
Интерфейс прослушивателя | Связанные действия |
WifiP2pManager.ActionListener | connect() , cancelConnect() , createGroup() , removeGroup() и discoverPeers() |
WifiP2pManager.ChannelListener | initialize() |
WifiP2pManager.ConnectionInfoListener | requestConnectInfo() |
WifiP2pManager.GroupInfoListener | requestGroupInfo() |
WifiP2pManager.PeerListListener | requestPeers() |
API Wi-Fi P2P определяют намерения, которые передаются при возникновении определённых событий Wi-Fi P2P, например, при обнаружении нового однорангового узла или при изменении состояния Wi-Fi устройства. Вы можете зарегистрироваться для получения этих намерений в своём приложении, создав приёмник широковещательных сообщений , который обрабатывает эти намерения:
Таблица 3. Намерения Wi-Fi P2P
Намерение | Описание |
WIFI_P2P_CONNECTION_CHANGED_ACTION | Трансляция при изменении состояния Wi-Fi-подключения устройства. |
WIFI_P2P_PEERS_CHANGED_ACTION | Широковещательная рассылка при вызове discoverPeers() . Обычно вы вызываете requestPeers() для получения обновлённого списка одноранговых узлов, если обрабатываете это намерение в своём приложении. |
WIFI_P2P_STATE_CHANGED_ACTION | Трансляция при включении или выключении Wi-Fi P2P на устройстве. |
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION | Рассылка уведомлений об изменении данных устройства, например имени устройства. |
Создайте широковещательный приемник для намерений Wi-Fi P2P
Широковещательный приемник позволяет вам получать намерения, транслируемые системой Android, чтобы ваше приложение могло реагировать на интересующие вас события. Основные шаги по созданию широковещательного приемника для обработки намерений Wi-Fi P2P следующие:
Создайте класс, расширяющий класс
BroadcastReceiver
. В конструкторе класса вы будете использовать параметры дляWifiP2pManager
,WifiP2pManager.Channel
и активности, в которой будет зарегистрирован этот приёмник широковещательных сообщений. Это позволит приёмнику отправлять обновления активности, а также получать доступ к оборудованию Wi-Fi и каналу связи при необходимости.В приёмнике широковещательной рассылки проверьте наличие интересующих вас намерений в методе
onReceive()
. Выполните необходимые действия в зависимости от полученного намерения. Например, если приёмник широковещательной рассылки получает намерениеWIFI_P2P_PEERS_CHANGED_ACTION
, вы можете вызвать методrequestPeers()
, чтобы получить список обнаруженных в данный момент пиров.
Следующий код показывает, как создать типичный приёмник широковещательных сообщений. Приёмник принимает объект WifiP2pManager
и действие в качестве аргументов и использует эти два класса для выполнения необходимых действий при получении интента:
Котлин
/** * A BroadcastReceiver that notifies of important Wi-Fi p2p events. */ class WiFiDirectBroadcastReceiver( private val manager: WifiP2pManager, private val channel: WifiP2pManager.Channel, private val activity: MyWifiActivity ) : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { val action: String = intent.action when (action) { WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> { // Check to see if Wi-Fi is enabled and notify appropriate activity } WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { // Call WifiP2pManager.requestPeers() to get a list of current peers } WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { // Respond to new connection or disconnections } WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> { // Respond to this device's wifi state changing } } } }
Ява
/** * A BroadcastReceiver that notifies of important Wi-Fi p2p events. */ public class WiFiDirectBroadcastReceiver extends BroadcastReceiver { private WifiP2pManager manager; private Channel channel; private MyWiFiActivity activity; public WiFiDirectBroadcastReceiver(WifiP2pManager manager, Channel channel, MyWifiActivity activity) { super(); this.manager = manager; this.channel = channel; this.activity = activity; } @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { // Check to see if Wi-Fi is enabled and notify appropriate activity } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // Call WifiP2pManager.requestPeers() to get a list of current peers } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { // Respond to new connection or disconnections } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) { // Respond to this device's wifi state changing } } }
На устройствах под управлением Android 10 и выше следующие намерения трансляции не являются фиксированными:
-
WIFI_P2P_CONNECTION_CHANGED_ACTION
- Приложения могут использовать
requestConnectionInfo()
,requestNetworkInfo()
илиrequestGroupInfo()
для получения информации о текущем соединении. -
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
- Приложения могут использовать
requestDeviceInfo()
для получения информации о текущем соединении.
Создайте приложение Wi-Fi P2P
Создание приложения Wi-Fi P2P включает в себя создание и регистрацию приёмника широковещательной рассылки для вашего приложения, обнаружение одноранговых узлов, подключение к одноранговому узлу и передачу данных одноранговому узлу. В следующих разделах описывается, как это сделать.
Начальная настройка
Перед использованием API Wi-Fi P2P необходимо убедиться, что ваше приложение может получить доступ к оборудованию и что устройство поддерживает протокол Wi-Fi P2P. Если Wi-Fi P2P поддерживается, вы можете получить экземпляр WifiP2pManager
, создать и зарегистрировать свой вещательный приёмник и начать использовать API Wi-Fi P2P.
Запросите разрешение на использование оборудования Wi-Fi на устройстве и укажите в манифесте Android, что у вашего приложения должна быть правильная минимальная версия 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" /> <!-- 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: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" />
Помимо указанных выше разрешений, для следующих API также требуется включение режима определения местоположения:
Проверьте, включен ли и поддерживается ли Wi-Fi P2P. Это можно проверить в вашем вещательном приёмнике, когда он получает намерение
WIFI_P2P_STATE_CHANGED_ACTION
. Уведомляйте свою активность о состоянии Wi-Fi P2P и реагируйте соответствующим образом:Котлин
override fun onReceive(context: Context, intent: Intent) { ... val action: String = intent.action when (action) { WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> { val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1) when (state) { WifiP2pManager.WIFI_P2P_STATE_ENABLED -> { // Wifi P2P is enabled } else -> { // Wi-Fi P2P is not enabled } } } } ... }
Ява
@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 is enabled } else { // Wi-Fi P2P is not enabled } } ... }
В методе
onCreate()
вашей активности получите экземплярWifiP2pManager
и зарегистрируйте приложение во фреймворке Wi-Fi P2P, вызвав методinitialize()
. Этот метод возвращает объектWifiP2pManager.Channel
, который используется для подключения вашего приложения к фреймворку Wi-Fi P2P. Вам также следует создать экземпляр широковещательного приёмника с объектамиWifiP2pManager
иWifiP2pManager.Channel
вместе со ссылкой на вашу активность. Это позволит широковещательному приёмнику уведомлять вашу активность об интересных событиях и обновлять её соответствующим образом. Кроме того, это позволит вам при необходимости управлять состоянием Wi-Fi устройства:Котлин
val manager: WifiP2pManager? by lazy(LazyThreadSafetyMode.NONE) { getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager? } var channel: WifiP2pManager.Channel? = null var receiver: BroadcastReceiver? = null override fun onCreate(savedInstanceState: Bundle?) { ... channel = manager?.initialize(this, mainLooper, null) channel?.also { channel -> receiver = WiFiDirectBroadcastReceiver(manager, channel, this) } }
Ява
WifiP2pManager manager; Channel channel; BroadcastReceiver receiver; ... @Override protected void onCreate(Bundle savedInstanceState){ ... manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); channel = manager.initialize(this, getMainLooper(), null); receiver = new WiFiDirectBroadcastReceiver(manager, channel, this); ... }
Создайте фильтр намерений и добавьте те же намерения, которые проверяет ваш приемник вещания:
Котлин
val intentFilter = IntentFilter().apply { addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) }
Ява
IntentFilter intentFilter; ... @Override protected void onCreate(Bundle savedInstanceState){ ... intentFilter = new IntentFilter(); intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); ... }
Зарегистрируйте приемник вещания в методе
onResume()
вашей активности и отмените его регистрацию в методеonPause()
вашей активности:Котлин
/* register the broadcast receiver with the intent values to be matched */ override fun onResume() { super.onResume() receiver?.also { receiver -> registerReceiver(receiver, intentFilter) } } /* unregister the broadcast receiver */ override fun onPause() { super.onPause() receiver?.also { receiver -> unregisterReceiver(receiver) } }
Ява
/* register the broadcast receiver with the intent values to be matched */ @Override protected void onResume() { super.onResume(); registerReceiver(receiver, intentFilter); } /* unregister the broadcast receiver */ @Override protected void onPause() { super.onPause(); unregisterReceiver(receiver); }
После получения
WifiP2pManager.Channel
и настройки широковещательного приемника ваше приложение сможет выполнять вызовы методов Wi-Fi P2P и получать намерения Wi-Fi P2P.Реализуйте свое приложение, используя функции Wi-Fi P2P, вызывая методы в
WifiP2pManager
.
В следующих разделах описывается, как выполнять общие действия, такие как обнаружение и подключение к одноранговым узлам.
Найдите сверстников
Вызовите discoverPeers()
для обнаружения доступных одноранговых узлов, находящихся в зоне действия и доступных для подключения. Вызов этой функции асинхронный, и информация об успешном или неудачном завершении передается в приложение с помощью onSuccess()
и onFailure()
если вы создали WifiP2pManager.ActionListener
. Метод onSuccess()
только уведомляет об успешном завершении процесса обнаружения и не предоставляет никакой информации о фактически обнаруженных одноранговых узлах, если таковые имеются. В следующем примере кода показано, как это настроить.
Котлин
manager?.discoverPeers(channel, object : WifiP2pManager.ActionListener { override fun onSuccess() { ... } override fun onFailure(reasonCode: Int) { ... } })
Ява
manager.discoverPeers(channel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { ... } @Override public void onFailure(int reasonCode) { ... } });
Если процесс обнаружения проходит успешно и пиры обнаружены, система передает широковещательное сообщение WIFI_P2P_PEERS_CHANGED_ACTION
, которое можно прослушивать в широковещательном приемнике для получения списка пиров. Когда ваше приложение получает сообщение WIFI_P2P_PEERS_CHANGED_ACTION
, вы можете запросить список обнаруженных пиров с помощью requestPeers()
. В следующем коде показано, как это настроить.
Котлин
override fun onReceive(context: Context, intent: Intent) { val action: String = intent.action when (action) { ... WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { manager?.requestPeers(channel) { peers: WifiP2pDeviceList? -> // Handle peers list } } ... } }
Ява
PeerListListener myPeerListListener; ... 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 (manager != null) { manager.requestPeers(channel, myPeerListListener); } }
Метод requestPeers()
также является асинхронным и может уведомлять вашу активность о доступности списка пиров с помощью метода onPeersAvailable()
, который определён в интерфейсе WifiP2pManager.PeerListListener
. Метод onPeersAvailable()
предоставляет список WifiP2pDeviceList
, который можно перебирать для поиска пиров для подключения.
Подключайтесь к коллегам
Получив список возможных пиров и выбрав устройство для подключения, вызовите метод connect()
. Для этого метода требуется объект WifiP2pConfig
, содержащий информацию об устройстве, к которому вы подключаетесь. WifiP2pManager.ActionListener
может уведомить вас об успешном или неудачном подключении. Следующий код демонстрирует, как создать подключение к устройству.
Котлин
val device: WifiP2pDevice = ... val config = WifiP2pConfig() config.deviceAddress = device.deviceAddress channel?.also { channel -> manager?.connect(channel, config, object : WifiP2pManager.ActionListener { override fun onSuccess() { //success logic } override fun onFailure(reason: Int) { //failure logic } } )}
Ява
//obtain a peer from the WifiP2pDeviceList WifiP2pDevice device; WifiP2pConfig config = new WifiP2pConfig(); config.deviceAddress = device.deviceAddress; manager.connect(channel, config, new ActionListener() { @Override public void onSuccess() { //success logic } @Override public void onFailure(int reason) { //failure logic } });
Передача данных
После установки соединения вы можете передавать данные между устройствами через сокеты. Основные этапы передачи данных следующие:
- Создайте
ServerSocket
. Этот сокет ожидает подключения от клиента по указанному порту и блокируется до его появления, поэтому работайте в фоновом потоке. - Создайте клиентский
Socket
. Клиент использует IP-адрес и порт серверного сокета для подключения к серверному устройству. - Передача данных от клиента к серверу. После успешного подключения клиентского сокета к серверному сокету данные от клиента к серверу могут передаваться потоками байтов.
- Серверный сокет ожидает клиентского подключения (с помощью метода
accept()
). Этот вызов блокируется до тех пор, пока клиент не подключится, поэтому вызовите его в другом потоке. После установления соединения серверное устройство может получить данные от клиента.
Следующий пример, модифицированный из демонстрации Wi-Fi P2P , показывает, как создать клиент-серверное сокетное соединение и передавать изображения JPEG с клиента на сервер с помощью сервиса. Чтобы получить полностью работающий пример, скомпилируйте и запустите демонстрационную версию.
Котлин
class FileServerAsyncTask( private val context: Context, private var statusText: TextView ) : AsyncTask<Void, Void, String?>() { override fun doInBackground(vararg params: Void): String? { /** * Create a server socket. */ val serverSocket = ServerSocket(8888) return serverSocket.use { /** * Wait for client connections. This call blocks until a * connection is accepted from a client. */ val client = serverSocket.accept() /** * If this code is reached, a client has connected and transferred data * Save the input stream from the client as a JPEG file */ val f = File(Environment.getExternalStorageDirectory().absolutePath + "/${context.packageName}/wifip2pshared-${System.currentTimeMillis()}.jpg") val dirs = File(f.parent) dirs.takeIf { it.doesNotExist() }?.apply { mkdirs() } f.createNewFile() val inputstream = client.getInputStream() copyFile(inputstream, FileOutputStream(f)) serverSocket.close() f.absolutePath } } private fun File.doesNotExist(): Boolean = !exists() /** * Start activity that can handle the JPEG image */ override fun onPostExecute(result: String?) { result?.run { statusText.text = "File copied - $result" val intent = Intent(android.content.Intent.ACTION_VIEW).apply { setDataAndType(Uri.parse("file://$result"), "image/*") } context.startActivity(intent) } } }
Ява
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 { /** * Create a server socket and wait for client connections. This * call blocks until a connection is accepted from a client */ ServerSocket serverSocket = new ServerSocket(8888); Socket client = serverSocket.accept(); /** * If this code is reached, a client has connected and transferred data * Save the input stream from the client as a JPEG file */ 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; } } /** * Start activity that can handle the JPEG image */ @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); } } }
На клиенте подключитесь к сокету сервера через сокет клиента и передайте данные. В этом примере JPEG-файл передаётся в файловую систему клиентского устройства.
Котлин
val context = applicationContext val host: String val port: Int val len: Int val socket = Socket() val buf = ByteArray(1024) ... try { /** * Create a client socket with the host, * port, and timeout information. */ socket.bind(null) socket.connect((InetSocketAddress(host, port)), 500) /** * Create a byte stream from a JPEG file and pipe it to the output stream * of the socket. This data is retrieved by the server device. */ val outputStream = socket.getOutputStream() val cr = context.contentResolver val inputStream: InputStream = cr.openInputStream(Uri.parse("path/to/picture.jpg")) while (inputStream.read(buf).also { len = it } != -1) { outputStream.write(buf, 0, len) } outputStream.close() inputStream.close() } catch (e: FileNotFoundException) { //catch logic } catch (e: IOException) { //catch logic } finally { /** * Clean up any open sockets when done * transferring or if an exception occurred. */ socket.takeIf { it.isConnected }?.apply { close() } }
Ява
Context context = this.getApplicationContext(); String host; int port; int len; Socket socket = new Socket(); byte buf[] = new byte[1024]; ... try { /** * Create a client socket with the host, * port, and timeout information. */ socket.bind(null); socket.connect((new InetSocketAddress(host, port)), 500); /** * Create a byte stream from a JPEG file and pipe it to the output stream * of the socket. This data is retrieved by the server device. */ 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 } /** * Clean up any open sockets when done * transferring or if an exception occurred. */ finally { if (socket != null) { if (socket.isConnected()) { try { socket.close(); } catch (IOException e) { //catch logic } } } }