Технология 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. Вызывайте этот метод перед вызовом любого другого метода P2P-соединения по Wi-Fi. |
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. Намерения P2P в Wi-Fi.
| Намерение | Описание |
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 | Сообщает об изменении данных устройства, например, его названия. |
Создайте широковещательный приемник для P2P-интентов Wi-Fi.
Приёмник широковещательных сообщений позволяет принимать интенты, передаваемые системой Android, чтобы ваше приложение могло реагировать на интересующие вас события. Основные шаги по созданию приёмника широковещательных сообщений для обработки интентов Wi-Fi P2P следующие:
Создайте класс, расширяющий класс
BroadcastReceiver. В конструкторе класса используйте параметры дляWifiP2pManager,WifiP2pManager.Channelи активности, в которой будет зарегистрирован этот широковещательный приемник. Это позволит широковещательному приемнику отправлять обновления в активность, а также иметь доступ к Wi-Fi-оборудованию и каналу связи при необходимости.В широковещательном приемнике проверьте наличие интересующих вас интентов в методе
onReceive(). Выполните необходимые действия в зависимости от полученного интента. Например, если широковещательный приемник получает интентWIFI_P2P_PEERS_CHANGED_ACTION, вы можете вызвать методrequestPeers(), чтобы получить список обнаруженных в данный момент пиров.
Приведенный ниже код демонстрирует, как создать типичный широковещательный приемник. Широковещательный приемник принимает в качестве аргументов объект WifiP2pManager и Activity и использует эти два класса для соответствующего выполнения необходимых действий при получении намерения:
Котлин
/** * 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 } } } }
Java
/** * 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()для получения информации о текущем соединении.
Создайте приложение для P2P-сети Wi-Fi.
Создание приложения для 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 } } } } ... }
Java
@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()вашего Activity получите экземплярWifiP2pManagerи зарегистрируйте ваше приложение в фреймворке Wi-Fi P2P, вызвав методinitialize(). Этот метод возвращает объектWifiP2pManager.Channel, который используется для подключения вашего приложения к фреймворку Wi-Fi P2P. Вам также следует создать экземпляр вашего широковещательного приемника с объектамиWifiP2pManagerиWifiP2pManager.Channel, а также ссылкой на ваше Activity. Это позволит вашему широковещательному приемнику уведомлять ваше Activity о важных событиях и соответствующим образом обновлять его. Это также позволит вам при необходимости управлять состоянием 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) } }
Java
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) }
Java
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) } }
Java
/* 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) { ... } })
Java
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 } } ... } }
Java
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 } } )}
Java
//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) } } }
Java
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() } }
Java
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 } } } }