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了
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容