一、安卓下的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" />
加了网络权限,还是报异常,这让我十分苦恼,还尝试了降低安卓运行版本的办法,试图降到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进行页面跳转。