وایفای دایرکت (که با نام نظیر به نظیر یا P2P نیز شناخته میشود) به برنامه شما اجازه میدهد تا به سرعت دستگاههای مجاور را در محدودهای فراتر از قابلیتهای بلوتوث پیدا کرده و با آنها تعامل داشته باشد.
رابطهای برنامهنویسی کاربردی (API) وایفای دایرکت (P2P) به برنامهها اجازه میدهند بدون نیاز به اتصال به شبکه یا هاتاسپات، به دستگاههای مجاور متصل شوند. اگر برنامه شما طوری طراحی شده است که بخشی از یک شبکه امن و نزدیک به برد باشد، وایفای دایرکت به دلایل زیر گزینه مناسبتری نسبت به شبکههای سنتی وایفای ad-hoc است:
- وایفای دایرکت از رمزگذاری WPA2 پشتیبانی میکند. (بعضی از شبکههای ad-hoc فقط از رمزگذاری WEP پشتیبانی میکنند.)
- دستگاهها میتوانند خدماتی را که ارائه میدهند، پخش کنند که به سایر دستگاهها کمک میکند تا راحتتر جفتهای مناسب را پیدا کنند.
- هنگام تعیین اینکه کدام دستگاه باید مالک گروه شبکه باشد، Wi-Fi Direct مدیریت انرژی، رابط کاربری و قابلیتهای سرویس هر دستگاه را بررسی میکند و از این اطلاعات برای انتخاب دستگاهی که میتواند مسئولیتهای سرور را به طور مؤثرتری انجام دهد، استفاده میکند.
- اندروید از حالت وایفای ad-hoc پشتیبانی نمیکند.
این درس به شما نشان میدهد که چگونه با استفاده از Wi-Fi P2P دستگاههای مجاور را پیدا کرده و به آنها متصل شوید.
تنظیم مجوزهای برنامه
برای استفاده از Wi-Fi Direct، مجوزهای ACCESS_FINE_LOCATION ، CHANGE_WIFI_STATE ، ACCESS_WIFI_STATE و INTERNET را به مانیفست خود اضافه کنید. اگر برنامه شما اندروید ۱۳ (سطح API ۳۳) یا بالاتر را هدف قرار میدهد، مجوز NEARBY_WIFI_DEVICES را نیز به مانیفست خود اضافه کنید. Wi-Fi Direct نیازی به اتصال به اینترنت ندارد، اما از سوکتهای استاندارد جاوا استفاده میکند که به مجوز 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، باید به intent های broadcast گوش دهید که به برنامه شما اطلاع میدهند چه زمانی رویدادهای خاصی رخ دادهاند. در برنامه خود، یک IntentFilter نمونهسازی کنید و آن را طوری تنظیم کنید که به موارد زیر گوش دهد:
-
WIFI_P2P_STATE_CHANGED_ACTION - نشان میدهد که آیا Wi-Fi Direct فعال است یا خیر
-
WIFI_P2P_PEERS_CHANGED_ACTION - نشان میدهد که لیست همتایان موجود تغییر کرده است.
-
WIFI_P2P_CONNECTION_CHANGED_ACTION - نشان میدهد که وضعیت اتصال Wi-Fi Direct تغییر کرده است. از اندروید ۱۰ به بعد، این وضعیت Sticky نیست. اگر برنامه شما به دلیل Sticky بودن این Broadcastها، در زمان ثبت به دریافت آنها متکی بوده است، در هنگام مقداردهی اولیه از متد
getمناسب برای دریافت اطلاعات استفاده کنید. -
WIFI_P2P_THIS_DEVICE_CHANGED_ACTION - نشان میدهد که جزئیات پیکربندی این دستگاه تغییر کرده است. از اندروید ۱۰ به بعد، این وضعیت چسبنده نیست. اگر برنامه شما به دلیل چسبنده بودن این پخشها، در هنگام ثبت به دریافت آنها متکی بوده است، در هنگام مقداردهی اولیه از متد
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)); } }
در نهایت، کدی اضافه کنید تا فیلتر intent و گیرنده پخش را هنگام فعال بودن activity اصلی ثبت کند و هنگام توقف activity، آنها را از حالت ثبت خارج کند. بهترین مکان برای انجام این کار، متدهای 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() گیرندهی پخش خود را طوری تغییر دهید که وقتی یک intent با اکشن WIFI_P2P_PEERS_CHANGED_ACTION دریافت میشود، requestPeers() فراخوانی کند. شما باید این listener را به نحوی به گیرنده منتقل کنید. یک راه این است که آن را به عنوان یک آرگومان به سازندهی گیرندهی پخش ارسال کنید.
کاتلین
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"); }... }
اکنون، یک intent با اکشن WIFI_P2P_PEERS_CHANGED_ACTION درخواستی برای فهرست بهروزرسانیشدهی peerها را فعال میکند.
به یک همتا متصل شوید
برای اتصال به یک نظیر، یک شیء 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 پشتیبانی نمیکنند - عمل کند، همان توالی مراحل بخش Connect to a Peer را دنبال میکنید، با این تفاوت که به جای connect() با استفاده از createGroup() یک WifiP2pManager.ActionListener جدید ایجاد میکنید. مدیریت فراخوانی در 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() را برای بازیابی جزئیات مربوط به نظیرهای موجود در شبکه، از جمله نام دستگاهها و وضعیت اتصال، فراخوانی کنید.