/*************************************************************************************************
PROJECT: wifi遥控小车
HARDWARE: STC89C52RC单机, L239D直流电机驱动器, ESP8266安信可wifi模块
SOFTWARE: Eclipse安卓开发环境
AUTHOR: DDDDD
DATE: 2014-12-5
*************************************************************************************************/
这个小项目有软件和硬件两大块,这里主要阐述在开发android客户端中遇到的问题
1、需要几个普通的Button,控制前进后退等。
findViewById()这个函数将定义的button变量和xml文件中的button控件绑定起来
upBtn = (Button)findViewById(R.id.upbtn);
setOnClickListener()为button控件添加监听器,这样button被按下后监听器就会执行里面的函数
downBtn.setOnClickListener(new runButtonListenner());
如果为每一个button都绑定一个监听器,程序很不友好。最常用的方法就是为所有同种类型的button设置同一个监听器。
upBtn.setOnClickListener(new runButtonListenner());
downBtn.setOnClickListener(new runButtonListenner());
leftBtn.setOnClickListener(new runButtonListenner());
rightBtn.setOnClickListener(new runButtonListenner());
stopBtn.setOnClickListener(new runButtonListenner());
在监听器里,利用switch语句来判断是哪个button被按下,onclick函数喊传入参数v,通过调用参数v的getId方法获取按钮
class runButtonListenner implements OnClickListener
{
@Override
public void onClick(View v)
{
// TODO Auto-generated method stub
switch(v.getId())
{
case R.id.upbtn:
break;
case R.id.upbtn:
break;
}
}
2、连接服务器的按钮应该是个开关型按钮,按下和松开是两种状态。在android里有ToggleButton类,这就是个按钮控件,但是个人感觉用着
很不舒服,因此就自定义了一个开关型的button。其实就是一个普通的button,通过判断语句来实现开关的功能,关于这一部分功能可以参
考我的源代码
3、socket编程,这个项目里TCP服务器已经由硬件实现,android只需要实现TCP客户端
(1)socket是要访问网络的,因此需要在AndroidManifset中申明网络访问权限
这句话代表访问网络权限,加在application标签后
(2)在开发时遇到一个问题,android4.0以上的版本不支持在同一个线程里访问网络,因此必须创建一个新线程
//创建一个线程,将ip和端口号传入,在新的线程里创建socket
TcpClientThread tcpClientThread = new TcpClientThread(ip, port);
//启动线程
tcpClientThread.start();
由于创建socket时IP和端口应该由外部输入,不能定死,因此需要在创建新线程时传递参数。这样就需要
在新的线程类中有带参数的构造函数
public class TcpClientThread extends Thread
{
String ip = null;
int port = 0;
//在构造函数里得到参数ip 和port
public TcpClientThread(String Ip, int Port)
{
// TODO Auto-generated constructor stub
ip = Ip;
port = Port;
}
@Override
//run方法会自动调用
public void run()
{
}
}
(3)创建线程的函数new Socket(dstName, dstPort)是一个阻塞的过程,如果服务器没有启动,那么程序就会一直等待,
因此用下面这种方法来创建一个socket
//创建一个socket
socket = new Socket();
//获取socket地址
SocketAddress socketAddress = new InetSocketAddress(ip, port);
//连接socket
socket.connect(socketAddress);
4、源代码
(1)MainActivity.java
点击(此处)折叠或打开
package com.example.tcp_client;
import java.io.IOException;
import java.io.OutputStream;
import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends Activity
{
//按钮控件,有前后左右,停车
private Button upBtn = null;
private Button downBtn = null;
private Button leftBtn = null;
private Button rightBtn = null;
private Button stopBtn = null;
//按钮控件,连接或者断开服务器
private Button tcpBtn = null;
//EditText 输入服务器ip和端口号
private EditText ipEdit = null;
private EditText portEdit = null;
//TextView 显示信息
private TextView tcpInfoText = null;
private TextView carInfoText = null;
private String IP = null;
private int PORT;
//如果已经连接到服务器,那么按钮上就应该显示断开服务器
private String linked = "断开服务器";
//如果服务器已经断开,那么按钮上就应该显示;连接服务器
private String unlink = "连接服务器";
//检测自定义ToggleButton的状态,false代表还没按下,true代表按下
private boolean isChecked = false;
//自定义的TcpClient类实体
private TcpClient tcpClient = null;
//输出流,用于socket
static OutputStream outStream = null;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
//指定布局文件
setContentView(R.layout.activity_main);
//将控件找到
upBtn = (Button)findViewById(R.id.upbtn);
downBtn = (Button)findViewById(R.id.downbtn);
leftBtn = (Button)findViewById(R.id.leftbtn);
rightBtn = (Button)findViewById(R.id.rightbtn);
stopBtn = (Button)findViewById(R.id.stopBtn);
//为控件绑定监听器Listener
upBtn.setOnClickListener(new runButtonListenner());
downBtn.setOnClickListener(new runButtonListenner());
leftBtn.setOnClickListener(new runButtonListenner());
rightBtn.setOnClickListener(new runButtonListenner());
stopBtn.setOnClickListener(new runButtonListenner());
//连接服务器的按钮式一个button控件,自定义为ToggleButton
tcpBtn = (Button)findViewById(R.id.tcpToggleBtn);
tcpBtn.setOnClickListener(new switchButtonListener());
tcpBtn.setText(unlink);
ipEdit = (EditText)findViewById(R.id.ipEdit);
portEdit = (EditText)findViewById(R.id.portEdit);
tcpInfoText = (TextView)findViewById(R.id.tcpInfoText);
carInfoText = (TextView)findViewById(R.id.carInfoText);
}
//关于小车状态按钮的监听器
class runButtonListenner implements OnClickListener
{
@Override
public void onClick(View v)
{
// TODO Auto-generated method stub
switch(v.getId())
{
case R.id.upbtn:
if(outStream != null)
{
tcpClient.sendBuffer(outStream, "#up*");
carInfoText.setText("小车正在前进");
}
break;
case R.id.downbtn:
if(outStream != null)
{
tcpClient.sendBuffer(outStream, "#dn*");
carInfoText.setText("小车正在后退");
}
break;
case R.id.leftbtn:
if(outStream != null)
{
tcpClient.sendBuffer(outStream, "#zz*");
carInfoText.setText("小车正在左转");
}
break;
case R.id.rightbtn:
if(outStream != null)
{
tcpClient.sendBuffer(outStream, "#yz*");
carInfoText.setText("小车正在右转");
}
break;
case R.id.stopBtn:
if(outStream != null)
{
tcpClient.sendBuffer(outStream, "#sp*");
carInfoText.setText("小车已经停车");
}
default:
break;
}
}
}
//自定义ToggleButton控件的监听器,按下和松开是两种显示状态
class switchButtonListener implements OnClickListener
{
@Override
public void onClick(View v)
{
// TODO Auto-generated method stub
//每次点击按钮之后,按钮的状态就应该取反
isChecked = !isChecked;
//如果按下按钮,ToggleButton的状态时true
if(isChecked)
{
//从ip的EditText获取IP
IP = ipEdit.getText().toString();
//如果没有输入IP,那么获取的字符串长度就是0,按钮的状态还是false
if(IP.length() == 0)
{
//没有IP,ToggleButton的状态还是false
isChecked = false;
//弹出提示对话框,提示用户输入IP
new AlertDialog.Builder(MainActivity.this)
.setTitle("提示")
.setIcon(android.R.drawable.ic_dialog_info)
.setMessage("请输入服务器IP地址")
.setPositiveButton("确定", new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface arg0, int arg1) {
// TODO Auto-generated method stub
}})
.show();
}
else
{
//如果没有输入端口号,那么从端口edit获取的字符串长度也是0
if(portEdit.getText().toString().length() == 0)
{
//没有端口号,ToggleButton的状态还是false
isChecked = false;
//弹出对话框,提示用户输入端口
new AlertDialog.Builder(MainActivity.this)
.setTitle("提示")
.setIcon(android.R.drawable.ic_dialog_info)
.setMessage("请输入服务器端口号")
.setPositiveButton("确定", new DialogInterface.OnClickListener(){
@Override
public void onClick(DialogInterface arg0, int arg1) {
// TODO Auto-generated method stub
}})
.show();
}
try{
//将获取的端口号转为一个整数
PORT = Integer.parseInt(portEdit.getText().toString());
}catch (Exception e){
e.printStackTrace();
}
//创建TcpClient实体
tcpClient = new TcpClient();
//创建socket,并且获取到一个outputStream
tcpClient.createTcpCient(IP, PORT);
if(outStream != null)
{
//如果得到了OutputStream,在信息框提示连接成功自定义ToggleButton就显示
tcpInfoText.setText("OK,连接服务器成功");
//已经连接到服务器,ToggleButton显示“断开连接”
tcpBtn.setText(linked);
}
else
{
//没有得到OutputStream,自定义ToggleButton的属性还是false
isChecked = false;
}
}
}
else
{
//如果ToggleButton的状态是false,那么就断开socket
try
{
TcpClient.socket.close();
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
//释放资源
outStream = null;
tcpClient = null;
//将ToggleButton的内容设置为“连接服务器”
tcpBtn.setText(unlink);
//信息edit提示服务器断开
tcpInfoText.setText("注意,服务器已断开");
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
}
(2)TcpClient.java
点击(此处)折叠或打开
package com.example.tcp_client;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
public class TcpClient
{
public static Socket socket = null;
public void createTcpCient(String ip, int port)
{
//创建一个线程,将ip和端口号传入,在新的线程里创建socket
TcpClientThread tcpClientThread = new TcpClientThread(ip, port);
//启动线程
tcpClientThread.start();
}
public class TcpClientThread extends Thread
{
String ip = null;
int port = 0;
//在构造函数里得到参数ip 和port
public TcpClientThread(String Ip, int Port)
{
// TODO Auto-generated constructor stub
ip = Ip;
port = Port;
}
@Override
//run方法会自动调用
public void run()
{
// TODO Auto-generated method stub
super.run();
try{
//创建一个socket
socket = new Socket();
//获取socket地址
SocketAddress socketAddress = new InetSocketAddress(ip, port);
//连接socket
socket.connect(socketAddress);
//从socket获取一个outputstream
MainActivity.outStream = socket.getOutputStream();
//将buffer的内容写入outputstream
}catch (IOException e){
e.printStackTrace();
}
}
}
//通过socket发送数据
public void sendBuffer(OutputStream outputStream, String buf)
{
byte buffer[] = null;
try
{
//将String转为byte类型
buffer = buf.getBytes("ISO-8859-1");
} catch (UnsupportedEncodingException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
try
{
//输出数据
outputStream.write(buffer, 0, buffer.length);
outputStream.flush();
} catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
(3)activity_main.xml
点击(此处)折叠或打开
xmlns: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" >
android:id="@+id/upbtn"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/downbtn"
android:text="前进" />
android:id="@+id/leftbtn"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/upbtn"
android:layout_marginRight="28dp"
android:layout_toLeftOf="@+id/upbtn"
android:text="左转" />
android:id="@+id/rightbtn"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="@+id/stopBtn"
android:layout_alignBottom="@+id/stopBtn"
android:layout_marginLeft="24dp"
android:layout_toRightOf="@+id/stopBtn"
android:text="右转" />
android:id="@+id/stopBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@+id/leftbtn"
android:layout_alignLeft="@+id/upbtn"
android:layout_alignRight="@+id/upbtn"
android:layout_below="@+id/upbtn"
android:text="停车" />
android:id="@+id/downbtn"
style="?android:attr/buttonStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/stopBtn"
android:layout_centerHorizontal="true"
android:text="后退" />
android:id="@+id/ipEdit"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint = "请输入服务器IP"
android:inputType="none"
android:layout_below="@+id/downbtn"
/>
android:id="@+id/portEdit"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint = "请输入服务器端口"
android:inputType="none"
android:layout_below="@+id/ipEdit"
/>
android:id="@+id/tcpInfoText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tcpToggleBtn"
android:singleLine="false"
/>
android:id="@+id/carInfoText"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tcpInfoText"
android:singleLine="false"
/>
android:id="@+id/tcpToggleBtn"
style="@android:style/MediaButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/portEdit"
android:layout_alignRight="@+id/portEdit"
android:layout_below="@+id/portEdit"
android:textOn="断开服务器"
android:textOff="连接服务器"
/>
(4)AndroidManifest.xml
点击(此处)折叠或打开
package="com.example.tcp_client"
android:versionCode="1"
android:versionName="1.0" >
android:minSdkVersion="8"
android:targetSdkVersion="18" />
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
android:name="com.example.tcp_client.MainActivity"
android:label="@string/app_name" >