Android NSD API适配异常:setText显示null但日志能输出IP地址
问题分析与解决方案
我一眼就看出问题所在了——你界面显示null是因为列表里存储的是还没完成解析的NsdServiceInfo对象,而日志里打印的是解析完成后的对象的IP,两者不是同一个(或者说列表里的对象没被更新)。
具体原因
在onServiceFound方法里,你直接把刚发现的serviceInfo添加到了services列表,但这时候这个对象的host属性还是null(NSD发现服务时只返回基本信息,IP和端口需要通过resolveService解析后才会填充)。虽然你之后调用了解析,但解析完成后拿到的nsdServiceInfo是更新后的对象,你并没有把列表里原来那个未解析的对象替换/更新,所以Adapter刷新时,取的还是host为null的原始对象,导致界面显示异常。
修复步骤
1. 调整服务添加/更新逻辑
把添加服务到列表的操作放到onServiceResolved里,同时处理重复服务的更新:
@Override public void onServiceFound(final NsdServiceInfo serviceInfo) { Log.d("TAG", "Service discovery success : " + serviceInfo); Log.d("TAG", "Host = " + serviceInfo.getServiceName()); Log.d("TAG", "Port = " + serviceInfo.getPort()); // 先解析服务,不要直接添加未解析的对象到列表 NsdManager.ResolveListener mResolveListener2 = new NsdManager.ResolveListener() { @Override public void onResolveFailed(NsdServiceInfo nsdServiceInfo, int errorCode) { Log.e("TAG", "Resolved failed " + errorCode); Log.e("TAG", "Service = " + nsdServiceInfo); // 解析失败时可以添加原始对象,并显示提示 boolean exists = false; for (NsdServiceInfo s : services) { if (s.getServiceName().equals(nsdServiceInfo.getServiceName())) { exists = true; break; } } if (!exists) { services.add(nsdServiceInfo); runOnUiThread(() -> mAdapter.notifyDataSetChanged()); } } @Override public void onServiceResolved(NsdServiceInfo nsdServiceInfo) { Log.d("TAG", "Resolve Succeeded " + nsdServiceInfo); if (!nsdServiceInfo.getServiceType().equals(SERVICE_TYPE)) { return; } hostPort = nsdServiceInfo.getPort(); hostAddress = nsdServiceInfo.getHost(); Log.d("hello", String.valueOf(hostAddress)); // 检查列表中是否已有该服务,有则更新,无则添加 boolean serviceExists = false; for (int i = 0; i < services.size(); i++) { NsdServiceInfo existingService = services.get(i); if (existingService.getServiceName().equals(nsdServiceInfo.getServiceName())) { // 更新已存在的服务信息 services.set(i, nsdServiceInfo); serviceExists = true; break; } } if (!serviceExists) { services.add(nsdServiceInfo); } // 在UI线程刷新Adapter runOnUiThread(() -> mAdapter.notifyDataSetChanged()); } }; mNsdManager.resolveService(serviceInfo, mResolveListener2); }
2. 修复onServiceLost的判断逻辑
你原来的判断里有两个错误:用==比较字符串(应该用equals),还有端口比较写错了(currentService.getPort() == currentService.getPort()永远为true),改成下面的写法:
@Override public void onServiceLost(NsdServiceInfo nsdServiceInfo) { Log.d("TAG", "Service lost " + nsdServiceInfo); // 用迭代器安全删除元素 Iterator<NsdServiceInfo> iterator = services.iterator(); while (iterator.hasNext()) { NsdServiceInfo currentService = iterator.next(); if (currentService.getServiceName().equals(nsdServiceInfo.getServiceName()) && currentService.getPort() == nsdServiceInfo.getPort() && Objects.equals(currentService.getHost(), nsdServiceInfo.getHost())) { iterator.remove(); break; } } runOnUiThread(() -> mAdapter.notifyDataSetChanged()); Log.d("TAG", "Xd" + services); }
3. 优化Adapter的显示逻辑
在NsdServiceInfoAdapter的getView方法里,增加null判断,避免显示null,还可以同时显示服务名称:
@NonNull @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { View listItem = convertView; if (listItem == null) listItem = LayoutInflater.from(mContext).inflate(R.layout.list_item, parent, false); NsdServiceInfo currentService = services.get(position); // 显示服务名称 TextView serviceNameTv = listItem.findViewById(R.id.TextView_serviceName); serviceNameTv.setText(currentService.getServiceName()); // 显示IP地址,增加null判断 TextView ipTv = listItem.findViewById(R.id.TextView_ip); // 假设你有这个TextView if (currentService.getHost() != null) { ipTv.setText(currentService.getHost().getHostAddress()); } else { ipTv.setText("正在解析..."); } Log.d("tag", "service name" + currentService.getServiceName()); return listItem; }
验证效果
做完这些修改后,当服务解析完成,列表里的对象会被更新为带有正确IP的NsdServiceInfo,Adapter刷新后就能正常显示IP地址了,不会再出现null的情况。
内容的提问来源于stack exchange,提问作者Grace




