安卓下的TCP通信socket編程


一、安卓下的Socket基本實現原理

    服務端:首先聲明一個ServerSocket對象並指定端口號,然后調用ServerSocket的accept( )方法接收客戶端的數據。accept()方法在沒有客戶端請求連接之前處於阻塞狀態,一旦接收到連接請求,則通過輸入流讀取接收的數據。代碼實例如下

 

 1 import java.io.DataInputStream;
 2 import java.net.*;
 3 public class TCPServer {
 4 
 5     public static void main(String[] args) throws Exception{
 6         ServerSocket ss = new ServerSocket(8000);
 7         //不止接受一個客戶端
 8         while (true) {
 9         Socket s = ss.accept();//接受一個連接
10         DataInputStream dis = new DataInputStream(s.getInputStream());//輸入管道
11         System.out.println(dis.readUTF());
12         dis.close();
13         s.close();
14         
15         }
16     }
17 
18 }

     客戶端:創建一個Socket對象,指定服務器端的ip地址和端口號,申請連接。通過輸入流InPutStream讀取服務端的數據,通過輸出流OutPutStream向服務端寫數據

 

 1 import java.io.DataOutputStream;
 2 import java.io.IOException;
 3 import java.io.OutputStream;
 4 import java.net.*;
 5 public class TCPClient {
 6 
 7     public static void main(String[] args) throws Exception {
 8         Socket s = new Socket("192.168.1.100", 8000);//申請鏈接
 9         OutputStream os = s.getOutputStream();
10         DataOutputStream dos = new DataOutputStream(os);
11         dos.writeUTF("hello server!");
12         dos.flush(); 
13         dos.close();
14         s.close();
15     }
16 }

二、安卓下實現socket通信
       注意:一定要添加網絡訪問權限,一開始沒有添加一直報異常,異常信息為:SocketException:socket failed:EACCES(Permission denied)

      在manifest.xml文件中添加<uses-permission android:name="android.permission.INTERNET" />

     我在最剛開始寫這個安卓APP的時候沒有加這個網絡權限,然后報這個異常,在網上知道是這個原因之后加了網絡權限,還是報異常,這讓我十分苦惱,還嘗試了降低安卓運行版本的辦法,試圖降到4.0以下的版本,發現不能實現。后來意外發現在我的手機上運行安卓程序時出現的是這樣的情況

而運行其他安卓程序時是這樣的

於是我發現問題的所在了,還是因為我的手機APP沒有訪問網絡的權限,可是我明明加了呀,無奈之下我只好嘗試將這個APP卸載掉,再重新安裝試試,一試還真可以了,簡直是一個大大的驚喜!解決這個問題之后,運行的時候APP沒有在崩掉,但是並沒有連上開發板通信,打log程序運行到Socket socket = new Socket("192.168.1.100",9500)就不往下運行了,這個問題又困擾到我了,查看日志信息發現

原來在安卓里面,涉及到網絡連接等耗時操作時,不能將其放在UI主線程中,需要添加子線程,在子線程進行網絡連接,這就涉及到安卓線程間的通信了,用Handle來實現。

三、Handler
         handle的定義: 主要接受子線程發送的數據, 並用此數據配合主線程更新UI.
          解釋: 當應用程序啟動時,Android首先會開啟一個主線程 (也就是UI線程) , 主線程為管理界面中的UI控件,進行事件分發, 比如說, 你要是點擊一個 Button, Android會分發事件到Button上,來響應你的操作。  如果此時需要一個耗時的操作,例如: 聯網讀取數據,或者讀取本地較大的一個文件的時候,你不能把這些操作放在主線程中,如果你放在主線程中的話,界面會出現假死現象, 如果5秒鍾還沒有完成的話,會收到Android系統的一個錯誤提示  "強制關閉".  這個時候我們需要把這些耗時的操作,放在一個子線程中,更新UI只能在主線程中更新,子線程中操作是危險的. 這個時候,Handler就出現了來解決這個復雜的問題,由於Handler運行在主線程中(UI線程中),它與子線程可以通過Message對象來傳遞數據,這個時候,Handler就承擔着接受子線程傳過來的(子線程用sedMessage()方法傳弟)Message對象,里面包含數據, 把這些消息放入主線程隊列中,配合主線程進行更新UI。

下面為安卓客戶端的MainActivity.java的代碼

  1 package com.tanxiaoyi.newApp;
  2 
  3 import java.io.BufferedReader;
  4 import java.io.IOException;
  5 import java.io.InputStream;
  6 import java.io.InputStreamReader;
  7 import java.io.OutputStream;
  8 import java.io.PrintStream;
  9 import java.io.PrintWriter;
 10 import java.math.MathContext;
 11 import java.net.Socket;
 12 import java.net.UnknownHostException;
 13 import java.util.Map;
 14 
 15 import com.tanxiaoyi.save.UserInfoUtil;
 16 import android.annotation.SuppressLint;
 17 import android.app.Activity;
 18 import android.content.Context;
 19 import android.content.Intent;
 20 import android.content.SharedPreferences;
 21 import android.content.SharedPreferences.Editor;
 22 import android.os.Bundle;
 23 import android.os.Handler;
 24 import android.os.Message;
 25 import android.os.StrictMode;
 26 import android.text.TextUtils;
 27 import android.util.Log;
 28 import android.view.Menu;
 29 import android.view.MenuItem;
 30 import android.view.View;
 31 import android.view.View.OnClickListener;
 32 import android.widget.Button;
 33 import android.widget.CheckBox;
 34 import android.widget.EditText;
 35 import android.widget.Toast;
 36 
 37 
 38 public class MainActivity extends Activity {
 39     protected static final String TAG = "TXY";
 40     //定義服務端的ip地址和端口號
 41     private final String SERVER_HOST_IP = "192.168.1.100";
 42     private final int SERVER_HOST_PORT = 9500;
 43     private Socket socket;
 44     private Thread thread = null; //聲明線程
 45     private OutputStream output;
 46     private InputStream In;
 47     
 48     private Button btn_load; // 聲明Button類型的對象
 49     private EditText et_secretKey;
 50     private Context mContext ;
 51     private CheckBox cb_rem ;
 52     private String secretKey = null;
 53     private String receive = null;
 54     public void toastText(String message)
 55       {
 56         Toast.makeText(this, message, Toast.LENGTH_LONG).show();
 57       }
 58 
 59       public void handleException(Exception e, String prefix)
 60       {
 61         e.printStackTrace();
 62         toastText(prefix + e.toString());
 63       }
 64       
 65     @Override
 66       public void onCreate(Bundle savedInstanceState) {
 67 
 68         super.onCreate(savedInstanceState);
 69         setContentView(R.layout.activity_main);
 70         mContext = this ;//this為MainActivity
 71         btn_load = (Button) findViewById(R.id.btn_load);
 72         cb_rem = (CheckBox) findViewById(R.id.cb_rem);
 73         et_secretKey = (EditText) findViewById(R.id.et_secretKey); 
 74         
 75         //連接服務器
 76         initClientSocket();
 77         
 78         //回顯秘鑰
 79         String parent_data = getSecreKey(mContext);
 80         et_secretKey.setText(parent_data);
 81         cb_rem.setChecked(true);
 82         
 83         
 84         btn_load.setOnClickListener(new OnClickListener() {
 85             
 86             @Override
 87             public void onClick(View v) {
 88                 login();
 89                 
 90             }
 91         });    
 92     }
 93     //登錄
 94     private void login ()  {
 95         
 96         //得到秘鑰
 97          secretKey = et_secretKey.getText().toString();
 98         //發送秘鑰
 99         sendSecretKey(secretKey);
100         Boolean isrem = cb_rem.isChecked();//默認記住秘鑰
101         if(TextUtils.isEmpty(secretKey)) {
102             Toast.makeText(mContext, "秘鑰不能為空!", Toast.LENGTH_SHORT).show();
103             return;
104         }
105         
106         //判斷是否記住秘鑰,如果記住將秘鑰保存到本地
107         if(isrem) {
108             Boolean result = saveSecreKey(mContext,secretKey);
109             if(result){
110             Toast.makeText(mContext, "秘鑰保存成功!", Toast.LENGTH_SHORT).show();
111             }
112             else {
113                 Toast.makeText(mContext, "秘鑰保存失敗!", Toast.LENGTH_SHORT).show();
114             }
115         }else {
116             //Toast.makeText(mContext, "無需保存!", Toast.LENGTH_SHORT).show();
117         }
118         
119         
120     }
121     
122     //保存秘鑰到本地
123     private Boolean saveSecreKey(Context context, String secretKey) {
124         try {
125             SharedPreferences sharedPreferences = context.getSharedPreferences("secretKeyInfo.txt", context.MODE_PRIVATE);
126             // 2.通過SharedPreference對象得到一個Editor對象
127             Editor editor = sharedPreferences.edit();
128             editor.putString("secretKey", secretKey);
129             editor.commit();
130             return true;
131             }catch(Exception e) {
132                 e.printStackTrace();
133         }
134         return false ;
135     }
136     
137     private String getSecreKey(Context context) {
138         SharedPreferences sharedPreferences = context.getSharedPreferences("secretKeyInfo.txt", context.MODE_PRIVATE);
139         // 2.通過SharedPreference對象獲取存放的數據
140         String data = sharedPreferences.getString("secretKey", "");
141         return data ;
142     }
143     
144     //處理線程通信
145     Handler handler = new Handler(){
146         public void handleMessage(android.os.Message msg) {
147             switch (msg.what) {
148             case 1:
149             IsAndOs stream = (IsAndOs)msg.obj;
150             output = stream.getOs();
151             In = stream.getIs();
152             //等輸入流的線程處理完之后再開這個線程
153             thread = new Thread(receivedDataThread);
154             thread.start();// 啟動接收線程
155             
156                 break;
157             case 2:
158                 receive= (String)msg.obj;
159                 if(!(receive ==null)) {
160                     Toast.makeText(mContext, "連接成功!", Toast.LENGTH_SHORT).show();
161                     Intent intent = new Intent(MainActivity.this,MainPageActivity.class);
162                     startActivity(intent);
163                 }
164                 break;
165             default:
166                 break;
167             }
168         };
169     };
170     
171     private void initClientSocket()
172       {
173          //開啟子線程
174           new Thread(new Runnable() {
175             
176             @Override
177             public void run() {
178                 try {
179                     socket = new Socket(SERVER_HOST_IP, SERVER_HOST_PORT);
180                      /* 獲取輸出,輸入流 */
181                       PrintStream op = new PrintStream(socket.getOutputStream(), true, "utf-8");
182                       InputStream In =socket.getInputStream();
183                       
184                       IsAndOs io = new IsAndOs();
185                       io.setIs(In);
186                       io.setOs(op);
187                       //傳到主線程的信息
188                       Message msg = handler.obtainMessage();
189                       //將輸入輸出流的類發送給主線程
190                       msg.obj = io;
191                       msg.what = 1;
192                       //傳遞消息到主線程
193                       handler.sendMessage(msg);
194                 } catch (UnknownHostException e) {
195                       handleException(e, "unknown host exception: " + e.toString());
196                 } catch (IOException e) {
197                     handleException(e, "io exception: " + e.toString());
198                 }
199             }
200         }).start();
201       }
202     
203     // 接收線程
204         private Runnable receivedDataThread = new Runnable()
205         {
206             @Override
207             public void run()
208             {
209                 
210                 try{
211                         byte buffer[] = new byte[1024];
212                         int count = In.read(buffer);
213                         String receiveData = new String(buffer, 0, count);
214                         Log.i(TAG, "read buffer:"+receiveData+",count="+count);
215                         Message msg = handler.obtainMessage();;
216                         msg.what = 2;
217                         msg.obj = receiveData;
218                         handler.sendMessage(msg); 
219 
220                     } catch (IOException e)
221                     {
222                         e.printStackTrace();
223                     }
224                 
225             }
226         };
227         
228         //發送秘鑰
229         private void sendSecretKey(String msg)
230           {
231             ((PrintStream) output).print(msg);
232           }
233         //創建一個類來存放輸入輸出流
234         public static class IsAndOs{
235             private InputStream is;
236             private OutputStream os;
237 
238             public void setIs(InputStream is){
239                 this.is = is;
240             }
241 
242             public InputStream getIs(){
243                 return is;
244             }
245 
246             public void setOs(OutputStream os){
247                 this.os = os;
248             }
249 
250             public OutputStream getOs(){
251                 return os;
252             }
253 
254         }
255 }


在這里,將通信網絡連接部分開了一個子線程,並傳消息(輸入輸出流對象)到主線程,同時開啟一個接收數據的線程,一定要重新開一個線程,要不然會報錯,客戶端在接收到數據后,再更新UI進行頁面跳轉。

 


免責聲明!

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



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