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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM