Android 安卓mdns(NSD服务) 客户端基本使用-->填坑-->进阶使用

对mdns完全小白的同学,推荐一篇文章iOS开发mdns之 Bonjour的使用, 在前一篇文章本人也分享了在iOS中如何使用Bonjour,请前往iOS mdns之Bonjour 基本使用及完美解决各种坑~

此篇文章完全是从零基础的角度出发,详细地记录Android中mdns的使用过程,希望能帮助到您

好了,下面回到正题,从新建项目开始
第一步,在Android Studio新建一个空activity项目

如下图


image.png
image.png

后面就是给项目取一个名称,然后构建项目了


image.png
第二步,增加一个网络权限

在安卓中,要使用mdns,是使用NSD服务的,在使用NSD服务之前需要申请网络权限. 因为要在局域网去发现设备,用到了网络.
/项目名/app/src/main/AndroidManifest.xml中增加权限, 增加权限代码需要放在<application />标签的前面;如下图:

image.png
第三步,完整代码及运行后的结果
package com.example.androidforbonjour;

import androidx.appcompat.app.AppCompatActivity;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    private static  NsdManager nsdManager;
    private static String SERVICE_TYPE = "_x5_gw._tcp";
    private NsdManager.DiscoveryListener mDiscoveryListener;
    private NsdManager.ResolveListener mResolverListener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        nsdManager = (NsdManager) getSystemService(NSD_SERVICE);
        createDiscoverListener();
    }

    private void createDiscoverListener() {
        mResolverListener = new NsdManager.ResolveListener() {
            @Override
            public void onResolveFailed(NsdServiceInfo nsdServiceInfo, int i) {
                Toast.makeText(MainActivity.this, "onResolveFailed", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onServiceResolved(NsdServiceInfo nsdServiceInfo) {
//                mNsdServiceInfo = nsdServiceInfo;
                Toast.makeText(MainActivity.this, "onServiceResolved", Toast.LENGTH_SHORT).show();
                Log.d("xm_owon--------",nsdServiceInfo.toString());

            }
        };


        mDiscoveryListener = new NsdManager.DiscoveryListener() {
            private NsdServiceInfo mmNsdServiceInfo;

            @Override
            public void onStartDiscoveryFailed(String s, int i) {
                Toast.makeText(MainActivity.this, "onStartDiscoveryFailed", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onStopDiscoveryFailed(String s, int i) {
                Toast.makeText(MainActivity.this, "onStopDiscoveryFailed", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDiscoveryStarted(String s) {
                Toast.makeText(MainActivity.this, "onDiscoveryStarted", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onDiscoveryStopped(String s) {
                Toast.makeText(MainActivity.this, "onDiscoveryStopped", Toast.LENGTH_SHORT).show();
            }


            @Override
            public void onServiceFound(NsdServiceInfo nsdServiceInfo) {
                mmNsdServiceInfo = nsdServiceInfo;
                //这里的nsdServiceInfo只能获取到名字,ip和端口都不能获取到,要想获取到需要调用NsdManager.resolveService方法
                Toast.makeText(MainActivity.this, "onServiceFound", Toast.LENGTH_SHORT).show();


                nsdManager.resolveService(mmNsdServiceInfo, mResolverListener);
            }

            @Override
            public void onServiceLost(NsdServiceInfo nsdServiceInfo) {
                Toast.makeText(MainActivity.this, "onServiceLost", Toast.LENGTH_SHORT).show();
            }
        };


        nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);

    }
}

运行的结果如下, 一旦找到了局域网中设备的ip地址与port端口, 后面就是使用它们,建立起连接,来与设备进行通讯了.


image.png
第四步,说明一下使用过程

1)第一步,初始化NsdManager

image.png

2)第二步,在局域网中去发现服务,在mDiscoveryListener方法中,去监听已发现的服务了

 nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);

3)mDiscoveryListener中方法,有好几个,其中onServiceFound这个方法里面就是找到服务了, 后面就是根据服务去解析设备信息出来

            @Override
            public void onServiceFound(NsdServiceInfo nsdServiceInfo) {
                mmNsdServiceInfo = nsdServiceInfo;
                //这里的nsdServiceInfo只能获取到名字,ip和端口都不能获取到,要想获取到需要调用NsdManager.resolveService方法
                Toast.makeText(MainActivity.this, "onServiceFound", Toast.LENGTH_SHORT).show();
                
                nsdManager.resolveService(mmNsdServiceInfo, mResolverListener);
            }

4),第三步,就是根据已经找到的服务去解析设备信息出来,具体代码如下:

nsdManager.resolveService(mmNsdServiceInfo, mResolverListener);

5)第四步,在mResolverListener,方法里面监听设备信息,这样就到达了在局域网中发现设备了.

            @Override
            public void onServiceResolved(NsdServiceInfo nsdServiceInfo) {
//                mNsdServiceInfo = nsdServiceInfo;
                Toast.makeText(MainActivity.this, "onServiceResolved", Toast.LENGTH_SHORT).show();
                Log.d("xm_owon--------",nsdServiceInfo.toString());

            }

结尾

为什么要用mdns呢,因为如果不用mdns的话,很多公司就会用到udp来做发现设备,但在实际使用中,会有概率性的找不到设备,也就是说有时候发现不了设备,找不到设备就没有办法与设备进行通讯.

好了,今天的分享就到这里了,看到这里的小伙们,或者觉得文章对你有点帮助的话,请点赞加关注喽,您的反馈就是我们前进的动力。后续会分享更多关于移动端的干货。谢谢~~

以下是更新

填坑记录

上面的代码,勉强是可以用的,不过要是反复测试的话,可能会有下面的报错信息

java.lang.IllegalArgumentException: listener already in use at android.net.nsd.NsdManager.resolveService(NsdManager.java:613) at com.example.miguel.broadcast.LocalService$2.onServiceFound(LocalService.java:145)

对,就是上面的错误,后面在StackOverFlow上找到了答案:
解决思路,mResolverListener 这个不要用全局变量,把它变为局部变量,每次都去创建它,就不会报上面的错了。下面本人封装了一个工具类,也解决了此问题。工具类使用起来 更爽,更方便。

进阶 工具类FindBonjourTool

第一步:定义一个接口BonjourInfoCallBack,用作回调,方便Activity里使用

package com.example.androidforbonjour;

import android.net.nsd.NsdServiceInfo;

import java.util.List;

public interface BonjourInfoCallBack {

    public void msgCallback(List<NsdServiceInfo> list);

}

第二步:定义一个工具类FindBonjourTool,专门用于处理Bonjour

package com.example.androidforbonjour;

import static android.content.Context.NSD_SERVICE;

import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.content.Context;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class FindBonjourTool {
    private static NsdManager nsdManager;
    private static String SERVICE_TYPE = "_x5_cfg._tcp";
    private NsdManager.DiscoveryListener mDiscoveryListener;
    BonjourInfoCallBack bonjourInfoCallBack;
    List<NsdServiceInfo> desList = new ArrayList<>();

    FindBonjourTool(Context context, BonjourInfoCallBack bonjourInfoCallBack) {
        if (nsdManager == null) {
            nsdManager = (NsdManager) context.getSystemService(NSD_SERVICE);
        }
        this.bonjourInfoCallBack = bonjourInfoCallBack;
    }


    public void start() {
        desList.clear();
        Log.d("xm--------", "start");
        createDiscoverListener();
    }

    public void stop() {

        nsdManager.stopServiceDiscovery(mDiscoveryListener);
        mDiscoveryListener = null;
        Log.d("xm--------", "stop");
        desList.clear();

    }

    private void createDiscoverListener() {


        if (mDiscoveryListener == null) {
            mDiscoveryListener = new NsdManager.DiscoveryListener() {
                private NsdServiceInfo mmNsdServiceInfo;

                @Override
                public void onStartDiscoveryFailed(String s, int i) {
                    Log.d("xm--------", "onStartDiscoveryFailed");
                }

                @Override
                public void onStopDiscoveryFailed(String s, int i) {
                    Log.d("xm--------", "onStopDiscoveryFailed");
                }

                @Override
                public void onDiscoveryStarted(String s) {
                    Log.d("xm--------", "onDiscoveryStarted");
                }

                @Override
                public void onDiscoveryStopped(String s) {
                    Log.d("xm--------", "onDiscoveryStopped");
                }

                @Override
                public void onServiceFound(NsdServiceInfo nsdServiceInfo) {
                    mmNsdServiceInfo = nsdServiceInfo;
                    //这里的nsdServiceInfo只能获取到名字,ip和端口都不能获取到,要想获取到需要调用NsdManager.resolveService方法

                    Log.d("xm--------", "onServiceFound");
                    Log.d("xm--------", nsdServiceInfo.getServiceName());


                    nsdManager.resolveService(mmNsdServiceInfo, new NsdManager.ResolveListener() {
                        @Override
                        public void onResolveFailed(NsdServiceInfo nsdServiceInfo, int i) {
                            Log.d("xm--------", "onResolveFailed");
                        }

                        @Override
                        public void onServiceResolved(NsdServiceInfo nsdServiceInfo) {
                            Log.d("xm--------", "onServiceResolved");

                            boolean didHad = false;
                            for (int i = 0; i < desList.size(); i++) {
                                NsdServiceInfo info = desList.get(i);
                                if (info.getServiceName().equals(nsdServiceInfo.getServiceName())) {
                                    didHad = true;
                                    break;
                                }
                            }
                            if (!didHad) {
                                desList.add(nsdServiceInfo);
                            }

                            bonjourInfoCallBack.msgCallback(desList);

                        }
                    });

                }

                @Override
                public void onServiceLost(NsdServiceInfo nsdServiceInfo) {
                    Log.d("xm--------", "onServiceLost");
                }
            };
        }

        nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);

    }

}

第三步:在MainActivity中使用工具类FindBonjourTool,其中List<NsdServiceInfo> list就是mdns发现的所有设备相关信息的数组

package com.example.androidforbonjour;

import androidx.appcompat.app.AppCompatActivity;

import android.net.nsd.NsdServiceInfo;
import android.os.Bundle;
import android.util.Log;

import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {
    FindBonjourTool findBonjourTool;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findBonjourTool = new FindBonjourTool(this, new BonjourInfoCallBack() {
            @Override
            public void msgCallback(List<NsdServiceInfo> list) {
                Log.d("xm--------list", String.valueOf(list.size()));

                for (int i = 0; i < list.size(); i++) {
                    NsdServiceInfo info = list.get(i);
                    info.getServiceName();
                    info.getHost();
                    info.getPort();
                    info.getAttributes();
                    Log.d("xm--------list", info.getServiceName());

                }
            }
        });

        findBonjourTool.start();

        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                findBonjourTool.stop();
            }
        }, 20000);

    }


}

祝君好运~

最终版本 工具类FindBonjourTool

在使用的过程中,如果局域网中有多台设备,在解析服务的过程发现会,解析失败onResolveFailed
原因是因为第一个服务在解析的过程 ,这个时候第二个服务进来了,也要开始解析,导致第一个服务解析过程中断,就导致会失败。所以我们得让解析过程,不要那么快,这样就可以做到,解析第一个服务 =====》解析完成 =====》解析第二个服务 =====》解析完成,以此类推。。
工具类完整代码如下:

package com.example.androidforbonjour;

import static android.content.Context.NSD_SERVICE;

import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.content.Context;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class FindBonjourTool {
    private static NsdManager nsdManager;
    private static String SERVICE_TYPE = "_x5_gw._tcp";
    private NsdManager.DiscoveryListener mDiscoveryListener;
    BonjourInfoCallBack bonjourInfoCallBack;
    List<NsdServiceInfo> desList = new ArrayList<>();
    List<NsdServiceInfo> tempServiceList = new ArrayList<>();

    FindBonjourTool(Context context, BonjourInfoCallBack bonjourInfoCallBack) {
        if (nsdManager == null) {
            nsdManager = (NsdManager) context.getSystemService(NSD_SERVICE);
        }
        this.bonjourInfoCallBack = bonjourInfoCallBack;
    }


    public void start() {
        desList.clear();
        Log.d("xm--------", "start");
        tempServiceList.clear();
        createDiscoverListener();
    }

    public void stop() {

        nsdManager.stopServiceDiscovery(mDiscoveryListener);
        mDiscoveryListener = null;
        nsdManager = null;
        Log.d("xm--------", "stop");
        desList.clear();

    }

    private void createDiscoverListener() {


        if (mDiscoveryListener == null) {
            mDiscoveryListener = new NsdManager.DiscoveryListener() {
//                private NsdServiceInfo mmNsdServiceInfo;

                @Override
                public void onStartDiscoveryFailed(String s, int i) {
                    Log.d("xm--------", "onStartDiscoveryFailed");
                }

                @Override
                public void onStopDiscoveryFailed(String s, int i) {
                    Log.d("xm--------", "onStopDiscoveryFailed");
                }

                @Override
                public void onDiscoveryStarted(String s) {
                    Log.d("xm--------", "onDiscoveryStarted");
                }

                @Override
                public void onDiscoveryStopped(String s) {
                    Log.d("xm--------", "onDiscoveryStopped");
                }

                @Override
                public void onServiceFound(NsdServiceInfo nsdServiceInfo) {
//                    mmNsdServiceInfo = nsdServiceInfo;
                    //这里的nsdServiceInfo只能获取到名字,ip和端口都不能获取到,要想获取到需要调用NsdManager.resolveService方法

                    Log.d("xm--------", "onServiceFound");
                    Log.d("xm--------", nsdServiceInfo.getServiceName());
                    boolean didHad = false;
                    for (int i = 0; i < tempServiceList.size(); i++) {
                        NsdServiceInfo currentInfo = tempServiceList.get(i);
                        if (nsdServiceInfo.getServiceName().equals(currentInfo.getServiceName())) {
                            didHad = true;
                            break;
                        }
                    }
                    if (!didHad) {
                        tempServiceList.add(nsdServiceInfo);
                    }

                    for (NsdServiceInfo info : tempServiceList) {
                        nsdManager.resolveService(info, new NsdManager.ResolveListener() {
                            @Override
                            public void onResolveFailed(NsdServiceInfo nsdServiceInfo, int i) {
                                Log.d("xm--------", "onResolveFailed");
                            }

                            @Override
                            public void onServiceResolved(NsdServiceInfo nsdServiceInfo) {
                                Log.d("xm--------", "onServiceResolved");

                                boolean didHad = false;
                                for (int i = 0; i < desList.size(); i++) {
                                    NsdServiceInfo info = desList.get(i);
                                    if (info.getServiceName().equals(nsdServiceInfo.getServiceName())) {
                                        didHad = true;
                                        break;
                                    }
                                }
                                if (!didHad) {
                                    desList.add(nsdServiceInfo);
                                }

                                bonjourInfoCallBack.msgCallback(desList);

                            }
                        });
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                    }

                }

                @Override
                public void onServiceLost(NsdServiceInfo nsdServiceInfo) {
                    Log.d("xm--------", "onServiceLost");
                }
            };
        }

        nsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);

    }

}


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

推荐阅读更多精彩内容