Android WI-FI Direct Kotlin 淺析(一)

原文地址:https://blog.qjm253.cn/?p=564

簡介

  • 百度百科:2010年10月,Wi-Fi Alliance(wi-fi聯盟)發佈Wi-Fi Direct白皮書,白皮書中介紹了有關於這種技術的基本信息、這種技術的特點和這種技術的功能,Wi-Fi Direct標準是指允許無線網絡中的設備無需通過無線路由器即可相互連接。與藍牙技術類似,這種標準允許無線設備以點對點形式互連,而且在傳輸速度與傳輸距離方面則比藍牙有大幅提升

  • 與Android開發者而言呢,Google嗅到了WI-FI直連的發展前景,整了一套WI-FI直連的API,在Android4.0+(API >= 14)的設備上均能使用。這給我們進行手機之間的互聯操作提供了一個新的實現思路。它可以不需要路由而讓兩個設備直接相連,但其傳輸速度可是遠遠超過藍牙傳輸。而且它不需要熱點的建立,甚至兩個設備不需要在一個局域網下。想象一下,不用建立熱點也可以實現像快牙,茄子一類的面對面快傳的需求~》《~

基本使用

  • 定義一個廣播接收器

    • 每當當前設備與Wi-fi直連相關的狀態發生改變,系統便會以廣播的形式通知我們,所以讓我們定義一個Receiver歡迎它
    • 在下面定義的Receiver裏面我們監聽了4個Action,這是最常用的4個,還有其它的Action可以看官方文檔,或者,來去擼一波源碼
    class WifiDirectReceiver : BroadcastReceiver() {
    
      /**
       * 寫一個便捷的註冊方法,動態註冊的時候就不用寫intentFilter了
       */
      fun registerReceiver(context: Context) {
          val intentFilter = IntentFilter()
          intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)
          intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
          intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
          intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
          context.registerReceiver(this, intentFilter)
      }
    
      override fun onReceive(context: Context?, intent: Intent?) {
          when (intent?.action) {
          /**
           *當Wifi功能打開或關閉的時候系統會發送 WIFI_P2P_STATE_CHANGED_ACTION 廣播
           * Tip: 並不是指是否已經成功連上WI-FI
           */
              WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
              }
          /**
           * 當前設備的詳細信息發生變化的時候,系統會發送 WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 廣播
           */
              WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {
              }
    
          /**
           * 當可連接的對等節點列表發生改變的時候,系統會發送 WIFI_P2P_PEERS_CHANGED_ACTION 廣播
           * invoke when the list of peers find, register, lost
           */
              WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
              }
          /**
           * 當一個連接建立或斷開的時候,系統會發送該廣播
           * This action received when the connection setup or dis-setup
           */
              WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
              }
          }
      }
    }
  • 接下來在Activity中動態註冊

    class WifiDirectActivity : AppCompatActivity() {
    
      private val wifiDirectReceiver = WifiDirectReceiver()
    
      override fun onStart() {
          super.onStart()
          wifiDirectReceiver.registerReceiver(this)
      }
    
      override fun onStop() {
          super.onStop()
          unregisterReceiver(wifiDirectReceiver)
      }
    
      override fun onCreate(savedInstanceState: Bundle?) {
          super.onCreate(savedInstanceState)
          setContentView(R.layout.activity_main)
      }
    }
    • 沒錯,直接動態註冊了!!!Android8.0的新特性讓Android開發喵幾乎一夜之間喪失在AndroidManifest中靜態註冊的技能(所以爲了,考慮兼容,我們就直接動態註冊了)
  • 好,接下來驗證一下WIFI_P2P_STATE_CHANGED_ACTION

    • 我們在Receiver裏面加點輸出(實際開發還是用Log,極不推薦直接輸出,這裏筆者還是決定偷懶♪(^∇^*))
    /**
     *當Wifi功能打開或關閉的時候系統會發送 WIFI_P2P_STATE_CHANGED_ACTION 廣播
     * Tip: 並不是指是否已經成功連上WI-FI
     */
    WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
        //get the state of current device
        val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
                WifiP2pManager.WIFI_P2P_STATE_DISABLED)
        if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) {
            //Wifi p2p enable
            println("wifi enable")
        } else {
            //wifi p2p disEnable
            println("wifi disEnable")
        }
    }
    • 然後跑到真機上(萬能的模擬器然而並不能模擬wifi功能,只能真機實測一波啦),反覆開關Wifi功能

      03-26 16:26:08.744 29313-29313/com.j.ming.eupanwifidirect I/System.out: wifi enable
      03-26 16:26:11.098 29313-29313/com.j.ming.eupanwifidirect I/System.out: wifi disEnable
      03-26 16:32:16.376 29313-29313/com.j.ming.eupanwifidirect I/System.out: wifi enable
    • OK, 得到上面的輸出,我們就知道確實,這個ACTION在WI-FI功能開關的時候會觸發

  • WIFI_P2P_THIS_DEVICE_CHANGED_ACTION

    • 大新聞:這個ACTION攜帶了一團神祕數據,^“^,那就是本設備的設備信息啦~
    • 同樣的,我們加一點輸出,看一下都包含哪些信息(當然,最直接的是看WifiP2pDevice這個數據)
    /**
     * 當前設備的詳細信息發生變化的時候,系統會發送 WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 廣播
     */
    WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {
        val device: WifiP2pDevice = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE)
        println(device)
    }
    • 同樣真機測試,來調皮的開關一下WI-FI功能(當然,一開始也會打印出設備信息,那就是在這個Receiver被註冊的時候)

      03-26 16:43:29.420 30294-30294/com.j.ming.eupanwifidirect I/System.out: wifi enable
      03-26 16:43:29.436 30294-30294/com.j.ming.eupanwifidirect I/System.out: Device: 魅藍 Note3
      03-26 16:43:29.436 30294-30294/com.j.ming.eupanwifidirect I/System.out:  deviceAddress: 2e:57:31:98:45:35
      03-26 16:43:29.436 30294-30294/com.j.ming.eupanwifidirect I/System.out:  primary type: 10-0050F204-5
      03-26 16:43:29.436 30294-30294/com.j.ming.eupanwifidirect I/System.out:  secondary type: null
      03-26 16:43:29.436 30294-30294/com.j.ming.eupanwifidirect I/System.out:  wps: 0
      03-26 16:43:29.436 30294-30294/com.j.ming.eupanwifidirect I/System.out:  grpcapab: 0
      03-26 16:43:29.436 30294-30294/com.j.ming.eupanwifidirect I/System.out:  devcapab: 0
      03-26 16:43:29.436 30294-30294/com.j.ming.eupanwifidirect I/System.out:  status: 3
      03-26 16:43:29.436 30294-30294/com.j.ming.eupanwifidirect I/System.out:  wfdInfo: WFD enabled: falseWFD DeviceInfo: 0
      03-26 16:43:29.436 30294-30294/com.j.ming.eupanwifidirect I/System.out:  WFD CtrlPort: 0
      03-26 16:43:29.436 30294-30294/com.j.ming.eupanwifidirect I/System.out:  WFD MaxThroughput: 0
    • 可以看到包括設備的名字,本設備的MAC地址,狀態balabala

  • ### 後面兩種ACTION涉及到連接,我們等下討論

WifiP2pManager 的異種接口

  • requestPeers、requestGroupInfo、discoverPeers、createGroup、removeGroup等等
  • 當然可以直接用,不過爲了簡介,我們一邊用一遍封裝出一個管理類
  • 獲得WifiP2pManager對象

    • 下面的操作需要添加以下權限
      <uses-permission
          android:name="android.permission.ACCESS_WIFI_STATE"
          android:required="true" />
      <uses-permission
          android:name="android.permission.CHANGE_WIFI_STATE"
          android:required="true" />
    object WifiDirectManager{
      private var manager: WifiP2pManager by Delegates.notNull()
      private var channel: WifiP2pManager.Channel by Delegates.notNull()
    
      fun init(context: Context): WifiDirectManager{
          //通過獲取系統服務的方式獲得Manager對象
          manager = context.getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager
          channel = manager.initialize(context, Looper.getMainLooper()){
              //初始化操作成功的回調
          }
          return this
      }
    }
    • 利用Kotlin 的object關鍵字,快速創建一個單例對象
    • channel在後續的操作中要用到,這個是manager初始化成功之後返回的渠道對象
    • 上述init方法建議在Application的onCreate中調用,至少在使用前調用
  • discoverPeers

    • 在上述的WifiDirectManager類裏面添加如下方法

      /**
       * discover available peer list
       */
      fun discoverPeers(): WifiDirectManager {
          manager.discoverPeers(channel, object : WifiP2pManager.ActionListener{
              override fun onSuccess() {
                  println("discover Peers success")
              }
      
              override fun onFailure(reason: Int) {
                  println("discover Peers fail: $reason")
              }
          })
          return this
      }
      
      /**
       * request the peer list available
       */
      fun requestPeers(): WifiDirectManager {
          manager.requestPeers(channel) { peers ->
              //請求對等節點列表操作成功
              println(peers)
          }
          return this
      }
    • 在Activity裏面拖一個button,在點擊事件裏面調用這個函數

      btnSendTestBroadcast.setOnClickListener {
          WifiDirectManager.discoverPeers()
      }
    • 上面的方法,望文生義,就是用來檢索附近的可用對等節點列表,然後後面就是這個整套API最坑的一個回調了

    • WifiP2pManager.ActionListener 這個回調裏面的success和failure**表示的是一個操作是否執行成功,而並不能反映出這個操作的結果**
    • 所以從上面這個回調裏我們只能知道,執行discoverPeers這個操作有沒有成功,是無法獲取到對等節點列表的
    • 那怎麼獲取檢索的結果呢,答案是:
    • 在Receiver裏面直接獲取(API >= 18)

    - 在Receiver裏面調用requestPeers()獲取

  • 兩種獲取對等節點列表的方式

    Tip:在調用discoverPeers之後才能獲取到對等節點列表

    /**
    * 當可連接的對等節點列表發生改變的時候,系統會發送 WIFI_P2P_PEERS_CHANGED_ACTION 廣播
    * invoke when the list of peers find, register, lost
    */
    WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
      //api > 18 have this extra info,
      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
          val wifiP2pList: WifiP2pDeviceList = intent.getParcelableExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST)
          println(wifiP2pList)
      } else { //if the sdk version lower than 18
          //get WifiP2pDeviceList by call WifiP2pManager.requestPeers to get
          WifiDirectManager.requestPeers()
      }
    }
    • 可以看到,當可用對等節點列表變化時,系統會發送 WIFI_P2P_PEERS_CHANGED_ACTION 廣播,我們有兩種方式獲取對等節點列表
    • API >= 18 的時候可以直接在 intent 攜帶數據中獲取到 WifiP2pDeviceList 對象
    • 當 API 在[14, 18) 之間的時候,只能調用manager的requestPeers方法,在回調當中獲取
    • 其實:只要都採用第二種方式,就能保持一致性了
    • 具體輸出結果可以自己試一試
  • connect

    /**
    * connect by MAC address(hardware address)
    */
    fun connect(deviceAddress: String) {
      val config = WifiP2pConfig()
      config.deviceAddress = deviceAddress
      config.wps.setup = WpsInfo.PBC
      manager.connect(channel, config, object : WifiP2pManager.ActionListener {
          override fun onSuccess() {
              println("connect operator success")
          }
    
          override fun onFailure(reason: Int) {
              println("connect operator fail: $reason")
          }
      })
    }
    
    /**
    * invoke this method to connect a p2p device
    */
    fun connect(device: WifiP2pDevice): WifiDirectManager {
      connect(device.deviceAddress)
      return this
    }
    • 在前面我們已經獲取到對等節點列表了,每個對等節點設備信息裏面包含其mac地址,用mac地址就能連接
    • 連接成功以後 系統就會發送 WIFI_P2P_CONNECTION_CHANGED_ACTION 廣播了,你可在裏面獲取對方設備的信息以及羣組的信息
    • 如果兩個對等節點都沒有創建羣組,則連接之後其中一端設備會自動成爲羣組,每個組員都能獲取到羣組的IP
  • WIFI_P2P_CONNECTION_CHANGED_ACTION 包含的信息

    • 給WifiDirectManager類添加幾個封裝函數

      
      /**
       * request the group info
       */
      fun requestGroup(): WifiDirectManager {
          manager.requestGroupInfo(channel) { group ->
          }
          return this
      }
      
      /**
       * create a group
       */
      fun createGroup(): WifiDirectManager {
          manager.createGroup(channel, object : WifiP2pManager.ActionListener {
              override fun onFailure(reason: Int) {
                  println("create group fail: $reason")
      
              }
      
              override fun onSuccess() {
                  println("create group success")
              }
          })
          return this
      }
      
      /**
       * remove a group
       */
      fun removeGroup(): WifiDirectManager {
          manager.removeGroup(channel, object : WifiP2pManager.ActionListener {
              override fun onSuccess() {
                  println("remove group success")
              }
      
              override fun onFailure(reason: Int) {
                  println("remove group success: $reason")
              }
      
          })
          return this
      }
      
      
    • 看看Receiver裏面的代碼

      /**
       * 當一個連接建立或斷開的時候,系統會發送該廣播
       * This action received when the connection setup or dis-setup
       */
          WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
              val networkInfo = intent.getParcelableExtra<NetworkInfo>(WifiP2pManager.EXTRA_NETWORK_INFO)
              val wifiP2pInfo = intent.getParcelableExtra<WifiP2pInfo>(WifiP2pManager.EXTRA_WIFI_P2P_INFO)
      
              if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                  val wifiP2pGroupInfo =  intent.getParcelableExtra<WifiP2pGroup>(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)
              } else {
      
              }
          }
      }
    • 從上面可以看出,當收到系統發送的 WIFI_P2P_CONNECTION_CHANGED_ACTION 廣播的時候,我們可以獲取下面信息

    • NetworkInfo
    • WifiP2pInfo (對端設備的信息)
    • WifiP2pGroup (兩種方式獲取, 和之前獲取對等節點列表的操作類似)
  • 應用方式

    • 可以一個設備主動創建一個Group,然後其它設備檢索後連接
    • 一旦連接成功,這些設備就處於同一自組網下了,組員是可以直接在WifiP2pGroup裏面獲取到羣主的ip。接下來就可以直接用Socket進行各種騷操作了
    • 至於羣主如何獲取組員的IP,目前想到的一種方式是羣主開一個Socket監聽,組員連接成功通過socket向羣主發送一個請求,然後羣主就可以在請求的socket對象裏面獲取組員的IP了
發佈了54 篇原創文章 · 獲贊 24 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章