Android开发之无线遥控器
最近弄了一个UDP/TCP的小东西,主要需要实现的功能如下(服务器端):
1、基于局域网
2、服务器端网络接口为无线与有线
3、服务器端接收到客户端的数据需要模拟按键进行处理
4、开机自启动
5、使用UDP进行连接,TCP进行通讯
基于以上几点,我们开始分析:
1.需要获取当前的网络IP地址,这里枚举了本机所有的网络地址,只返回ipv4
1 public String getAddressIP() { 2 //检查网络是否连接 3 while (!isNetWorkConnected()) { 4 //等待网络连接 5 } 6 ip = getLocalIpAddress(); 7 return ip; 8 } 9 10 public String getLocalIpAddress() { 11 String address = null; 12 try { 13 for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { 14 NetworkInterface intf = en.nextElement(); 15 for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) { 16 InetAddress inetAddress = enumIpAddr.nextElement(); 17 if (!inetAddress.isLoopbackAddress()) {//127.0.0.1 18 address = inetAddress.getHostAddress().toString(); 19 //ipV6 20 if(!address.contains("::")){ 21 return address; 22 } 23 } 24 } 25 } 26 } catch (SocketException ex) { 27 Log.e("getIpAddress Exception", ex.toString()); 28 } 29 return null; 30 } 31 32 private boolean isNetWorkConnected() { 33 // TODO Auto-generated method stub 34 try{ 35 connectivity = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 36 if(connectivity != null){ 37 netWorkinfo = connectivity.getActiveNetworkInfo(); 38 if(netWorkinfo != null && netWorkinfo.isAvailable()){ 39 if(netWorkinfo.getState() == NetworkInfo.State.CONNECTED){ 40 isConnected = true; 41 return true; 42 } 43 } 44 } 45 }catch(Exception e){ 46 Log.e("UdpService : ",e.toString()); 47 return false; 48 } 49 return false; 50 }
2.获得IP之后,创建一个多播组
1 try { 2 3 while(ip == null){ 4 ip = getAddressIP(); 5 } 6 7 inetAddress = InetAddress.getByName(BROADCAST_IP);//多点广播地址组 8 multicastSocket = new MulticastSocket(BROADCAST_PORT);//多点广播套接字 9 multicastSocket.setTimeToLive(1); 10 multicastSocket.joinGroup(inetAddress); 11 12 } catch (UnknownHostException e) { 13 e.printStackTrace(); 14 } catch (IOException e) { 15 e.printStackTrace(); 16 }
这里设置一组特殊网络地址作为多点广播地址,第一个多点广播地址都被看作是一个组,当客户端需要发送接收广播信息时,加入该组就可以了。
IP协议为多点广播提供这批特殊的IP地址,这些IP地址范围是224.0.0.0---239.255.255.255,其中224.0.0.0为系统自用。
下面BROADCAST_IP是自己声明的一个String类型的变量,其范围也是前面所说的IP范围,比如BROADCAST_IP="224.224.224.224"。
1 private static int BROADCAST_PORT = 1234; 2 private static int PORT = 4444; 3 private static String BROADCAST_IP = "224.0.0.1";
3.服务端开始发送本机IP地址广播,如果网络断开,则结束掉此线程,并设置标识
1 public class UDPBoardcastThread extends Thread { 2 public UDPBoardcastThread() { 3 this.start(); 4 } 5 6 @Override 7 public void run() { 8 DatagramPacket dataPacket = null; 9 //将本机的IP地址放到数据包里 10 byte[] data = ip.getBytes(); 11 dataPacket = new DatagramPacket(data, data.length, inetAddress, BROADCAST_PORT); 12 //判断是否中断连接了 13 while (isNetWorkConnected()) { 14 try { 15 multicastSocket.send(dataPacket); 16 Thread.sleep(5000); 17 Log.i("UDPService:","再次发送ip地址广播"); 18 } catch (Exception e) { 19 e.printStackTrace(); 20 } 21 } 22 isConnected = false; 23 Message msg = new Message(); 24 msg.what = 0x0001; 25 mHandler01.sendMessage(msg); 26 27 } 28 }
4.新开一个线程,等待客户端连接,使用TCP进行通讯
1 new Thread() { 2 @Override 3 public void run() { 4 try { 5 //建立一个线程池,每次收到一个客户端,新开一个线程 6 mExecutorService = Executors.newCachedThreadPool(); 7 Socket client = null; 8 mList.clear(); 9 while (isConnected) { 10 11 client = server.accept(); 12 //把客户端放入客户端集合中 13 if (!connectOrNot(client)) { 14 mList.add(client); 15 Log.i("UDPService","当前连接数:"+mList.size()); 16 } 17 mExecutorService.execute(new Service(client)); 18 } 19 //释放客户端 20 for(int i = 0 ; i < mList.size() ; i++) 21 mList.get(i).close(); 22 23 } catch (IOException e) { 24 e.printStackTrace(); 25 } 26 } 27 }.start();
5.新开一个客户端的线程,处理客户端发送过来的数据等
1 //客户端线程,组成线程池 2 class Service implements Runnable { 3 private Socket socket; 4 private BufferedReader in = null; 5 private String msg = ""; 6 7 public Service(Socket socket) { 8 this.socket = socket; 9 } 10 11 @Override 12 public void run() { 13 try { 14 in = new BufferedReader(new InputStreamReader(socket.getInputStream())); 15 //等待接收客户端发送的数据 16 while (isConnected) { 17 18 if ((msg = in.readLine()) != null) { 19 20 // 创建一个Instrumentation对象,调用inst对象的按键模拟方法 21 Instrumentation inst = new Instrumentation(); 22 try{ 23 int codeKey = Integer.parseInt(msg); 24 //codeKey对应键值参照KeyCodeTable.txt文件,在客户端中实现 25 inst.sendKeyDownUpSync(codeKey); 26 27 //发送回执 28 this.sendmsg(socket); 29 }catch(Exception ex){ 30 ex.printStackTrace(); 31 } 32 33 } 34 } 35 } catch (Exception e) { 36 e.printStackTrace(); 37 } 38 } 39 40 private void sendmsg(Socket socket2) { 41 // TODO Auto-generated method stub 42 PrintWriter pout = null; 43 44 try { 45 pout = new PrintWriter(new BufferedWriter( 46 new OutputStreamWriter(socket2.getOutputStream())), true); 47 pout.println("I am ok"); 48 } catch (IOException e) { 49 // TODO Auto-generated catch block 50 e.printStackTrace(); 51 } 52 53 } 54 55 }
这里使用了Instrumentation()对象来模拟按键的处理,在实际使用中,效率还行,没有很严重的延时,若真有延时,感觉也是网络方面的。
使用了socket.getInputStream()与socket.getOutputStream()方法来进行socket数据的接收与发送
6.最后新开一个Handler对网络断开时进行处理,也可以监听系统网络变化的广播,有时间研究下service的生命周期
1 private Handler mHandler01 = new Handler(){ 2 3 @Override 4 public void handleMessage(Message msg) { 5 // TODO Auto-generated method stub 6 super.handleMessage(msg); 7 switch(msg.what){ 8 //连接失败 9 case 0x0001: 10 initData(); 11 break; 12 } 13 } 14 15 };
7.开机自启动,继承BroadcastReceiver,监听系统开机广播就ok了,记得在AndroidManifest.xml文件中声明BOOT_COMPLETED属性
1 if(intent.getAction().equals("android.intent.action.BOOT_COMPLETED")){ 2 Intent intent2 = new Intent(context, UdpService.class); 3 context.startService(intent2); 4 }
8.还有一个问题,如果我们就这样直接编译,输出apk到电视中,会出现权限不足的error,原因是apk不是系统应用,只有uid为system id才可以去模拟按键事件,所以在
AndroidManifest.xml中加上android:sharedUserId="android.uid.system",以及<uses-permission android:name="android.permission.INJECT_EVENTS" />
再编写Android.mk,最后在android源码中使用mm命令编译apk,这样就ok了。
服务器端的流程差不多是这样了,附上完整源码,包含服务器端与客户端Demo:
http://download.csdn.net/detail/u012062785/9684842
thread与runnable的区别:https://www.oschina.net/question/565065_86563
作者:pngcui
博客园:http://www.cnblogs.com/pngcui/
github:https://github.com/pngcui
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明。