📶WiFi P2P (Direct)

Destekleyen cihazlar için android WiFi P2P bağlantısı

🏗️ WiFi P2P Activity Oluşturma

👨‍💻 WiFi P2P Sınıfını Kodlama

class WifiP2pActivity : AppCompatActivity() {

    /**
     * WiFi değişikliklerinde receiver'ı çalıştırma
     */
    private lateinit var manager: WifiP2pManager

    /**
     * WiFi P2P Framework'ü ile uygulamamıza bağlanmayı sağlayacak obje
     */
    private lateinit var channel: WifiP2pManager.Channel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_wi_fi_direct)

        initWifiP2p()
    }

    private fun initWifiP2p(): Unit {
        manager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
        channel = manager.initialize(this, mainLooper, null)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            // getWifiP2PPermissions()
        }
    }
    // ...
}

💎 WiFi P2P Durumları

👮‍♂️ Gerekli İzinlerin Alınması

📝 Manifest Dosyasına İzinleri Kaydetme

Manifest dosyasının içeriğine aşağıdaki kodu ekleyin

AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.nsdchat"
    ...
    
    <!-- Wi-Fi Direct (P2P) bağlantısı için eklendi -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.INTERNET" />
    ...

🧰 API için Ek Olarak Gereken İzinler

Aşağıdaki metotlar Location Mode (Konum Hizmeti) özelliğinin aktif olmasına da ihtiyaç duyar

👨‍💻 Kod Tarafında İzin İsteme

val PRC_ACCESS_FINE_LOCATION = 1

@RequiresApi(Build.VERSION_CODES.M)
	private fun getWifiP2PPermissions() {
		if (!hasWifiP2PPermission()) {
			requestWifiP2PPermissions()
		}
	}

	@RequiresApi(Build.VERSION_CODES.M)
	private fun hasWifiP2PPermission(): Boolean {
		return checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) 
		== PackageManager.PERMISSION_GRANTED
	}

	@RequiresApi(Build.VERSION_CODES.M)
	private fun requestWifiP2PPermissions() {
		requestPermissions(
			arrayOf(Manifest.permission.ACCESS_FINE_LOCATION), 
			PRC_ACCESS_FINE_LOCATION
		)
	}

	override fun onRequestPermissionsResult(
		requestCode: Int, 
		permissions: Array<out String>, 
		grantResults: IntArray
	) {
		super.onRequestPermissionsResult(requestCode, permissions, grantResults)
		when(requestCode) {
			PRC_ACCESS_FINE_LOCATION -> 
				if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
					Toast.makeText(this, "Konum izni gereklidir", Toast.LENGTH_SHORT).show()
					finish()
				}
			}
	}

📻 WiFi için Broadcast Receiver Tanımlama

📡 Broadcast Alıcısı Tanımlama

open class WifiP2PBroadcastReceiver(
    var manager: WifiP2pManager,
    var channel: WifiP2pManager.Channel,
    var wifiP2pActivity: WifiP2pActivity
) : BroadcastReceiver() {

    companion object {
        val TAG = this::class.java.simpleName
    }

    @RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
    override fun onReceive(context: Context, intent: Intent) {
    		when (intent.action) {
    			WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> onStateChanged(intent)
    			WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> onPeerChanged()
    			WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION ->
    				onConnectionChanged()
    			WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION ->
    				onThisDeviceChanged()
    		}
	  }

    private fun onStateChanged(intent: Intent): Unit {
        Log.d(TAG, "onStateChanged: Wifi P2P durumu değişti")
    }

    private fun onPeerChanged(): Unit {
        Log.d(TAG, "onPeerChanged: WiFi eşleri değişti")
    }

    private fun onConnectionChanged(): Unit {
        Log.d(TAG, "onConnectionChanged: WiFi P2P bağlantısı değişti")
    }

    private fun onThisDeviceChanged(): Unit {
        Log.d(TAG, "onThisDeviceChanged: Cihazın WiFi P2P durumu değişti")
    }
}

🎫 Broadcast Alıcısını Kaydetme

  • ▶️ Uygulama çalıştığında alıcının kayıt edilmesi

  • 🧹 Durdurulduğunda kaydın silinmesi gerekir

  • 🎳 Kayıt silinmezse gereksiz yere sistemi yorar ve alıcı kayıtları defalarca kaydedilebilir

class WifiP2pActivity : AppCompatActivity() {
     
     // ...
     
     /**
     * WiFi alıcısı için filtreleme
     */
    private val wifiFilter = 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)
    }
     
    /**
     * Wifi alıcısı
     */
     private lateinit var wifiReceiver: WifiDirectBroadcastReceiver
     
     // ...
     
     private fun initWifiP2p(): Unit {
        manager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
        channel = manager.initialize(this, mainLooper, null)
        
        // Yeni kısım 👇
        wifiReceiver = WifiDirectBroadcastReceiver(manager, channel, this)
        
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            getPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
        }
    }
     
    private fun registerWifiReceiver(): Unit {
        registerReceiver(wifiReceiver, wifiFilter)
    }
    
    private fun unregisterWifiReceiver(): Unit {
        unregisterReceiver(wifiReceiver)
    }
    
    override fun onResume() {
        super.onResume()
        registerWifiReceiver()
    }
    
    override fun onPause() {
        super.onPause()
        unregisterWifiReceiver()
    }
}

👷‍♂️ P2P Durum Değişikliklerini Algılama

  • 👮‍♂️ Keşfetme işlemlerine başlamadan önce WiFi durumu kontrol edilmelidir

  • ✖️ Eğer WiFi P2P aktif değilse keşif yapılamaz

activity:wifip2p.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".WifiP2pActivity">

    <TextView
        android:id="@+id/tvP2pStatus"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.053" />

</androidx.constraintlayout.widget.ConstraintLayout>
WifiP2pActivity.java
/**
 * WiFi P2P aktiflik durumu
 */
var p2pEnable: Boolean = false
		set(value) {
			field = value
			tvP2pStatus.text = value.toString()
		}
WifiP2PBroadcastReceiver.java
/**
 * Wifi P2P durum değişikliklerinde tetiklenir
 */
private fun onStateChanged(intent: Intent): Unit {
	Log.d(TAG, "onStateChanged: Wifi P2P durumu değişti")

	wifiP2pActivity.p2pEnable = when (
		intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)
		) {
		WifiP2pManager.WIFI_P2P_STATE_ENABLED -> true
		else -> false
	}
}

🔍 Eşleşebilecek Cihazları Arama

  • 🔍 Keşif işlemi manager.discover metodu ile yapılır

  • ✔️ Keşif başarılı olursa, WIFI_P2P_PEERS_CHANGED_ACTION haberi salınır

  • 🕵️‍♂️ BroadcastReceiver üzerinden haber durumunda ne yapılacağına karar verilir

  • 💁‍♂️ onPeerChanged metodu tetiklenecektir

acitivity:wifip2p.xml
<androidx.constraintlayout.widget.ConstraintLayout>

    <!-- ... -->
    
    <Button
    android:id="@+id/btnDiscover"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginTop="16dp"
    android:text="Discover"
    android:onClick="onDiscoverButtonClicked"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toBottomOf="@+id/tvP2pStatus" />

</androidx.constraintlayout.widget.ConstraintLayout>
WifiP2pActivity.java
@RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
fun onDiscoverButtonClicked(view: View) {
    Log.d(TAG, "onDiscoverButtonClicked: Butona tıklandı")

    manager.discoverPeers(channel, P2pActionListener("Keşif"))
}

class P2pActionListener(private val purpose: String) : WifiP2pManager.ActionListener {
	override fun onSuccess() {
		Log.d(TAG, "onSuccess: $purpose başarılı")
	}

	override fun onFailure(reason: Int) {
		val reasonMsg = when (reason) {
			WifiP2pManager.P2P_UNSUPPORTED -> "P2P desteklenmiyor"
			WifiP2pManager.ERROR -> "hata oluştur"
			WifiP2pManager.BUSY -> "cihaz başka bir bağlantı ile meşgul"
			else -> ""
		}

		Log.e(TAG, "onDiscoverButtonClick: $purpose başarısız, $reasonMsg")
	}
}

🧾 Eşleşilebilir Cihazları Listeleme

  • 🔍 Keşfetme (discover) işlemi başarıyla yapıldıktan sonra çalışır

  • 🙆‍♂️ Eşleşilebilir cihazların listesi requestPeers ile talep edilir

  • 💾 Talep edilen liste onPeerAvailable metodu içerisinde peerList objesine kaydedilir

WifiP2pActivity.java
/**
 * Eşleşilebilir cihazların listesi
 */
val peerList = ArrayList<WifiP2pDevice>()

// ...

/**
 * Cihazları peerList objesine kaydetme
 */
fun storePeers(peers: WifiP2pDeviceList) {
	peers.apply {
		Log.v(TAG, "onPeersAvailable: $deviceList")

		peerList.apply {
			if (this != deviceList) {
				clear()
				addAll(deviceList)
			}
		}
	}
}
WifiP2PBroadcastReceiver.java
// ...

@RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
private fun onPeerChanged(): Unit {
    Log.d(TAG, "onPeerChanged: WiFi eşleri değişti")

    manager.requestPeers(channel, wifiP2pActivity::storePeers)
}

// ...

📶 Eşlerden Birine Bağlanma

  • 🔗 Bağlanma işlemi için manager.connect metodu kullanılır

  • 👨‍💼 Bağlantı değiştiğinde WIFI_P2P_CONNECTION_CHANGED_ACTION haberi salınır

  • 💡 Bağlantı bilgisi almak için manager.requestConnectionInfo metodu ile istekte bulunur

  • 🕊️ Bilgi doğrultusunda sunucu ve istemci türüne karar verilir

WifiP2pActivity.java
@RequiresPermission(Manifest.permission.ACCESS_FINE_LOCATION)
fun connectPeer(peer: WifiP2pDevice) {
    val config = WifiP2pConfig().apply {
        deviceAddress = peer.deviceAddress
    }

    manager.connect(channel, config, P2pActionListener("Bağlantı"))
}
WifiP2PBroadcastReceiver.java
// WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> onConnectionChanged()

private fun onConnectionChanged(): Unit {
    Log.d(TAG, "onConnectionChanged: WiFi P2P bağlantısı değişti")

    manager.requestConnectionInfo(channel, wifiP2pActivity::createSocket)
}
WifiP2pActivity.java
fun createSocket(info: WifiP2pInfo) {
	  when {
			isServer(info) -> { /* createServerSocket() */ }
			isClient(info) -> { /* createClientSocket() */ }
		}
}

private fun isServer(info: WifiP2pInfo): Boolean {
		return info.run {
				groupFormed && isGroupOwner
		}
}

private fun isClient(info: WifiP2pInfo): Boolean {
		return info.run {
				groupFormed && !isGroupOwner
		}
}

🕳️ TCP Socket Oluşturma

  • 🏹 Veri aktarımı Socket üzerinden yapılmaktadır

  • 🏗️ Aktarılmadan önce Client veya Server Socket oluşturulmalıdır

  • 📶 Server Socket'in IP adresi Client'e aktarılmalıdır

📢 Socket işlemleri Thread içerisinde yapılmalıdır, UI Thread'in engellenmemesi gerekir

inner class ServerClass : Thread() {

    private lateinit var serverSocket: ServerSocket
    private lateinit var socket: Socket

    override fun run() {
        serverSocket = ServerSocket(8888)
        socket = serverSocket.accept()

        socketCreated = true

        sendReceive = SendReceive(socket)
        sendReceive.start()
        Log.i(TAG, "run: ServerClass başarılı")
    }
}
inner class ClientClass(inetAddress: InetAddress) : Thread() {

    private val socket = Socket()
    private val hostAddress: String = inetAddress.hostAddress

    override fun run() {
        try {
            socket.apply {
                bind(null)
                connect(InetSocketAddress(hostAddress, 8888), 500)
            }

            sendReceive = SendReceive(socket)
            sendReceive.start()

            Log.i(TAG, "run: ClientClass başarılı")
        } catch (e: Exception) {
            Log.e(TAG, "run", e)
        }
    }
}
val MESSAGE_READ = 1
val handler = Handler {
    when (it.what) {
        MESSAGE_READ -> {
            (it.obj as ByteArray).run {
                // tvMsg layout dosyasına sonradan tanımlanacak
                tvMsg.text = String(this, 0, it.arg1) 
            }
        }
    }
    return@Handler true
}
inner class SendReceive(private val socket: Socket) : Thread() {

    private val inputStream = socket.getInputStream()
    private val outputStream = socket.getOutputStream()

    override fun run() {
        val buffer = ByteArray(1024)
        var bytes = 0

        while (socket != null) {
            inputStream!!.read(buffer).let {
                if (it > 0) {
                    handler.obtainMessage(
                        MESSAGE_READ, it, -1, buffer
                    ).sendToTarget()
                }
            }
            Log.d(TAG, "SendReceive: Socket kontrol edildi")
        }
    }

    fun write(byteArray: ByteArray) {
        GlobalScope.launch {
            withContext(Dispatchers.IO) {
                outputStream.write(byteArray)
                Log.i(TAG, "write: Yazma başarılı")
            }
        }
    }

    fun write(fileUri: Uri) {
        
        Log.i(TAG, "SendReceive: Dosya gönderme işlemine başlandı")
    
        val cr = contentResolver
        val inputStream = cr.openInputStream(fileUri)
    
        var result: Boolean? = null
        if (inputStream != null) {
            result = SocketAPI.copyFile(inputStream, outputStream!!)
        }
    
        when (result) {
            null -> Log.d(
                    TAG,
                    "write: Sockete yazacak dosya yok ${fileUri.path}"
            )
            true -> Log.d(
                    TAG,
                    "write: Sockete yazma başarılı ${fileUri.path}"
            )
            else -> Log.e(
                    TAG,
                    "write: Sockete yazma başarısız ${fileUri.path}"
            )
        }
    }
    
}
fun onSendButtonClick(view: View) {
  Log.d(TAG, "onSendButtonClick: Send butonuna tıklandı}")

  sendReceive.write("hello ${Random.nextInt(10)}".toByteArray())
}
<androidx.constraintlayout.widget.ConstraintLayout>

    <!-- ... -->

    <Button
        android:id="@+id/btnSend"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Send"
        android:onClick="onSendButtonClick"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/tvMsg"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="17dp"
        android:text="Message"
        app:layout_constraintBottom_toTopOf="@+id/btnSend"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
fun createServerSocket() {
    ServerClass().also { it.start() }
    tvMsg.text = "Server"
}

fun createClientSocket() {
    ClientClass().also { it.start() }
    tvMsg.text = "Client"
}

🕳️ UDP Socket Oluşturma

🐞 Hata Çözümleri

🚫 Peers objesinin boş dönmesi

  • 👮‍♂️ İlk olarak ACCESS_FINE_LOCATION iznini cihaza vermelisin

  • 📍 Ardından cihazda konum hizmetini açmalısın

  • 🎉 Eşleşen cihazlar belirmeye başlayacaktır

‍🧙‍♂ Detaylı bilgi için Android Wifi Direct detects peers but Peer List is empty alanına bakabilirsin.

🔗 Faydalı Bağlantılar

Last updated

© 2024 ~ Yunus Emre Ak ~ yEmreAk