这几天在做一个android平台的关于网络通信的项目,之前android学习的不是很多,java语法也一知半解,所以在学习的过程中遇到了很多迷惑,一点点解决起来也十分困难。
所以首先建议初学者最好是先认真的学习java语言,然后再接触安卓开发,这样学习安卓的速度有很大提升,而且对于扎实的学习很有帮助。
在安卓4.0版本以后,无法在主线程中访问网络。因为访问网络的话,如果因为一些网速原因或者手机处理器慢等等原因,造成了延迟大于五秒(好像是这个时间),那么主线程(activity)会造成系统警报,报错。所以安卓4.0以后的系统版本将socket访问网络规定为只能在 子线程中进行。
下面是一个服务器和手机客户端的交互实例。
功能:手机端接受一条来自服务器的string数据并且显示。
界面:测试用的demo,所以随便拖了一个textview和button进去,IDE上随便用鼠标拖了一下位置。
代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/textView1"
android:layout_centerHorizontal="true"
android:text="Button" />
</RelativeLayout>
服务器端代码:
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
public class SerMachine {
//PORT 变量代表服务器端口
private static final int PORT = 40001;
public static void main(String[] args) {
//socket类的实例化对象建立,必须放在try里面,这是规定
try {
// 实例化服务器套接字 设置端口号9999
ServerSocket server = new ServerSocket(PORT);
//服务器端的ServerSocket对象是不需要关闭的,如代码最后所示,只需要关闭流对象就可以了
while (true) {
//socket中的阻塞不是由while(true)产生的,而是由accept产生的
Socket socket = server.accept();
//如果accept没有接受到客户端的接入请求的话,就会一直阻塞在这里
//所以一个服务器面向多个客户端的时候,都是用多线程socket的
//就是说为每一个来访的客户端配置一个子线程,每个线程用来负责一个客户端
//客户端的所有请求都在对应的子线程中处理
//对应的,服务器接受客户端发来的数据可以使用BufferedReader + InputStreamReader
//此处是向客户端发送数据,所以用BufferedWriter + OutputStreamWriter
//曾经我试过OutputStream writer=(OutputStream) socket.getOutputStream();
//但是我没有调出来,经过鉴定还是上述的B+I和B+O的组合最好用,可行性强,比较简单
//上面OutputStream = socket.getOutputStream这种写法是我看李刚老师的疯狂android看到的
//我还试过printStream等等好多流,反正只有B+I+O成功了
//下面就是获取输出流
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream()));
//向客户端发送string数据
writer.write("Messages come from SerMachine");
//每次发送完数据都要刷新一下,据说这这样比较好
writer.flush();
//关闭流
writer.close();
}
//抛出异常是必须的!必须的!必须的!
} catch (UnknownHostException e){
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
下面是客户端的代码:
package com.example.service_behind2;
import android.os.Bundle;
import android.app.Activity;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
import android.annotation.SuppressLint;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
@SuppressLint("HandlerLeak")
//上面这句话可有可无,网上有说明,大家可以自行百度
public class MainActivity extends Activity {
//先建立两个对象,用来获取xml中的控件
private Button receive_button;
private TextView MessageShow;
//这个string用来保存从服务器接受来的信息
private String message_from_sermachine;
//HOST表示IP地址,这是我的电脑联网时候的IP地址
//服务器也是运行在我的电脑上,所以IP地址就是这个
private static final String HOST = "121.229.136.83";
//PORT表示对应端口,必须和服务器的ServeSocket端口一样
private static final int PORT = 40001;
//查看IP的方法是:开始-》运行-》cmd-》ipconfig-》ppp适配器-》Ipv4
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//通过R.id找到对应的xml控件
receive_button = (Button) findViewById(R.id.button1);
MessageShow = (TextView) findViewById(R.id.textView1);
//给receive_button添加事件监听器
receive_button.setOnClickListener(new ReceiverListener());
}
@SuppressLint("HandlerLeak")
//上面这行代码同理
class ReceiverListener implements OnClickListener {
//实现OnClickListener接口
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
new Thread() {
//点击button后,会开一个新的线程!
@Override
public void run() {
//重写线程中的run方法
try {
// 实例化Socket对象
Socket socket = new Socket(HOST, PORT);
// 获得输入流
//此处同理,请见服务器端代码注释
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
message_from_sermachine = br.readLine();
//message_from_sermachine.getBytes("utf-8");
//这行代码注释掉是因为有没有都一样
//从服务器接受到的string,编码方式和android不一样
//android用的是utf-8编码
//所以造成了服务器传输中文汉字时候不能被客户端正确解码的现象
//会显示一堆乱码,所以我想用getBytes();函数来解决编码问题
//但是失败了,这个实例到现在还是没办法正确显示服务器发来的汉子
//希望有办法的朋友教教我
br.close();
//关闭流
//抛出异常是必须的!
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//接受好的数据已经保存在了message_from_sermachine变量里面
//但是就像主线程无法访问网络,只能在子线程访问一样
//更新UI只能在主线程中做,不能在子线程中控制前台UI控件
//所以需要handle类来解决这个问题
//下面调用handler对象的sendEmptyMessage方法
//事实上传出的是空消息
//但是服务器发送来的数据,已经保存在了那个string里面
handler.sendEmptyMessage(0);
}
}.start();
//开启这个线程
}
}
// 定义Handler对象
private Handler handler = new Handler() {
@Override
//只要接到消息发出,就会执行这个方法
//包括空消息
public void handleMessage(Message msg) {
super.handleMessage(msg);
// 处理前台的UI控件
//将接受的数据,显示在textview里面
MessageShow.setText(message_from_sermachine);
}
};
}
此外,千万不要忘了在androidManifest文件中添加访问网络的权限。
<uses-permission android:name="android.permission.INTERNET"/>
这行代码有时候复制会报错,所以建议大家最好是自己重新打一遍。
并且做了适当的修改,是能够正确运行的。
测试的时候,我的API版本是4.2建立的工程。
有任何疑问欢迎大家交流。