一、安卓下的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進行頁面跳轉。