近期做了一个Yodar的音乐播放器,其实就是在Android上做了一个Yodar的遥控器,用了播放器的样式与Yodar的设备屏幕同步。而比较麻烦得是Yodar的协议是UDP的。并且涉及到发json数据,不能有丝毫错误,必须按规则来,否则不会返回任何信息。接下来,笔者就简单描述下制作过程中碰到的一些问题。还有,音乐播放器的源码属于公司产品项目,所以笔者上传的源码是测试写的API源码,当然与实际项目比较,也就差了一个界面和交互而已,核心的都在。
public class UDPUtil {
private static UDPUtil udpUtil;
DatagramSocket ds;
private final int SENDPORT=10061;
private final int REVCIVEPORT=10062;
private final int SOCKETTIMEOUT=1000;
private final String IP="255.255.255.255";
private OnResolveDataListener onResolveDataListener;
private UDPUtil() throws IOException {
init();
startUDPService();
}
private void init() throws IOException {
ds= new DatagramSocket(REVCIVEPORT);
ds.setBroadcast(true);
// ds.setSoTimeout(SOCKETTIMEOUT);//设置超时时间
}
public synchronized static UDPUtil getInstance() throws IOException {
if (udpUtil==null) udpUtil=new UDPUtil();
return udpUtil;
}
public void sendUDP(final byte[] sendBuf){
new Thread(new Runnable() {
@Override
public void run() {
try {
DatagramPacket sendDp = new DatagramPacket(sendBuf, sendBuf.length, InetAddress.getByName(IP), SENDPORT);
ds.send(sendDp);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
public void startUDPService(){
if(ds==null)return;
Log.i("wsy","UDP Service star");
new Thread(new Runnable() {
@Override
public void run() {
byte[] receiveBuf = new byte[1024];
DatagramPacket receiveDp = new DatagramPacket(receiveBuf, receiveBuf.length);
while (true){
try {
ds.receive(receiveDp); // 该方法为阻塞式的方法
String ip = receiveDp.getAddress().getHostAddress();
int port = receiveDp.getPort();
byte[] text = receiveDp.getData();
// for (int i=0;i<receiveDp.getLength();i++) {
// Log.d("wsy","接收到第"+i+"byte的值="+ text[i]);
// }
Log.d("wsy", ip + ":" + port + ":" + byteToString(text, 0, receiveDp.getLength()));
if(onResolveDataListener!=null) onResolveDataListener.resolveData(text,receiveDp.getLength());
} catch (IOException e) {
e.printStackTrace();
Log.e("wsy","UDP Service error end");
}
}
}
}).start();
}
/**
* 将Byte转为String(按照ASCLL码表将10进制的byte值转成字符)
* @param b
* @param start
* @param len
* @return
*/
public String byteToString(byte[] b,int start,int len){
try {
byte[] bytes=new byte[len];
for (int i=start;i<start+len;i++){
bytes[(i-start)]=b[i];
}
return new String(bytes,"UTF-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return "转化出错";
}
public interface OnResolveDataListener{
void resolveData(byte[] bytes,int len);
}
public void setOnResolveDataListener(OnResolveDataListener onResolveDataListener) {
this.onResolveDataListener = onResolveDataListener;
}
}
这方面知识点文章很多,直接上代码,笔者做了一个单例类来管理这个UDPUtil,实现了UDP的发送和接收,以及byte和String的转化方法。默认广播IP“255.255.255.255”,发送端口10061(YodarUDP协议监听的这个端口),接收端口10062(不是10061就好,否则会接收到自己发的UDP包)
首先做这一步,得阅读Yodar的协议文档。发送则按照要求构建btye[]数据。下面是获取主机信息的例子
public void getHostInfo() {
byte[] sendBuf = new byte[3];
sendBuf[0] = (byte) 0xce;
sendBuf[1] = 0x00;
sendBuf[2] = (byte) (0xce ^ 0x00);
udpUtil.sendUDP(sendBuf);
}
接收的话,笔者通过协议的信息对不同的数据做了判断,截取获取到的JSON数据,构建对应的Bean类,然后通过广播发送给监听了对应Action的使用者。 这么做的目的是,UDP获取数据的时间不是我们控制的,也不是一定是发一条就接收一条,并且在Yodar切换歌曲等的时候,发主动发送UDP包给我们,我们需要一直去接收,自己判断属于什么数据,然后分发。
public void resolveData(byte[] bytes,int len) {
Map<String, Object> maps=new HashMap<>();
Intent intent=new Intent();
intent.setAction(BROADCASTMESSAGE);
switch (bytes[0]){
case (byte) 0xef://主机信息返回
maps.put("Command",bytes[0]);
maps.put("Address",bytes[5]);
maps.put("Model",bytes[4]);//通道数
maps.put("iphoneId",bytes[5]);//编号
maps.put("NameId",bytes[6]);
maps.put("Name",udpUtil.byteToString(bytes,8,bytes[7]));
hostInfo=maps;
intent.putExtra("bean",gson.fromJson(gson.toJson(maps), HostInfoBean.class));
break;
case (byte)0x0f:
String josnStr=udpUtil.byteToString(bytes,4,len-5);
JSONObject jsonObject=null;
String ack="";
String notify="";
JSONObject arg=null;
try {
jsonObject=new JSONObject(josnStr);
try{
ack=jsonObject.getString("ack");
}catch (JSONException e){ }
try{
notify=jsonObject.getString("notify");
}catch (JSONException e){ }
try{
arg=jsonObject.getJSONObject("arg");
}catch (JSONException e){ }
if(!ack.equals("")){
switch (ack){
case "player.info"://歌曲信息---请求的
processMusicInfo(arg.toString(),intent);
break;
case "list.dirNodeList":
TableBean tableBean=new Gson().fromJson(josnStr, TableBean.class);
intent.putExtra("bean",tableBean);
break;
}
}
if(!notify.equals("")){
switch (notify){
case "player.info"://歌曲信息---通知的
processMusicInfo(arg.toString(),intent);
break;
case "player.time":
TimeNotifyBean timeNotifyBean=new Gson().fromJson(arg.toString(), TimeNotifyBean.class);
intent.putExtra("bean",timeNotifyBean);
break;
case "player.state":
StateNotifyBean stateNotifyBean=new Gson().fromJson(arg.toString(), StateNotifyBean.class);
intent.putExtra("bean",stateNotifyBean);
break;
case "player.mode":
ModeNotifyBean modeNotifyBean=new Gson().fromJson(arg.toString(), ModeNotifyBean.class);
intent.putExtra("bean",modeNotifyBean);
break;
case "player.volume":
VolumeNotifyBean volumeNotifyBean=new Gson().fromJson(arg.toString(), VolumeNotifyBean.class);
intent.putExtra("bean",volumeNotifyBean);
break;
}
}
} catch (Exception e) {
e.printStackTrace();
return;
}
break;
}
mC.sendBroadcast(intent);
}
笔者做Android版本的Yodar遥控器时碰到了这个问题,据IOS说没有这个问题。笔者只好20S间隔一次,去给Yodar发送一个获取主机信息的UDP包,就不会出现Yodar不响应的问题。感觉就像发心跳包,需要一直保持联系。笔者也没搞明白为什么,这UDP协议按道理是不需要这样的牵手的。
Yodar遥控器测试项目源码 YodarUDP协议在源码根目录中!!!