Wi-Fi Direct (که با نام همتا به همتا یا P2P نیز شناخته میشود) به برنامه شما امکان میدهد بهسرعت دستگاههای اطراف را پیدا کند و با آنها تعامل داشته باشد، در محدودهای فراتر از قابلیتهای بلوتوث.
Wi-Fi Direct (P2P) APIs allow applications to connect to nearby devices without needing to connect to a network or hotspot. اگر برنامهتان طوری طراحی شده است که بخشی از شبکه امن و نزدیکبرد باشد، 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 (سطح میانای برنامهسازی کاربردی ۳۳) یا بالاتر را هدف قرار میدهد،
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"/> ...
علاوهبر اجازههای قبلی، میاناهای برنامهسازی کاربردی زیر نیز به فعال بودن «حالت مکان» نیاز دارند:
راهاندازی گیرنده رویداد ثبتشده و مدیر همتا به همتا
برای استفاده از 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() فرایند کشف را شروع میکند و سپس
بلافاصله برمیگردد. اگر فرایند کشف همتا با موفقیت ازطریق فراخوانی روشها در شنونده کنش ارائهشده شروع شود، سیستم به شما اطلاع میدهد.
همچنین، کاوش تا زمانی که اتصالی برقرار شود یا گروه همتابههمتایی تشکیل شود فعال میماند.
واکشی فهرست همتاها
اکنون کدی را بنویسید که فهرست همتایان را واکشی و پردازش میکند. ابتدا
میانای 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() گیرنده رویداد ثبتشده را اصلاح کنید تا وقتی قصدی با کنش WIFI_P2P_PEERS_CHANGED_ACTION دریافت میشود، requestPeers() را فراخوانی کند. باید
این شنونده را بهنحوی به گیرنده منتقل کنید. یک راه این است که آن را بهعنوان آرگومان به سازنده گیرنده رویداد ثبتشده ارسال کنید.
کاتلین
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() را فراخوانی کنید تا جزئیات مربوط به همتایان در
شبکه، ازجمله نام دستگاهها و وضعیت اتصال، را بازیابی کنید.