第一部分 Android网络基础
Android平台浏览器采用了WeBKit引擎,这款名为Chorme Lite的Web浏览器拥有强大扩展特性,每个开发者都以为编写自己的插件,使得浏览器的功能更加完善。
目前Android平台有3种网络接口。
第一种 java.net.*(标准java接口)
此接口提供与联网有关的类,包括流和数据包套接字、Internet协议、常见HTTP处理。如:创建URL以及URLConnection/HttpURLConnection对象、设置连接参数、连接服务器、向服务器写
数据、从服务器读取数据等通信。
下例为常见java.net包的Http例子:
try{
URL url = new URL("http://www.google.com")//定义地址
HttpURLConnection http = (HttpURLConnection) url.openConnection();//打开连接
int nRC = http.getResponseCode();//得到连接状态
if(nRC == HttpURLConnection.HTTP_OK){
InputStream is = http.getInputStream();//取得数据
.....//处理数据
}
}catch(Exception e){
//因是连接网络,不免会出现一些异常,所以必须处理这些异常
}
第二种 Apache接口
Android提供的Apache HttpClient,它是一个开源项目,功能更加完善,为客户端的Http编程提供高效、最新、功能丰富的工具包。
Android目前使用的是HttpClient4.0(org.apache.http.*),可将Apache看为目前流行的开源Web服务器,主要包括创建HttpClient以及Get/Post、HttpRequest等对象,设置连接参数,执行HTTP操作,
处理服务器返回结果等功能。
下例为使用android.net.http.*包的例子:
try{
HttpClient hc = new DefaultHttpClient();//创建HttpClient,这里使用DefaultHttpClient表示默认属性
HttpGet hg = new HttpGet("http://www.google.com");//HttpGet实例
HttpResponse rp = hc.execute(hg);//连接
if(rp.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
InputStream is = rp.getEntity().getContent();
.....//处理数据
}
}catch(IOEeception e){
}
第三中 Android网络接口
android.net.*包实际是通过Apache中HttpClient的封装来实现的一个HTTP编程接口,同时还提供了Http请求队列管理以及Http连接池管理,以提高并发请求情况下(如装载网页时)的效率
还包括 网络状态监视等接口,网络访问的Socket,常用的Uri类以及有关WiFi相关的类等等。
下例为最简单的Socket连接
try{
InetAddress ia = InetAddress.getByName("192.168.1.110");//IP地址
Socket sk = new Socket(inetAddress,61203,true);//端口
InputStream is =sk.getInputStream();//得到数据
OutputStream os = sk.getOutputStream();
.....//数据处理
os.close();
is.close();
sk.close();
}catch(UnknownHostException e){
}catch(IOException e){
}
第二部分 Http通信
Android提供了HttpURLConnection和HttpClient接口来开发Http程序。
Http(Hyper Text Transfer Protocol,超文本传输协议)用于传输WWW方式的数据。
HTTP通信中客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程为"一次连接"。要保证客户端程序在线状态,需要不断地向
服务器发送连接请求。通常的做法是即使不需要获得任何数据,客户端也保持没隔一段固定的时间向服务器发送一次“保持连接”的请求,服务器在收到该请求后对客户端进行回复,表明
知道客户端在线。若长时间无法收到客户端的请求,则认为客户端下线,若客户端长时间无法收到服务器的回复,则认为网络已经断开。很多情况下,需要服务器端主动向客户端发送数据
以保持客户端与服务器数据的实时与同步。Http通信中,服务器需要等到客户端发送一次请求后才能将数据传回给客户端,因此,客户端定时向服务器端发送连接请求,不仅可以保持在线,
同时也是在"询问"服务器是否有新数据,如果有就将数据传给客户端。
Http采用请求/响应的模式。客户端向服务器发送一个请求,请求头包含了请求方法,URI,协议版本,以及包含请求修饰符,客户信息和内容,类似于MIME消息结构。服务器以一个状态行作
为响应,响应的内容包含消息协议的版本,成功或错误编码,还包含服务器消息,实体元信息以及可能的实体内容。它是一个属于应用层的面向对象的协议,适用于分布式超媒体信息系统。
许多HTTP通信是由一个用户代理初始化的,并且包括一个申请在源服务器上资源的请求。最简单的情况可能是在用户代理和服务器之间通过一个单独的连接来完成。
在Internet上,Http通信通常发生在TCP/IP连接上,缺省端口是TCP80.其他端口也是可用的。
第一种 HttpURLConnection接口
Http通信中使用最多的是Get和Post.
Get请求可用获得静态页面,也可以把参数放在URL字串的后面,传递给服务器。
Post参数不是放在URL字串里面,而是放在http请求数据中。
URLConnection与HttpURLConnection都是抽象类,无法直接实例化对象。其对象主要通过URL的openConnection方法获得,但openConnection方法只是创建URLConnection或者HttpURLConnection
的实例,并不是进行真正的连接操作。因此在连接之前我们可用对一些属性进行设置
对HttpConnection实例的属性设置:
connection.setDoOutput(true);//设置输出流
connection.setDoInput(true);//设置输出流
connection.setRequestMethod("POST");//设置方式为POST
connection.setUseCaches(false);//Post请求不能使用缓存
urlConn.disconnect();//连接完成之后关闭HttpURLConnection连接
首先在服务器上建立一个不需要传递参数的网页http1.jsp文件。代码如下:
<HTML>
<HEAD>
<TITLE>
Http Test
</TITLE>
</HEAD>
<BODY>
<% out.println("<h1>HTTP TEST<br>http test</h1>"); %>
</BODY>
</HTML>
再创建一个适用Get和Post来传递参数的网页httpget.jsp 代码如下:
<%@ page language="java" import = "java.util.*" pageEncoding = "gb2312" %>
<HTML>
<HEAD>
<TITLE>
Http Test
</TITLE>
</HEAD>
<BODY>
<%
String type = request.getParameter("par");
String result = new String(type.getBytes("iso-8859-1")),"gb2312");
out.println("<h1>parameters:"+result+"</h1>");
%>
</BODY>
</HTML>
以下例中通过Android程序分别以不同的方式访问这两个页面。
main.xm 文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="通过下面的按钮进行不同方式的连接"/>
<Button
android:id="@+id/Button_HTTP"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="直接获取数据"/>
<Button
android:id="@+id/Button_Get"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="以GET方式传递数据"/>
<Button
android:id="@+id/Button_Post"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="以POST方式传递数据"/>
</LinearLayout>
http.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/TextView_HTTP"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Button
android:id="@+id/Button_Back"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="返回"/>
</LinearLayout>
public class Activity01 extends Activity{//进入界面实现3个控件的Activity
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button_http = (Button) findViewById(R.id.Button_HTTP);
/* 监听button的事件信息 */
button_http.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
/* 新建一个Intent对象 */
Intent intent = new Intent();
/* 指定intent要启动的类 */
intent.setClass(Activity01.this, Activity02.class);
/* 启动一个新的Activity */
startActivity(intent);
/* 关闭当前的Activity */
Activity01.this.finish();
}
});
Button button_Get = (Button) findViewById(R.id.Button_Get);
/* 监听button的事件信息 */
button_Get.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
/* 新建一个Intent对象 */
Intent intent = new Intent();
/* 指定intent要启动的类 */
intent.setClass(Activity01.this, Activity03.class);
/* 启动一个新的Activity */
startActivity(intent);
/* 关闭当前的Activity */
Activity01.this.finish();
}
});
Button button_Post = (Button) findViewById(R.id.Button_Post);
/* 监听button的事件信息 */
button_Post.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
/* 新建一个Intent对象 */
Intent intent = new Intent();
/* 指定intent要启动的类 */
intent.setClass(Activity01.this, Activity04.class);
/* 启动一个新的Activity */
startActivity(intent);
/* 关闭当前的Activity */
Activity01.this.finish();
}
});
}
}
public class Activity02 extends Activity{//直接获取数据
private final String DEBUG_TAG = "Activity02";
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.http);
TextView mTextView = (TextView)this.findViewById(R.id.TextView_HTTP);
String httpUrl = "http://192.168.1.110:8080/http1.jsp";//http地址
String resultData = "";//获得的数据
URL url = null;
try{
url = new URL(httpUrl); //构造一个URL对象
}catch (MalformedURLException e){
Log.e(DEBUG_TAG, "MalformedURLException");
}
if (url != null){
try{
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();//使用HttpURLConnection打开连接
InputStreamReader in = new InputStreamReader(urlConn.getInputStream());//得到读取的内容(流)
BufferedReader buffer = new BufferedReader(in);// 为输出创建BufferedReader
String inputLine = null;
while (((inputLine = buffer.readLine()) != null)){//使用循环来读取获得的数据
resultData += inputLine + "\n";//我们在每一行后面加上一个"\n"来换行
}
in.close();//关闭InputStreamReader
urlConn.disconnect();//关闭http连接
if ( resultData != null ){
mTextView.setText(resultData);//设置显示取得的内容
}else {
mTextView.setText("读取的内容为NULL");
}
}catch (IOException e){
Log.e(DEBUG_TAG, "IOException");
}
}else{
Log.e(DEBUG_TAG, "Url NULL");
}
Button button_Back = (Button) findViewById(R.id.Button_Back);//设置按键事件监听
button_Back.setOnClickListener(new Button.OnClickListener(){//监听button的事件信息
public void onClick(View v){
Intent intent = new Intent();
intent.setClass(Activity02.this, Activity01.class);
startActivity(intent);
Activity02.this.finish();
}
});
}
}
public class Activity03 extends Activity{//以Get方式上传参数
private final String DEBUG_TAG = "Activity03";
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.http);
TextView mTextView = (TextView)this.findViewById(R.id.TextView_HTTP);
String httpUrl = "http://192.168.1.110:8080/httpget.jsp?par=abcdefg";//http地址"?par=abcdefg"是我们上传的参数
String resultData = "";//获得的数据
URL url = null;
try{
url = new URL(httpUrl); //构造一个URL对象
}catch (MalformedURLException e){
Log.e(DEBUG_TAG, "MalformedURLException");
}
if (url != null){
try{
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();// 使用HttpURLConnection打开连接
InputStreamReader in = new InputStreamReader(urlConn.getInputStream());//得到读取的内容(流)
BufferedReader buffer = new BufferedReader(in); // 为输出创建BufferedReader
String inputLine = null;
while (((inputLine = buffer.readLine()) != null)){//使用循环来读取获得的数据
resultData += inputLine + "\n";//我们在每一行后面加上一个"\n"来换行
}
in.close();//关闭InputStreamReader
urlConn.disconnect();//关闭http连接
if ( resultData != null ){
mTextView.setText(resultData);//设置显示取得的内容
}else {
mTextView.setText("读取的内容为NULL");
}
}catch (IOException e){
Log.e(DEBUG_TAG, "IOException");
}
}else{
Log.e(DEBUG_TAG, "Url NULL");
}
Button button_Back = (Button) findViewById(R.id.Button_Back);
button_Back.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
Intent intent = new Intent();
intent.setClass(Activity03.this, Activity01.class);
startActivity(intent);
Activity03.this.finish();
}
});
}
}
public class Activity04 extends Activity{//以Post方式上传参数
private final String DEBUG_TAG = "Activity04";
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.http);
TextView mTextView = (TextView)this.findViewById(R.id.TextView_HTTP);
String httpUrl = "http://192.168.1.110:8080/httpget.jsp";//http地址"?par=abcdefg"是我们上传的参数
String resultData = "";//获得的数据
URL url = null;
try{
url = new URL(httpUrl); //构造一个URL对象
}catch (MalformedURLException e){
Log.e(DEBUG_TAG, "MalformedURLException");
}
if (url != null){
try{
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();// 使用HttpURLConnection打开连接
urlConn.setDoOutput(true);//因为这个是post请求,设立需要设置为true
urlConn.setDoInput(true);
urlConn.setRequestMethod("POST");// 设置以POST方式
urlConn.setUseCaches(false);// Post 请求不能使用缓存
urlConn.setInstanceFollowRedirects(true);
urlConn.setRequestProperty("Content-Type","application/x-www-form-urlencoded"); // 配置本次连接的Content-type,配置为application/x-www-form-urlencoded的
// 连接,从postUrl.openConnection()至此的配置必须要在connect之前完成,
// 要注意的是connection.getOutputStream会隐含的进行connect。
urlConn.connect();
DataOutputStream out = new DataOutputStream(urlConn.getOutputStream());//DataOutputStream流
String content = "par=" + URLEncoder.encode("ABCDEFG", "gb2312");//要上传的参数
out.writeBytes(content); //将要上传的内容写入流中
out.flush();//刷新、关闭
out.close();
BufferedReader reader = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));//获取数据
String inputLine = null;
while(((inputLine = reader.readLine()) != null)){//使用循环来读取获得的数据
resultData += inputLine + "\n";//我们在每一行后面加上一个"\n"来换行
}
reader.close();
urlConn.disconnect();//关闭http连接
if ( resultData != null ){
mTextView.setText(resultData);//设置显示取得的内容
}else{
mTextView.setText("读取的内容为NULL");
}
}catch (IOException e){
Log.e(DEBUG_TAG, "IOException");
}
}else{
Log.e(DEBUG_TAG, "Url NULL");
}
Button button_Back = (Button) findViewById(R.id.Button_Back);
button_Back.setOnClickListener(new Button.OnClickListener(){
public void onClick(View v){
Intent intent = new Intent();
intent.setClass(Activity04.this, Activity01.class);
startActivity(intent);
Activity04.this.finish();
}
});
}
}
上面完成的是网络通信自是文本形式的,如果要显示网络上的一张图片,连接方式和前面相同,只需要将连接之后得到数据流转换成Bitmap就可以了。
下例为显示网络图片的方法
GetNetBitmap方法
//取得网络上的图片
//url:图片地址
public Bitmap GetNetBitmap(String url){
URL imageUrl = null;
Bitmap bitmap = null;
try{
imageUrl = new URL(url);
}catch(MalformedURLException){
Log.e(DEBUG_TAG,e.getMessage());
}
try{
HttpURLConnection conn = (HttpURLConnection)imageUrl.openConnection();
conn.setDoInput(true);
conn.connect();
InputStream is = conn.getInputStream();//将得到的数据转换成InputStream
bitmap = BitmapFactory.decodeStream(is);//将InputStream转换成Bitmap
id.close();
}catch(IOException e){
Log.e(DEBUG_TAG,e.getMessage());
}
}
第二种 HttpClient接口
与第一种相比HttpClient对java.net中的类做了封装和抽象,更适合我们在Android上开发互联网应用。
需了解如下一些类:
ClinetConnectionManager接口
此接口是客户端连接管理器接口,有如下抽象方法:
ClientConnectionManager 关闭所有无效、超时的连接
closeIdleConnections 关闭空闲的连接
releaseConnection 释放一个连接
requestConnection 请求一个新的连接
shutdown 关闭管理器并释放资源
DefaultHttpClient
是默认的一个HTTP客户端,可用它创建一个Http连接 代码如下:
HttpClinet httpclinet = new HttpClient();
HttpResponse
是一个HTTP连接响应,当执行一个HTTP连接后,就会返回一个HttpResponse,可以通过HttpResponse获得一些响应的信息。
下例为请求一个HTTP连接并获得该请求是否成功的代码:
HttpResponse httpResponse = httpclient.execute(httpRequest);
if(httpResponse.getStatusLine(),getStatusCode() == HttpStates.SC_OK){//判断是否连接成功
}
下例中分别使用Get和Post方式请求一个网页
main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="使用HttpClient来进行GET和POST连接"
/>
<Button
android:id="@+id/Button_Get"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="以GET方式传递数据"/>
<Button
android:id="@+id/Button_Post"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="以POST方式传递数据"/>
</LinearLayout>
http.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/TextView_HTTP"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<Button
android:id="@+id/Button_Back"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="返回"/>
</LinearLayout>
public class Activity01 extends Activity{
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button button_Get = (Button) findViewById(R.id.Button_Get);
/* 监听button的事件信息 */
button_Get.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
/* 新建一个Intent对象 */
Intent intent = new Intent();
/* 指定intent要启动的类 */
intent.setClass(Activity01.this, Activity02.class);
/* 启动一个新的Activity */
startActivity(intent);
/* 关闭当前的Activity */
Activity01.this.finish();
}
});
Button button_Post = (Button) findViewById(R.id.Button_Post);
/* 监听button的事件信息 */
button_Post.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
/* 新建一个Intent对象 */
Intent intent = new Intent();
/* 指定intent要启动的类 */
intent.setClass(Activity01.this, Activity03.class);
/* 启动一个新的Activity */
startActivity(intent);
/* 关闭当前的Activity */
Activity01.this.finish();
}
});
}
}
public class Activity02 extends Activity{//Get方式请求例子
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.http);
TextView mTextView = (TextView) this.findViewById(R.id.TextView_HTTP);
String httpUrl = "http://192.168.1.110:8080/httpget.jsp?par=HttpClient_android_Get";// http地址
HttpGet httpRequest = new HttpGet(httpUrl);//HttpGet连接对象
try{
HttpClient httpclient = new DefaultHttpClient();//取得HttpClient对象
HttpResponse httpResponse = httpclient.execute(httpRequest);//请求HttpClient,取得HttpResponse
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK){//请求成功
String strResult = EntityUtils.toString(httpResponse.getEntity());//取得返回的字符串
mTextView.setText(strResult);
}else{
mTextView.setText("请求错误!");
}
}catch (ClientProtocolException e){
mTextView.setText(e.getMessage().toString());
}catch (IOException e){
mTextView.setText(e.getMessage().toString());
}catch (Exception e){
mTextView.setText(e.getMessage().toString());
}
Button button_Back = (Button) findViewById(R.id.Button_Back);//设置按键事件监听
button_Back.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
Intent intent = new Intent();
intent.setClass(Activity02.this, Activity01.class);
startActivity(intent);
Activity02.this.finish();
}
});
}
}
public class Activity03 extends Activity{//Post方式请求
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.http);
TextView mTextView = (TextView) this.findViewById(R.id.TextView_HTTP);
String httpUrl = "http://192.168.1.110:8080/httpget.jsp";// http地址
HttpPost httpRequest = new HttpPost(httpUrl);//HttpPost连接对象
List<NameValuePair> params = new ArrayList<NameValuePair>();//使用NameValuePair来保存要传递的Post参数
params.add(new BasicNameValuePair("par", "HttpClient_android_Post"));//添加要传递的参数
try{
HttpEntity httpentity = new UrlEncodedFormEntity(params, "gb2312");//设置字符集,Post需要设置所使用的字符集
httpRequest.setEntity(httpentity);//请求httpRequest
HttpClient httpclient = new DefaultHttpClient();//取得默认的HttpClient
HttpResponse httpResponse = httpclient.execute(httpRequest);//取得HttpResponse
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK){//HttpStatus.SC_OK表示连接成功
String strResult = EntityUtils.toString(httpResponse.getEntity());//取得返回的字符串
mTextView.setText(strResult);
}else{
mTextView.setText("请求错误!");
}
}catch (ClientProtocolException e){
mTextView.setText(e.getMessage().toString());
}catch (IOException e){
mTextView.setText(e.getMessage().toString());
}catch (Exception e){
mTextView.setText(e.getMessage().toString());
}
Button button_Back = (Button) findViewById(R.id.Button_Back);//设置按键事件监听
button_Back.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v){
Intent intent = new Intent();
intent.setClass(Activity03.this, Activity01.class);
startActivity(intent);
Activity03.this.finish();
}
});
}
}
第三部分 实时更新
第二部分只是简单地一次性获取网页数据,而在实际开发中更多的是需要我们实时获取最新数据,比如道路流量,实时天气信息等等。
可通过一个线程来控制视图的更新,要实时的从网络获取数据,其实就是把获取网络数据的代码写到线程中,不停的进行更新。
注意:Android中更新视图不能直接在线程中进行,所以需要使用Handler来实现更新。
下例中我们创建一个网页来显示系统当前的时间,然后每隔5秒系统自动刷新一次视图。
首先,创建一个显示当前系统时间的jsp网页文件如下:
date.jsp
<% page language="java" import="java.util.*" pageEncoding="gb2312"%>
<HTML>
<HEAD>
<TITLE>
Date Test
</TITLE>
</HEAD>
<BODY>
<%java.text.SimpleDateFormat formatter = new java.text.SimpleDateFormat("yyyy-MM--dd HH:mm:ss");>
</BODY>
</HTML>
main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/TextView01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
/>
<Button
android:id="@+id/Button01"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="刷新" />
</LinearLayout>
public class Activity01 extends Activity{
private final String DEBUG_TAG = "Activity02";
private TextView mTextView;
private Button mButton;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mTextView = (TextView)this.findViewById(R.id.TextView01);
mButton = (Button)this.findViewById(R.id.Button01);
mButton.setOnClickListener(new Button.OnClickListener(){
public void onClick(View arg0){
refresh();//刷新
}
});
new Thread(mRunnable).start();//开启线程
}
private void refresh(){//刷新网页显示
String httpUrl = "http://192.168.1.110:8080/date.jsp";
String resultData = "";
URL url = null;
try{
url = new URL(httpUrl);// 构造一个URL对象
}catch (MalformedURLException e){
Log.e(DEBUG_TAG, "MalformedURLException");
}
if (url != null){
try{
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();// 使用HttpURLConnection打开连接
InputStreamReader in = new InputStreamReader(urlConn.getInputStream());// 得到读取的内容(流)
BufferedReader buffer = new BufferedReader(in);// 为输出创建BufferedReader
String inputLine = null;
while (((inputLine = buffer.readLine()) != null)){// 使用循环来读取获得的数据
resultData += inputLine + "\n";// 我们在每一行后面加上一个"\n"来换行
}
in.close();// 关闭InputStreamReader
urlConn.disconnect();// 关闭http连接
if (resultData != null){
mTextView.setText(resultData);// 设置显示取得的内容
}else{
mTextView.setText("读取的内容为NULL");
}
}catch (IOException e){
Log.e(DEBUG_TAG, "IOException");
}
}else{
Log.e(DEBUG_TAG, "Url NULL");
}
}
private Runnable mRunnable = new Runnable(){
public void run(){
while (true){
try{
Thread.sleep(5 * 1000);
mHandler.sendMessage(mHandler.obtainMessage());//发送消息
}catch (InterruptedException e){
Log.e(DEBUG_TAG, e.toString());
}
}
}
};
Handler mHandler = new Handler(){
public void handleMessage(Message msg){
super.handleMessage(msg);//接受消息
refresh();//刷新
}
};
}
第四部分 Socket通信
如果要开发一款多人联网的游戏,Http已经不能很好的满足要求了。这时就需要Socket通信了。
Socket通常称为"套接字",用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过“套接字”向网络发出请求或者应答网络请求。它是通信的基石,是支持TCP/IP协议的网络
通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必需的5种信息:
连接使用的协议、本地主机的IP地址、本地进程的协议端口、远地主机的IP地址、远地进程的协议端口。
1、Socket传输模式
Socket有两种主要操作方式:面向连接的 和 无连接的
面向连接的Socket操作就像一部电话机,必须要等对方接上之后才能通话。所有的数据到达的顺序与它出发时的顺序是一样的。面向连接的操作使用TCP协议,即此模式下必须先
连接上目的地的Socket,连接上后Socket就可以使用一个流接口进行打开、读、写、关闭等操作。所有所发信息都会在另一端以同样的顺序被接收。安全性高,但效率低。
无连接的就像是一个邮件投递,没有保证,多个邮件到达时的顺序可能与出发时的顺序不一样。无连接的操作使用数据报协议,一个数据报是一个独立的单元,它包含了这次投递的
所有信息。可将其想象成一个信封,这个模式下的Socket不需要连接一个目的Socket,它只是简单的投出数据报。无连接的操作时快速和高效的,但是数据安全性不高。
到底用哪种由应用程序的需要决定。如:文件服务器需要数据的正确性和有序性,因选面向连接的。
2、Socket编程原理
Socket构造
java.net包中提供两个类Socket和ServerSocket,分别用来表示双向连接的客户端和服务器端.
两类中其构造方法如下:
Socket(InetAddress address,int port);
Socket(InetAddress address,int port,boolean stream);
Socket(String host,int port);
Socket(String host,int port,boolean stream);
Socket(SocketImpl impl);
Socket(String host,int port,InetAddress localAddr,int localPort);
Socket(InetAddress address,int port,InetAddress localAddr,int localPort);
ServerSocket(int port);
ServerSocket(int port,int backlog);
ServerSocket(int port,int backlog,InetAddress bindAddr);
其中参数意义:
address 双向连接中另一方的IP地址
host 双向连接中另一方的主机名
port 双向连接中另一方的端口号
stream 指明Socket是流Socket还是数据报Socket
localPort 本地主机的端口号
localAddr和bindAddr是本地机器的地址(ServerSocket的主机地址)
impl 是Socket的父类,即可以用来创建ServerSocket,又可以用来创建Socket
例:
//count表示服务端所支持的最大连接数
Socket client = new Socket("192.168.1.110",54321);
ServerSocket server = new ServerSocket(54321);
注意:在选择端口时每一个端口对应一个服务,只有给出正确的端口,才能获得相应的服务。0~1023的端口号为系统所保留,例如http服务的端口号为80,telent服务的端口号
为21,ftp服务的端口号为23,所以选择端口号时最好选择一个大于1023的数 如上的54321,防止发生冲突。在创建Socket时如果发生错误,将产生IOException,所以在创建Socket
和ServerSocket时必须捕获或抛出异常。
Socket 客户端
要想使用Socket来与一个服务器通信,就必须先在客户端创建一个Socket,并指出需要连接的服务器端的IP地址和端口,代码如下:
try{
Socket socket = new Socket("192.168.1.110",33221);//"192.168.1.110"是IP地址,33221是端口号
}catch(IOException e){
}
ServerSocket 服务器端
创建一个服务器端的代码:
ServerSocket server = null;
try{
server = new ServerSocket(33221);//服务器端在33221端口号监听客户请求,在这里服务器端只能接收一个请求,接收后服务器端就退出了。实际的应用中总是让他不
停地循环接收,一旦有客户请求,服务器端总是会创建一个服务器线程来服务新来的客户,而自己则继续监听。
}catch(IOException e){
}
try{
Socket socket = new server.accpet();//accpet()为一个阻塞函数,即该方法被调用后将等待客户的请求,直到有一个客户启动并请求连接到相同的端口,然后accept
返回一个对应于客户端的Socket.这时,客户方和服务方都建立了用于通信的Socket,接下来就由各个Socket分别打开各自的输入
输出流。
}catch(IOExcetion e){
}
输入、输出流
Socket 提供了getInputStream()和getOutPutStream()来得到对应的输入(输出)流以进行读(写)操作,这两个方法分别返回InputStream和OutputStream类对象。
为了便于读(写)数据,可以在返回输入、输出流对象上建立过滤流。如:DataInputStream、DataOutPutStream、或PrintStream类对象。对于文本方式流对象,可以采用
InputStreamReader和OutputStreamWriter、PrintWirter处理 代码如下:
PrintStream os = new PrintStream(new BufferedOutputStream(Socket.getOutputStream()));
DataInputStream is = new DataInputStream(socket.getInputStream());
PrintWriter out = new PrintWriter(socket.getOutStream(),true);
BufferedReader in = new ButfferedReader(new InputStreamReader(Socket.getInputStream()));
关闭Socket和流
在Socket使用完毕后需要将其关闭,以释放资源。
注意:在关闭Socket之前,应将与Socket相关的所有的输入、输出流先关闭,以释放资源。要注意关闭的顺序。
os.close();//输出流先关闭
is.close();//输入流其次
socket.close();//最后关闭Socket
下例中 实现一个服务器和客户端通信。客户端发送数据并接受服务器发回的数据。
public class Server implements Runnable{//服务器实现 注意:该程序需要单独编译,并在命令行模式下启动
public void run(){
try{
ServerSocket serverSocket = new ServerSocket(54321);//创建ServerSocket 设置端口号为54321
while (true){
Socket client = serverSocket.accept();//通过accept监听接受客户端请求
System.out.println("accept");
try{
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));//通过BufferedReader对象接收客户端消息
String str = in.readLine();
System.out.println("read:" + str);
PrintWriter out = new PrintWriter( new BufferedWriter( new OutputStreamWriter(client.getOutputStream())),true); //通过PrintWriter向服务器发送消息,但
需要通过Socket对象来取得其输出流
out.println("server message");
out.close();//关闭流
in.close();
}catch (Exception e){
System.out.println(e.getMessage());
e.printStackTrace();
}finally{
client.close();//关闭
System.out.println("close");
}
}
}catch (Exception e){
System.out.println(e.getMessage());
}
}
public static void main(String a[]){//main函数用来开启服务器
Thread desktopServerThread = new Thread(new Server());
desktopServerThread.start();//开启线程
}
}
public class Activity01 extends Activity{//客户端
private final String DEBUG_TAG = "Activity01";
private TextView mTextView=null;
private EditText mEditText=null;
private Button mButton=null;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mButton = (Button)findViewById(R.id.Button01);
mTextView=(TextView)findViewById(R.id.TextView01);
mEditText=(EditText)findViewById(R.id.EditText01);
mButton.setOnClickListener(new OnClickListener(){//登陆
public void onClick(View v){
Socket socket = null;
String message = mEditText.getText().toString() + "\r\n";
try {
socket = new Socket("192.168.1.110",54321);//创建Socket 连接服务器
PrintWriter out = new PrintWriter( new BufferedWriter( new OutputStreamWriter(socket.getOutputStream())),true); //向服务器发送消息
out.println(message);
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); //接收来自服务器的消息
String msg = br.readLine(); //读取
if ( msg != null ){
mTextView.setText(msg);//接受消息后更新显示到TextView中
}else{
mTextView.setText("数据错误!");
}
out.close();//关闭流
br.close();
socket.close(); //关闭Socket
}catch (Exception e) {
Log.e(DEBUG_TAG, e.toString());
}
}
});
}
}
通过上例总结了一下:
使用Socket实现客户端的步骤;
1、通过IP地址和端口实例化Socket,请求连接服务器
2、获取Socket上的流以进行读写
3、把流包装进BufferReader/PrintWriter的实例
4、对Socket进行读写
5、关闭打开的流
创建服务器的步骤:
1、指定端口实例化一个ServerSocket
2、调用ServerSocket的accept()以在等待连接期间造成阻塞
3、获取位于该层Socket的流以进行读写操作
4、将数据封装成流
5、对Socket进行读写
6、关闭打开的流
第五部分 Socket应用---简易聊天室
第四部分例子中实现了一个客户端和一个服务器的单独通信,并且只能一次通信,在实际中,往往需要在服务器上运行一个永久的程序,它可以接收来自其他多个客户端的请求,并
提供相应服务。这就需要多线程来实现多客户机制。服务器总是在指定的端口上监听是否有客户请求,一旦监听到客户请求,服务器就会启动一个专门的服务线程来响应该客户的请
求,而服务器本身在启动完线程后马上又进入监听状态,等待下一个客户。
下例中使用Socket通信实现了一个简单的聊天室程序。
下例我们需要启动两个客户端来同时连接服务器,一个客户端是Android程序,另一个是Java程序.首先启动服务器---启动Android客户端---启动另一个PC客户端----Android客户端
发送消息----pc客户端发送消息---Android客户端发送消息----pc客户端发送消息....(当一个客户端发送消息(或连接服务器))后,服务器将向所有客户端发送一个消息,这就需要
服务器和客户端一直处于监听状态。
main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<EditText
android:id="@+id/EditText01"
android:text="聊天记录:\n"
android:layout_width="fill_parent"
android:layout_height="200px">
</EditText>
<EditText
android:id="@+id/EditText02"
android:text="输入要发送的内容"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_y="200px">
</EditText>
<Button
android:id="@+id/Button_In"
android:layout_width="80px"
android:layout_height="wrap_content"
android:text="登陆"
android:layout_x="30px"
android:layout_y="260px"
/>
<Button
android:id="@+id/Button_Send"
android:layout_width="80px"
android:layout_height="wrap_content"
android:text="发送"
android:layout_x="210px"
android:layout_y="260px"
/>
</AbsoluteLayout>
public class Server{//服务器端 需要单独编译并在命令行模式下启动测试
private static final int SERVERPORT = 54321; //服务器端口
private static List<Socket> mClientList = new ArrayList<Socket>(); //客户端连接 通过List来储存所有连接进来的客户端的Socket对象(也可用CopyOnWriteArrayList来储存)
private ExecutorService mExecutorService; //线程池 需要为每个客户端都开启一个线程
private ServerSocket mServerSocket; //ServerSocket对象
public static void main(String[] args){ //main方法 开启服务器
new Server();
}
public Server(){
try{
mServerSocket = new ServerSocket(SERVERPORT);//设置服务器端口
mExecutorService = Executors.newCachedThreadPool();//创建一个线程池
System.out.println("start...");
Socket client = null;//用来临时保存客户端连接的Socket对象
while (true){
client = mServerSocket.accept(); //接收客户连接并添加到list中
mClientList.add(client);
mExecutorService.execute(new ThreadServer(client));//开启一个客户端线程
}
}catch (IOException e){
e.printStackTrace();
}
}
static class ThreadServer implements Runnable{//每个客户端单独开启一个线程
private Socket mSocket;
private BufferedReader mBufferedReader;
private PrintWriter mPrintWriter;
private String mStrMSG;
public ThreadServer(Socket socket) throws IOException{
this.mSocket = socket;
mBufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
mStrMSG = "user:"+this.mSocket.getInetAddress()+" come total:" + mClientList.size();
sendMessage();
}
public void run(){
try{
while ((mStrMSG = mBufferedReader.readLine()) != null){
if (mStrMSG.trim().equals("exit")){
//当一个客户端退出时
mClientList.remove(mSocket);
mBufferedReader.close();
mPrintWriter.close();
mStrMSG = "user:"+this.mSocket.getInetAddress()+" exit total:" + mClientList.size();
mSocket.close();
sendMessage();
break;
}else{
mStrMSG = mSocket.getInetAddress() + ":" + mStrMSG;
sendMessage();
}
}
}catch (IOException e){
e.printStackTrace();
}
}
private void sendMessage() throws IOException{//发送消息给所有客户端
System.out.println(mStrMSG);
for (Socket client : mClientList){
mPrintWriter = new PrintWriter(client.getOutputStream(), true);
mPrintWriter.println(mStrMSG);
}
}
}
}
public class Client2{//需要单独编译并在命令行模式下启动测试
private static final int PORT = 54321;
private static ExecutorService exec = Executors.newCachedThreadPool();
public static void main(String[] args) throws Exception{
new Client2();
}
public Client2(){
try{
Socket socket = new Socket("192.168.1.110", PORT);
exec.execute(new Sender(socket));
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg;
while ((msg = br.readLine()) != null){
System.out.println(msg);
}
}catch (Exception e){
}
}
static class Sender implements Runnable{//客户端线程获取控制台输入消息
private Socket socket;
public Sender(Socket socket){
this.socket = socket;
}
public void run(){
try{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
String msg;
while (true){
msg = br.readLine();
pw.println(msg);
if (msg.trim().equals("exit")){
pw.close();
br.close();
exec.shutdownNow();
break;
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
public class Activity01 extends Activity{//客户端, 客户端需要与服务器始终保持通信状态 注意:因Android是线程安全的,所以不能直接在线程中更新视图,需使用Handler来更新视图
当点击”登陆“按钮时,连接服务器,并取得需要操作的流,点击"发送"按钮时取出输入框中的内容发送向服务器,由服务器发送给每个客户端
private final String DEBUG_TAG = "Activity01";
private static final String SERVERIP = "192.168.1.110";//服务器IP、端口
private static final int SERVERPORT = 54321;
private Thread mThread = null;
private Socket mSocket = null;
private Button mButton_In = null;
private Button mButton_Send= null;
private EditText mEditText01 = null;
private EditText mEditText02 = null;
private BufferedReader mBufferedReader = null;
private PrintWriter mPrintWriter = null;
private String mStrMSG = "";
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mButton_In = (Button)findViewById(R.id.Button_In);
mButton_Send = (Button)findViewById(R.id.Button_Send);
mEditText01=(EditText)findViewById(R.id.EditText01);
mEditText02=(EditText)findViewById(R.id.EditText02);
mButton_In.setOnClickListener(new OnClickListener(){//登陆按钮
public void onClick(View v){
try {
mSocket = new Socket(SERVERIP, SERVERPORT); //连接服务器
mBufferedReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));//取得输入、输出流
mPrintWriter=new PrintWriter(mSocket.getOutputStream(), true);
}catch (Exception e) {
Log.e(DEBUG_TAG, e.toString());
}
}
});
mButton_Send.setOnClickListener(new OnClickListener(){//发送消息按钮
public void onClick(View v){
try {
String str = mEditText02.getText().toString() + "\n";//取得编辑框中我们输入的内容
mPrintWriter.print(str);//发送给服务器
mPrintWriter.flush();
}catch (Exception e) {
Log.e(DEBUG_TAG, e.toString());
}
}
});
mThread = new Thread(mRunnable);
mThread.start();
}
private Runnable mRunnable = new Runnable() { //线程:监听服务器发来的消息
public void run(){
while (true){
try{
if ( (mStrMSG = mBufferedReader.readLine()) != null ){
mStrMSG+="\n";//消息换行
mHandler.sendMessage(mHandler.obtainMessage());// 发送消息
}
}catch (Exception e){
Log.e(DEBUG_TAG, e.toString());
}
}
}
};
Handler mHandler = new Handler() {
public void handleMessage(Message msg){
super.handleMessage(msg);//接受消息 刷新
try{
mEditText01.append(mStrMSG); //将聊天记录添加进来
}catch (Exception e){
Log.e(DEBUG_TAG, e.toString());
}
}
};
}
第六部分 网络通信的中文乱码问题
要想解决Java中文乱码问题,需了解如下内容:
字符:是文字与符号的总称,包括文字、图形符号、数学符号等。
字符集:就是一组抽象字符的集合。字符集常常和一种具体的语言文字对应起来,该文字中所有字符或者大部分字符就构成了该字符集。比如英文字符集、繁体汉字字符集、日文字符集等。
字符编码:计算机要处理各种字符,就需要将字符和二进制内码对应起来,这种对应关系就是字符编码。要确定编码首先要确定字符集,并将字符集内的字符排序,然后和二进制数字对应
起来。根据字符集内字符的多少,确定用几个字节来编码。
ASCII编码是目前用得最广的字符集及编码。
Unicode编码是计算机上使用的字符编码。UTF-8就是Unicode编码的实现方式。
GB2312字集是简体字集
BIG5字集台湾繁体字集
GBK字集是简繁字集
GB18030是国家制定的一个强制性大字集标准,它的推出使汉字有了统一的标准。
Linux系统默认使用的是ISO-8859-1编码
Win32系统默认使用的是GB2312编码
网络通信中,产生乱码的原因主要是通信过程中使用了不同的编码方式:如:服务器中的编码方式、传输过程中的编码方式、传输到达终端设备的编码方式。因此在传输过程中至少需要两次
编码转换。首先从服务器编码转换为网络编码,再从网络编码转换为终端设备编码。在转换过程中发生任何情况都可能引起编码混乱,
可以通过两种方式来避免编码混乱:
第一种:由于大部分终端设备都支持Unicode字符集,所以在连接网页时,我们希望网页数据在网络传输时使用UTF-8方式传输,这样就可以将UTF-8转换成Unicode字符集了。
下面为我们将通信过程中得到的流先转换为字节,然后再将字节按GB2312的方式进行转换得到字符串。
InputStream is = conn.getInputStream();
BufferedInputStream bis =new.BufferedInputStream(is);
byte bytearray[] = new byte[1024];
int current = -1;
int i = 0;
while((current = bis.read()) != -1){
bytearray[i] = (byte) current;
i++;
}
resultData = new String(bytearray,"GB2312");//resultData字符串便可显示中文效果
第二种:在数据传输过程中使用ISO-8859-1字符集,这样就是直接使用ASCII编码方式,当然在传递到终端设备时,需要将其数据反转才能正常显示。
下面将一个字符串按ISO-8859-1字符集进行转换。
public static String FormatStr(String str){
if(str = null||str.length() ==0){
return "";
}
try{
return new String(str.getBytes("ISO-8859-1"),"bgk");//使用getBytes("编码方式") 对汉字进行重编码,得到它的字节数组。再使用new String(Bytes[],"解码方式")
来对字节数组进行相应的解码。其实只要掌握了什么时候应该编码,什么时候应该解码,怎么编码、解码就
不怕中文乱码问题了。
}catch(UnsupportedEncodingException ex){
return str;
}
}
第七部分 WebKit应用
Android 浏览器的内核是Webkit引擎,WebKit的前身是KDE小组的KHTML.
WebKit
是一个开源浏览器网页排版引擎。与之对应的引擎有Gecko和Trident等。
WebKit 由3个模块组成:
WebKit 整个项目的名称
JavaScriptCore JavaScrip解释器
WebCore 整个项目的核心,用来实现Render引擎,解析Web页面,生成一个DOM树和一个Render树。
WebKit的解析过程:
1、CURL获得网站的stream。
2、解析划分字符串。
3、通过DOM Builer按合法的HTML规范生成DOM树。
4、如果有Javascript,JSEngine就通过ECMA-262标准完善DOM树。
5、把DOM传给LayoutEngine进行布局,如果有CSS样式,就通过CSSParser解析。
6、最后Rendering渲染出来。
WebView
WebView控件是WebKit类中专门用来浏览网页的控件。
其使用需要在XML文件中如下设计:
<WebView
android:id="@+id/WebView01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
/>
然后需要在程序代码中转载这个控件 ,并设置其颜色、字体、要访问的网址(也可在XML文件中设置)。
通过loadUrl方法设置当前WebView需要访问的网址代码如下:
mWebView= (WebView)findViewById(R.id.WebView01);
mWebView.loadUrl("http://www.google.com");
WebSettings
在Android中可以通过WebSettings来设置WebKit的一些属性、状态等。在创建WebView时,系统有一个默认的设置,可以通过WebView.getSettings来得到如下:
WebSettings webSettings = mWebView.getSetting();//得到mWebView的WebSettings对象
WebSettings和WebView 都在同一生命周期中存在,当WebView被销毁后,如果再使用WebSettings则会抛出IllegalStateException异常。
下为时设置一些常用属性、状态的方法。将下面的set改为get即可得到WebView的一些属性和状态。
setAllowFileAccess 启动或禁止WebView访问文件数据
setBlockNetworkImage 是否显示图像
setBuiltInZoomControls 设置是否支持缩放
setCacheMode 设置缓冲模式
setDefaultFontSize 设置默认的字体大小
setDefaultTextEncodingName 设置在解码时使用的默认编码
setFixedFontFamily 设置固定使用的字体
setJavaScriptEnabled 设置是否支持JavaScript
setLayoutAlgorithm 设置布局方式
setLightTouchEnabled 设置用鼠标激活被选项
setSupportZoom 设置是否支持变焦
WebViewClient
在程序中可以用WebViewClient来自定义网页浏览程序。
此类专门辅助WebView处理各种通知,请求等事件的类。可以通过WebView的setWebViewClient方法来创建一个WebViewClient对象。
WebViewClient常用方法:
doUpdateVisitedHistory 更新历史记录
onFormResubmission 应用程序重新请求网页数据
onLoadResource 加载指定地址提供的资源
onPageFinished 网页加载完毕
onPageStarted 网页开始加载
onReceivedError 报告错误信息
onScaleChanged WebView发生改变
shouldOverrideUrlLoading 控制新的连接在当前WebView中打开
我们可以覆盖这些方法来辅助WebView浏览网页.如下:我们设置覆盖shouldOverrideUrlLoading方法。
public boolean shouldOverrideUrlLoading(WebView view ,String url){
view.loadUrl(url);//设置访问的网址
ruturn true;
}
WebChromeClient
在Android中通过WebChromeClient专门来辅助WebView处理Javascript的对话框、网站图标、网站Title、加载进度等。
WebChromeClient中常用方法
onCloseWindow 关闭WebView
onCreateWindow 创建WebView
onJsAlert 处理Javascript中的Alert对话框
onJsConfirm 处理Javascript中的Confirm对话框
onJsPrompt 处理Javascript中的Prompt对话框
onProgressChanged 加载进度条改变
onReceivedIcon 网页图标更改
onReceivedTitle 网页Title更改
onRequestFocus WebView显示焦点
下面来实现onReceiveTitle方法,用于更改应用程序的Title 代码如下:
public void onReceivedTitle(WebView view,String title){
Activity01.this.setTitle(title);
super.onReceivedTitle(view,title);
}
下例为对上面知识的综合运用。
prom_dialog.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="center_horizontal"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<TextView
android:id="@+id/TextView_PROM"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
<EditText
android:id="@+id/EditText_PROM"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:selectAllOnFocus="true"
android:scrollHorizontally="true"/>
</LinearLayout>
main.xml文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:animationCache="true"
android:layout_weight="9">
<EditText
android:id="@+id/EditText01"
android:layout_width="wrap_content"
android:layout_weight="9"
android:layout_height="wrap_content"
android:text="请输入网址"/>
<Button
android:id="@+id/Button01"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="连接" />
</LinearLayout>
<WebView
android:id="@+id/WebView01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
/>
</LinearLayout>
public class Activity01 extends Activity{
private final String DEBUG_TAG = "Activity01";
private Button mButton;//界面中使用EditText来输入网址,用Button来确认连接,用WebView来显示网页内容。
private EditText mEditText;
private WebView mWebView;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mButton = (Button) findViewById(R.id.Button01);
mEditText = (EditText) findViewById(R.id.EditText01);
mWebView = (WebView) findViewById(R.id.WebView01);
WebSettings webSettings = mWebView.getSettings();//设置支持JavaScript脚本
webSettings.setJavaScriptEnabled(true);
webSettings.setAllowFileAccess(true);//设置可以访问文件
webSettings.setBuiltInZoomControls(true);//设置支持缩放
mWebView.setWebViewClient(new WebViewClient(){ //设置WebViewClient 来辅助WebView处理一些事件
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
public void onPageFinished(WebView view, String url) {
super.onPageFinished(view, url);
}
public void onPageStarted(WebView view, String url, Bitmap favicon) {
super.onPageStarted(view, url, favicon);
}
});
mWebView.setWebChromeClient(new WebChromeClient(){//设置WebChromeClient 来辅助WebView处理JavaScripte对话框
public boolean onJsAlert(WebView view, String url, String message,final JsResult result) {//处理javascript中的alert
//构建一个Builder来显示网页中的对话框
Builder builder = new Builder(Activity01.this);
builder.setTitle("提示对话框");
builder.setMessage(message);
builder.setPositiveButton(android.R.string.ok,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
esult.confirm();//点击确定按钮之后,继续执行网页中的操作。通过confirm和cancel方法将我们的操作传递给Javascript处理
}
});
builder.setCancelable(false);
builder.create();
builder.show();
return true;
};
public boolean onJsConfirm(WebView view, String url, String message,final JsResult result) {//处理javascript中的confirm
Builder builder = new Builder(Activity01.this);
builder.setTitle("带选择的对话框");
builder.setMessage(message);
builder.setPositiveButton(android.R.string.ok,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.confirm();//通过confirm和cancel方法将我们的操作传递给Javascript处理
}
});
builder.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.cancel();//通过confirm和cancel方法将我们的操作传递给Javascript处理
}
});
builder.setCancelable(false);
builder.create();
builder.show();
return true;
};
public boolean onJsPrompt(WebView view, String url, String message,
String defaultValue, final JsPromptResult result) {//处理javascript中的prompt,message为网页中对话框的提示内容,defaultValue在没有输入时,默认显示的内容
final LayoutInflater factory = LayoutInflater.from(Activity01.this);//自定义一个带输入的对话框由TextView和EditText构成
final View dialogview = factory.inflate(R.layout.prom_dialog, null);
((TextView) dialogview.findViewById(R.id.TextView_PROM)).setText(message);//设置TextView对应网页中的提示信息
((EditText) dialogview.findViewById(R.id.EditText_PROM)).setText(defaultValue);//设置EditText对应网页中的输入框
Builder builder = new Builder(Activity01.this);
builder.setTitle("带输入的对话框");
builder.setView(dialogview);
builder.setPositiveButton(android.R.string.ok,
new AlertDialog.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
String value = ((EditText) dialogview.findViewById(R.id.EditText_PROM)).getText().toString();//点击确定之后,取得输入的值,传给网页处理
result.confirm(value);//通过confirm和cancel方法将我们的操作传递给Javascript处理
}
});
builder.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
result.cancel();//通过confirm和cancel方法将我们的操作传递给Javascript处理
}
});
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
public void onCancel(DialogInterface dialog) {
result.cancel();//通过confirm和cancel方法将我们的操作传递给Javascript处理
}
});
builder.show();
return true;
};
public void onProgressChanged(WebView view, int newProgress) {//设置网页加载的进度条
Activity01.this.getWindow().setFeatureInt(Window.FEATURE_PROGRESS, newProgress * 100);
super.onProgressChanged(view, newProgress);
}
public void onReceivedTitle(WebView view, String title) {//设置应用程序的标题title
Activity01.this.setTitle(title);
super.onReceivedTitle(view, title);
}
});
mButton.setOnClickListener(new OnClickListener(){//连接按钮事件监听
public void onClick(View v){
try {
String url = mEditText.getText().toString();//取得编辑框中我们输入的内容
if ( URLUtil.isNetworkUrl(url) ){ //判断输入的内容是不是网址
mWebView.loadUrl(url);//装载网址
}else{
mEditText.setText("输入网址错误,请重新输入");
}
}catch (Exception e){
Log.e(DEBUG_TAG, e.toString());
}
}
});
}
public boolean onKeyDown(int keyCode, KeyEvent event){
if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()){//当我们按返回键时 可以通过goBack和goForward方法来设置其前进和后退,但在使用之前我们通过
canGoBack和canGoForward方法来检测是否能够后退和前进。
mWebView.goBack();//返回前一个页面
return true;
}
return super.onKeyDown(keyCode, event);
}
}
WebView和Javascript互相调用
下例为实现了WebView和Javascript如何互相调用,该例实现了从Android应用中调出个人资料,然后通过Javascript显示出来。
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<WebView
android:id="@+id/WebView01"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</LinearLayout>
public class Activity01 extends Activity{
private WebView mWebView;
private PersonalData mPersonalData;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
mPersonalData = new PersonalData();
mWebView = (WebView)this.findViewById(R.id.WebView01);
mWebView.getSettings().setJavaScriptEnabled(true);//设置支持JavaScript
mWebView.addJavascriptInterface(this, "PersonalData");//把本类的一个实例添加到js的全局对象window中,这样就可以使用window.PersonalData来调用它的方法
mWebView.loadUrl("file:///android_asset/PersonalData.html");//加载网页。
在Java代码中通过WebView.loadUrl("javascript:方法名");可以直接调用Javascript方法
为了让WebViwe从Apk文件中加载assets,Android SDK提供了一个Schema,前缀
为"file:///android_asset/"。当WebView遇到Schema,就去当前包中的Assets目录中找内容
}
public PersonalData getPersonalData(){//在js脚本中调用得到PersonalData对象
return mPersonalData;
}
class PersonalData{//js脚本中调用显示的资料。定义一个PersonalData类,用来保存个人资料,并且定义得到这些数据的成员函数,供Javascript调用
String mID;
String mName;
String mAge;
String mBlog;
public PersonalData(){
this.mID="123456789";
this.mName="Android";
this.mAge="100";
this.mBlog="http://yarin.javaeye.com";
}
public String getID(){
return mID;
}
public String getName(){
return mName;
}
public String getAge(){
return mAge;
}
public String getBlog(){
return mBlog;
}
}
}
通过上面的初始化WebView后,在Javascript中就可以用window.PersonalData.getPersonalData()来取得Java对象。
下面是在Javascript访问Android应用的代码。
assets.test.js文件
window.onload = function(){
var personaldate = window.PersonalData.getPersonalData();//取得java对象
if(personaldate){
var Personaldate = document.getElementById("Personaldate");
pnode = document.createElement("P");
tnode = document.createTextNode("ID:"+personaldate.getID());//通过Java对象访问数据
pnode = appendChild(tnode);
Personaldata.appendChild(pnode);
pnode = document.createElement("P");
tnode = document.createTextNode("Name:"+personaldate.getName());//通过Java对象访问数据
pnode = appendChild(tnode);
Personaldata.appendChild(pnode);
pnode = document.createElement("P");
tnode = document.createTextNode("Age:"+personaldate.getAge());//通过Java对象访问数据
pnode = appendChild(tnode);
Personaldata.appendChild(pnode);
pnode = document.createElement("P");
tnode = document.createTextNode("Blog:"+personaldate.getBlog());//通过Java对象访问数据
pnode = appendChild(tnode);
Personaldata.appendChild(pnode);
}
}
第八部分 WiFi---Wireless Fidelity
WiFi又称802.11b标准,最大优点传输速度高。
WiFi操作的一些常见类和接口
ScanResult
主要用来描述已经检测出的接入点,包括接入点的地址、名称、身份认证、频率、信号强度等信息。
WifiConfiguration
WiFi网络的配置,包括安全配置等
WifiInfo
WiFi无线连接的描述,包括接入点、网络连接状态、隐藏的接入点、IP地址、连接速度、MAC地址、网络ID、信号强度等信息。
WifiManager
包括了管理WiFi连接的大部分API,如下内容:
已经配置好的网络清单。这个清单可以查看和修改,而且可以修改个别记录的属性。
当连接中有活动的Wi-Fi网络时,可以建立或关闭这个连接,并且可以查询有关网络状态的动态信息。
对接入点的扫描结果包含足够的信息来决定需要与什么接入点建立连接。
还定义了许多常量来表示Wi-Fi状态的改变。
WifiManager.WifiLock
用户一段时间没有任何操作时,WiFi就会自动关闭,我们可以通过WifiLock来锁定WiFi网络,使其一直保持连接,直到锁定被释放。
当有多个应用程序时可能会有多个WifiLock,在无线电设备中必须保证任何一个应用中都没有使用WifiLock时,才可以关闭它。
在使用WiFi锁之前,必须确定应用需要WiFi的接入,或者能够在移动网络下工作。
WiFi锁不能超越客户端的Wi-Fi Enabled设置,也没有飞行模式。
如下代码可获得WifiManager对象,可使用这个对象来操作WiFi连接
WifiManager wifiManager = (WifiManager)getSystemService(Context.WIFI_SERVICE);
如下为WifiManager的常用方法:
addNetwork 添加一个配置好的网络连接
calculateSignalLevel 计算信号强度
compareSignalLevel 比较两个信号的强度
createWifiLock 创建一个Wifi锁
disableNetwork 取消一个配置好的网络,即使其不可用
disconnect 从接入点断开
enableNetwork 允许指定的网络连接
getConfiguredNetworks 得到客户端所有已经配置好的网络列表
getConnectionInfo 得到正在使用的连接的动态信息
getDhcpInfo 得到最后一次成功的DHCP请求的DHCP地址
getScanResults 得到被扫描到的接入点
getWifiState 得到可用WiFi的状态
isWifiEnabled 得到WiFi是否可用
pingSupplicant 检查客户端对请求的反应
reassociate 从当前接入点重新连接
removeNetwork 从已经配置好的网络列表中删除指定ID的网络
saveConfiguration 保存当前配置好的网络列表
setWifiEnabled 设置WiFi是否可用
startScan 扫描存在的接入点
updateNetwork 更新已经配置好的网络
例:下例中的WifiAdmin需要打开(关闭)WIFI、锁定(释放)WifiLock、创建WifiLock、取得配置好的网络、扫描、连接、断开、获取网络连接的信息。
因WiFi与设备有关所以需在真机上才能看到效果。
public class Activity01 extends Activity{
private WifiManager wifiManager;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
public class WifiAdmin{
private WifiManager mWifiManager;//定义WifiManager对象
private WifiInfo mWifiInfo;//定义WifiInfo对象
private List<ScanResult> mWifiList;//扫描出的网络连接列表
private List<WifiConfiguration> mWifiConfiguration;//网络连接列表
WifiLock mWifiLock;//定义一个WifiLock
public WifiAdmin(Context context){//构造器
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);//取得WifiManager对象
mWifiInfo = mWifiManager.getConnectionInfo();//取得WifiInfo对象
}
public void OpenWifi(){//打开WIFI
if (!mWifiManager.isWifiEnabled()){
mWifiManager.setWifiEnabled(true);
}
}
public void CloseWifi(){//关闭WIFI
if (!mWifiManager.isWifiEnabled()){
mWifiManager.setWifiEnabled(false);
}
}
public void AcquireWifiLock(){//锁定WifiLock
mWifiLock.acquire();
}
public void ReleaseWifiLock(){//解锁WifiLock
if (mWifiLock.isHeld()){//判断时候锁定
mWifiLock.acquire();
}
}
public void CreatWifiLock(){ //创建一个WifiLock
mWifiLock = mWifiManager.createWifiLock("Test");
}
public List<WifiConfiguration> GetConfiguration(){//得到配置好的网络
return mWifiConfiguration;
}
public void ConnectConfiguration(int index){//指定配置好的网络进行连接
if(index > mWifiConfiguration.size()){//索引大于配置好的网络索引返回
return;
}
mWifiManager.enableNetwork(mWifiConfiguration.get(index).networkId, true);//连接配置好的指定ID的网络
}
public void StartScan(){
mWifiManager.startScan();
mWifiList = mWifiManager.getScanResults();//得到扫描结果
mWifiConfiguration = mWifiManager.getConfiguredNetworks();//得到配置好的网络连接
}
public List<ScanResult> GetWifiList(){//得到网络列表
return mWifiList;
}
public StringBuilder LookUpScan(){//查看扫描结果
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < mWifiList.size(); i++){
stringBuilder.append("Index_"+new Integer(i + 1).toString() + ":");
//将ScanResult信息转换成一个字符串包,其中把包括:BSSID、SSID、capabilities、frequency、level
stringBuilder.append((mWifiList.get(i)).toString());
stringBuilder.append("\n");
}
return stringBuilder;
}
public String GetMacAddress(){//得到MAC地址
return (mWifiInfo == null) ? "NULL" : mWifiInfo.getMacAddress();
}
public String GetBSSID(){//得到接入点的BSSID
return (mWifiInfo == null) ? "NULL" : mWifiInfo.getBSSID();
}
public int GetIPAddress(){//得到IP地址
return (mWifiInfo == null) ? 0 : mWifiInfo.getIpAddress();
}
public int GetNetworkId(){//得到连接的ID
return (mWifiInfo == null) ? 0 : mWifiInfo.getNetworkId();
}
public String GetWifiInfo(){//得到WifiInfo的所有信息包
return (mWifiInfo == null) ? "NULL" : mWifiInfo.toString();
}
public void AddNetwork(WifiConfiguration wcg){//添加一个网络并连接
int wcgID = mWifiManager.addNetwork(wcg);
mWifiManager.enableNetwork(wcgID, true);
}
public void DisconnectWifi(int netId){//断开指定ID的网络
mWifiManager.disableNetwork(netId);
mWifiManager.disconnect();
}
}
AndroidManifest.xml文件 最后需要在此文件中对权限进行声明
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"></uses-permission>
<uses-permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES"></uses-permission>
<uses-permission android:name="android.permission.WAKE_LOCK"></uses-permission>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"></uses-permission>
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE"></uses-permission>
第九部分 蓝牙
蓝牙是一种短距离的无线连接技术标准的代称,蓝牙的实质内容就是建立通用无线电空中接口及其控制软件的公开标准。
蓝牙采用分散式网络结构以及快调频和短包技术,支持点对点以及多对点通信,工作在全球通用的2.4GHz ISM(即工业、科学、医学)频段,其数据速率为1Mbps,采用时分双工传输方案。
蓝牙协议分4层:即核心协议层、电缆替代协议层、电话控制协议层、采纳其他的协议层。
Android 2.0 即API Level等级为5或以上版本才包含蓝牙功能
蓝牙的类和接口位于android.bluetooth包中,有如下功能:
BluetoothAdapter 蓝牙适配器(代表本地蓝牙适配器)
BluetoothClass 蓝牙类(主要包括服务和设备)
BluetoothClass.Device 蓝牙设备类
BluetoothClass.Device.Major 蓝牙设备管理
BluetoothClass.Service 有关蓝牙服务的类
BluetoothDevice 蓝牙设备(主要指远程蓝牙设备)
BluetoothServerSocket 监听蓝牙连接的类
BluetoothSocket 蓝牙连接类
要使用蓝牙API必须要在AndroidManifest.xml中声明权限代码如下:
<uses-sdk android:minSdkVersion="5" />//API等级为5
<uses-permission android:name="android.permission.BLUETOOTH" />//蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />//蓝牙管理、操作
<uses-permission android:name="android.permission.READ_CONTACTS"/>//读取联系人
要使用蓝牙必须先取得蓝牙适配器 代码如下:
BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter();//通过getDefaultAdapter()方法获取本地蓝牙适配器,要获取远程的用BluetoothDevice类。
如何打开、关闭本地蓝牙和如何才能使别的设备能够搜索到自己的设备?如下:
首先定义两个常量,分别代表请求打开和请求能够被搜索代码如下:
private static final int REQUEST_ENABLE;//请求打开蓝牙
private static final int REQUEST_DISCOVERABLE;//请求能够被搜索
1、请求开启蓝牙
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);//请求系统允许用户打开蓝牙
startActivityForResult(enabler, REQUEST_ENABLE);
2、请求能够被搜索
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);//请求系统允许蓝牙设备被搜索
startActivityForResult(enabler, REQUEST_DISCOVERABLE);
BluetoothAdapter类中其他动作常量
ACTION_DISCOVERY_FINISHED 已经完成搜索
ACTION_DISCOVERY_STARTED 已经开始搜索蓝牙设备
ACTION_LOCAL_NAME_CHANGED 更改蓝牙名字
ACTION_REQUEST_DISCOVERABLE 请求能够被搜索
ACTION_REQUEST_ENABLE 请求开启蓝牙
ACTION_SCAN_MODE_CHANGED 描述模式已改变
ACTION_STATE_CHANGED 状态已改变
3、打开蓝牙
_bluetooth.enable();//使用BluetoothAdapter类中的enable方法
4、关闭蓝牙
_bluetooth.disable();//使用BluetoothAdapter类中的diable方法
BluetoothAdapter中的常用方法
cancelDiscovery 取消当前设备搜索过程
checkBlutoothAddress 检测蓝牙地址是否正确。如"00:43:A8:23:10:F0"字母必须是大写
disable 关闭蓝牙适配器
enable 打开蓝牙适配器
getAddress 取得本地蓝牙的硬件适配器地址
getDefaultAdapter 得到默认的蓝牙适配器
getName 得到蓝牙的名字
getRemoteDevice 取得指定蓝牙硬件地址的BluetoothDevice对象
getScanMode 得到扫描模式
getState 得到状态
isDiscovering 是否允许被搜索
isEnabled 是否打开
setName 设置名字
startDiscovery 开始搜索
5、搜索蓝牙设备
搜索远程蓝牙设备需要使用BluetoothDevice类
首先使用BluetoothAdapter类的getRemoteDevice方法来得到一个指定地址的BluetoothDevice
BluetoothDevice类实际是一个蓝牙硬件地址薄,该类对象是不可改变的。其操作都是远程蓝牙硬件地址使用BluetoothAdapter来创建一个BluetoothDevice对象。
public class DiscoveryActivity extends ListActivity{//此类为搜索蓝牙的类
private Handler _handler = new Handler();
private BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter();//取得默认的蓝牙适配器
private List<BluetoothDevice> _devices = new ArrayList<BluetoothDevice>();// 用List来存储搜索到的蓝牙设备
private volatile boolean _discoveryFinished;//是否完成搜索
private Runnable _discoveryWorkder = new Runnable() {
public void run() {
_bluetooth.startDiscovery();//开始搜索
for (;;) {
if (_discoveryFinished) {
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e){}
}
}
};
private BroadcastReceiver _foundReceiver = new BroadcastReceiver(){// 接收器 当搜索蓝牙设备完成时调用
public void onReceive(Context context, Intent intent) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);//从intent中取得搜索结果数据
_devices.add(device);//将结果添加到列表中
showDevices();//显示列表
}
};
private BroadcastReceiver _discoveryReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
/* 卸载注册的接收器 */
unregisterReceiver(_foundReceiver);
unregisterReceiver(this);
_discoveryFinished = true;
}
};
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND, WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
setContentView(R.layout.discovery);
if (!_bluetooth.isEnabled()){//如果蓝牙适配器没有打开,则结果
finish();
return;
}
/* 注册接收器 */
IntentFilter discoveryFilter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);//1搜索以完成 程序时通过线程来控制蓝牙设备的搜索的(startDiscovery)
当搜索过程中触发1、2两个接收器就直接传递给接收器进性保存
注意:搜索蓝牙设备流程为:
取得蓝牙适配器(getDefaultAdapter)---开始搜索---
搜索完成(ACTION_DISCOVERY_FINISHED)卸载BroadcastReceiver
---发现设备(ACTION_FOUND)---取得设备(EXTRA_DEVICE)
registerReceiver(_discoveryReceiver, discoveryFilter);
IntentFilter foundFilter = new IntentFilter(BluetoothDevice.ACTION_FOUND);//2发现设备
registerReceiver(_foundReceiver, foundFilter);
SamplesUtils.indeterminate(DiscoveryActivity.this, _handler, "Scanning...", _discoveryWorkder, new OnDismissListener() {//显示一个对话框,正在搜索蓝牙设备
public void onDismiss(DialogInterface dialog){
for (; _bluetooth.isDiscovering();){
_bluetooth.cancelDiscovery();
}
_discoveryFinished = true;
}
}, true);
}
protected void showDevices(){//显示列表 最后将保存在List中的BluetoothDevice显示在一个ListView中
List<String> list = new ArrayList<String>();
for (int i = 0, size = _devices.size(); i < size; ++i){
StringBuilder b = new StringBuilder();
BluetoothDevice d = _devices.get(i);
b.append(d.getAddress());
b.append('\n');
b.append(d.getName());
String s = b.toString();
list.add(s);
}
final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list);
_handler.post(new Runnable() {
public void run(){
setListAdapter(adapter);
}
});
}
protected void onListItemClick(ListView l, View v, int position, long id){
Intent result = new Intent();
result.putExtra(BluetoothDevice.EXTRA_DEVICE, _devices.get(position));
setResult(RESULT_OK, result);
finish();
}
}
蓝牙Socket服务器与客户端
蓝牙服务器端就是通过注册一个具有名称和唯一识别的UUID号的BluetoothServerSocket,然后一直监听客户端(BluetoothSocket)的请求,并作出相应处理。
如何注册蓝牙服务器代码:
private BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter();//获取默认的蓝牙适配器
//其中listenUsingRfcommWithServiceRecord可以返回一个蓝牙服务器BluetoothServerSocker对象,其中参数PROTOCOL_SCHEME_RFCOMM是一个String类型常量,代表蓝牙服务器名称
//UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c666")是该蓝牙服务器唯一标示UUID。在客户端连接这个服务器时需要使用该UUID.
private BluetoothServerSocker _serverSocket = _bluetooth.listenUsingRfcommWithServiceRecord(PROTOCOL_SCHEME_RFCOMM,UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c666"));
连接之后可以通过BluetoothServerSocket的accept方法来接收客户端请求,并作出相应处理代码如下:
BluetoothSocket socket = _serverSocket.accept();//接收客户端的连接请求,accept方法返回一个BluetoothSocket对象代表客户端
if(socket != null){//处理请求内容
}
...
_serverSocket.close;//通过BluetoothServerSocker的close方法 关闭蓝牙服务器
public class ServerSocketActivity extends ListActivity{//蓝牙Socket服务器
/* 一些常量,代表服务器的名称 */
public static final String PROTOCOL_SCHEME_L2CAP = "btl2cap";
public static final String PROTOCOL_SCHEME_RFCOMM = "btspp";
public static final String PROTOCOL_SCHEME_BT_OBEX = "btgoep";
public static final String PROTOCOL_SCHEME_TCP_OBEX = "tcpobex";
private static final String TAG = ServerSocketActivity.class.getSimpleName();
private Handler _handler = new Handler();
private BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter();// 取得默认的蓝牙适配器
private BluetoothServerSocket _serverSocket;//蓝牙服务器
private Thread _serverWorker = new Thread() {// 线程-监听客户端的链接
public void run() {
listen();
};
};
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
setContentView(R.layout.server_socket);
if (!_bluetooth.isEnabled()) {
finish();
return;
}
_serverWorker.start();//开始监听 开启线程
}
protected void onDestroy() {
super.onDestroy();
shutdownServer();
}
protected void finalize() throws Throwable {
super.finalize();
shutdownServer();
}
private void shutdownServer() {//停止服务器
new Thread() {
public void run() {
_serverWorker.interrupt();
if (_serverSocket != null) {
try {
_serverSocket.close();//关闭服务器
} catch (IOException e) {
Log.e(TAG, "", e);
}
_serverSocket = null;
}
};
}.start();
}
public void onButtonClicked(View view) {
shutdownServer();
}
protected void listen() {
try {
// 创建一个蓝牙服务器 参数分别:服务器名称、UUID
_serverSocket = _bluetooth.listenUsingRfcommWithServiceRecord(PROTOCOL_SCHEME_RFCOMM,
UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c666"));
final List<String> lines = new ArrayList<String>();//客户端连线列表 将连接进的所有客户端都放进一个List中
_handler.post(new Runnable() {
public void run() {
lines.add("Rfcomm server started...");
ArrayAdapter<String> adapter = new ArrayAdapter<String>(ServerSocketActivity.this,android.R.layout.simple_list_item_1, lines);
setListAdapter(adapter);
}
});
BluetoothSocket socket = _serverSocket.accept();//接受客户端的连接请求
if (socket != null) {//处理请求内容
InputStream inputStream = socket.getInputStream();
int read = -1;
final byte[] bytes = new byte[2048];
for (; (read = inputStream.read(bytes)) > -1;) {
final int count = read;
_handler.post(new Runnable(){
public void run() {
StringBuilder b = new StringBuilder();
for (int i = 0; i < count; ++i) {
if (i > 0) {
b.append(' ');
}
String s = Integer.toHexString(bytes[i] & 0xFF);
if (s.length() < 2) {
b.append('0');
}
b.append(s);
}
String s = b.toString();
lines.add(s);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(ServerSocketActivity.this,android.R.layout.simple_list_item_1, lines);
setListAdapter(adapter);
}
});
}
}
} catch (IOException e) {
Log.e(TAG, "", e);
} finally {
}
}
}
蓝牙客户端连接
public class ClientSocketActivity extends Activity{//蓝牙客户端
private static final String TAG = ClientSocketActivity.class.getSimpleName();
private static final int REQUEST_DISCOVERY = 0x1;;
private Handler _handler = new Handler();
private BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter();
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND,WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
setContentView(R.layout.client_socket);
if (!_bluetooth.isEnabled()) {
finish();
return;
}
Intent intent = new Intent(this, DiscoveryActivity.class);
Toast.makeText(this, "select device to connect", Toast.LENGTH_SHORT).show();//提示选择一个要连接的服务器(即搜索处理的蓝牙设备)
startActivityForResult(intent, REQUEST_DISCOVERY);// 跳转到搜索的蓝牙设备列表区,进行选择
}
protected void onActivityResult(int requestCode, int resultCode, Intent data) {//选择了服务器之后进行连接
if (requestCode != REQUEST_DISCOVERY) {
return;
}
if (resultCode != RESULT_OK) {
return;
}
final BluetoothDevice device = data.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);//因首先判断本地蓝牙是否启动,在连接时首先要确保本地连接已经启动
然后通过BluetoothDevice device = Intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);代码
取得要连接的蓝牙设备。
new Thread() {
public void run() {
connect(device);//连接
};
}.start();
}
protected void connect(BluetoothDevice device) {
BluetoothSocket socket = null;
try {
//创建一个Socket连接:只需要服务器在注册时的UUID号
// socket = device.createRfcommSocketToServiceRecord(BluetoothProtocols.OBEX_OBJECT_PUSH_PROTOCOL_UUID);
socket = device.createRfcommSocketToServiceRecord(UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c666"));
socket.connect();//与服务器进行连接
} catch (IOException e) {
Log.e(TAG, "", e);
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
}
}
}
其他几个类以供参考
public class Activity01 extends Activity{
/* 取得默认的蓝牙适配器 */
private BluetoothAdapter _bluetooth = BluetoothAdapter.getDefaultAdapter();
/* 请求打开蓝牙 */
private static final int REQUEST_ENABLE = 0x1;
/* 请求能够被搜索 */
private static final int REQUEST_DISCOVERABLE = 0x2;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
/* 开启蓝牙 */
public void onEnableButtonClicked(View view){
// 用户请求打开蓝牙
//Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
//startActivityForResult(enabler, REQUEST_ENABLE);
//打开蓝牙
_bluetooth.enable();
}
/* 关闭蓝牙 */
public void onDisableButtonClicked(View view){
_bluetooth.disable();
}
/* 使设备能够被搜索 */
public void onMakeDiscoverableButtonClicked(View view){
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
startActivityForResult(enabler, REQUEST_DISCOVERABLE);
}
/* 开始搜索 */
public void onStartDiscoveryButtonClicked(View view){
Intent enabler = new Intent(this, DiscoveryActivity.class);
startActivity(enabler);
}
/* 客户端 */
public void onOpenClientSocketButtonClicked(View view){
Intent enabler = new Intent(this, ClientSocketActivity.class);
startActivity(enabler);
}
/* 服务端 */
public void onOpenServerSocketButtonClicked(View view){
Intent enabler = new Intent(this, ServerSocketActivity.class);
startActivity(enabler);
}
/* OBEX服务器 */
public void onOpenOBEXServerSocketButtonClicked(View view){
Intent enabler = new Intent(this, OBEXActivity.class);
startActivity(enabler);
}
}
public class OBEXActivity extends Activity{
private static final String TAG = "@MainActivity";
private Handler _handler = new Handler();
private BluetoothServerSocket _server;
private BluetoothSocket _socket;
private static final int OBEX_CONNECT = 0x80;
private static final int OBEX_DISCONNECT = 0x81;
private static final int OBEX_PUT = 0x02;
private static final int OBEX_PUT_END = 0x82;
private static final int OBEX_RESPONSE_OK = 0xa0;
private static final int OBEX_RESPONSE_CONTINUE = 0x90;
private static final int BIT_MASK = 0x000000ff;
Thread t = new Thread() {
public void run(){
try{
_server = BluetoothAdapter.getDefaultAdapter().listenUsingRfcommWithServiceRecord("OBEX", null);
new Thread() {
public void run(){
Log.d("@Rfcom", "begin close");
try{
_socket.close();
}catch (IOException e){
Log.e(TAG, "", e);
}
Log.d("@Rfcom", "end close");
};
}.start();
_socket = _server.accept();
reader.start();
Log.d(TAG, "shutdown thread");
}catch (IOException e){
e.printStackTrace();
}
};
};
Thread reader = new Thread() {
public void run(){
try{
Log.d(TAG, "getting inputstream");
InputStream inputStream = _socket.getInputStream();
OutputStream outputStream = _socket.getOutputStream();
Log.d(TAG, "got inputstream");
int read = -1;
byte[] bytes = new byte[2048];
ByteArrayOutputStream baos = new ByteArrayOutputStream(bytes.length);
while ((read = inputStream.read(bytes)) != -1){
baos.write(bytes, 0, read);
byte[] req = baos.toByteArray();
int op = req[0] & BIT_MASK;
Log.d(TAG, "read:" + Arrays.toString(req));
Log.d(TAG, "op:" + Integer.toHexString(op));
switch (op){
case OBEX_CONNECT:
outputStream.write(new byte[] { (byte) OBEX_RESPONSE_OK, 0, 7, 16, 0, 4, 0 });
break;
case OBEX_DISCONNECT:
outputStream.write(new byte[] { (byte) OBEX_RESPONSE_OK, 0, 3, 0 });
break;
case OBEX_PUT:
outputStream.write(new byte[] { (byte) OBEX_RESPONSE_CONTINUE, 0, 3, 0 });
break;
case OBEX_PUT_END:
outputStream.write(new byte[] { (byte) OBEX_RESPONSE_OK, 0, 3, 0 });
break;
default:
outputStream.write(new byte[] { (byte) OBEX_RESPONSE_OK, 0, 3, 0 });
}
Log.d(TAG, new String(baos.toByteArray(), "utf-8"));
baos = new ByteArrayOutputStream(bytes.length);
}
Log.d(TAG, new String(baos.toByteArray(), "utf-8"));
}catch (IOException e){
e.printStackTrace();
}
};
};
private Thread put = new Thread() {
public void run(){
};
};
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.obex_server_socket);
t.start();
}
protected void onActivityResult(int requestCode, int resultCode, Intent data){
Log.d(TAG, data.getData().toString());
switch (requestCode){
case (1):
if (resultCode == Activity.RESULT_OK){
Uri contactData = data.getData();
Cursor c = managedQuery(contactData, null, null, null, null);
for (; c.moveToNext();){
Log.d(TAG, "c1---------------------------------------");
dump(c);
Uri uri = Uri.withAppendedPath(data.getData(), ContactsContract.Contacts.Photo.CONTENT_DIRECTORY);
Cursor c2 = managedQuery(uri, null, null, null, null);
for (; c2.moveToNext();){
Log.d(TAG, "c2---------------------------------------");
dump(c2);
}
}
}
break;
}
}
private void dump(Cursor c){
for (int i = 0, size = c.getColumnCount(); i < size; ++i){
String col = c.getColumnName(i);
String s = c.getString(i);
Log.d(TAG, col + "=" + s);
}
}
}
abstract class SamplesUtils{
public static void indeterminate(Context context, Handler handler, String message, final Runnable runnable, OnDismissListener dismissListener){
try{
indeterminateInternal(context, handler, message, runnable, dismissListener, true);
}catch (Exception e){
; // nop.
}
}
public static void indeterminate(Context context, Handler handler, String message, final Runnable runnable, OnDismissListener dismissListener,boolean cancelable){
try{
indeterminateInternal(context, handler, message, runnable, dismissListener, cancelable);
}catch (Exception e){
; // nop.
}
}
private static ProgressDialog createProgressDialog(Context context, String message){
ProgressDialog dialog = new ProgressDialog(context);
dialog.setIndeterminate(false);
dialog.setMessage(message);
return dialog;
}
private static void indeterminateInternal(Context context, final Handler handler, String message, final Runnable runnable,
OnDismissListener dismissListener, boolean cancelable){
final ProgressDialog dialog = createProgressDialog(context, message);
dialog.setCancelable(cancelable);
if (dismissListener != null){
dialog.setOnDismissListener(dismissListener);
}
dialog.show();
new Thread(){
@Override
public void run(){
runnable.run();
handler.post(new Runnable() {
public void run(){
try{
dialog.dismiss();
}catch (Exception e){
; // nop.
}
}
});
};
}.start();
}
}
discovery.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView
android:id="@+id/android:list"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
<!--
<TextView
android:id="@+id/android:empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="no device found" />
-->
</LinearLayout>
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip">
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="打开蓝牙"
android:onClick="onEnableButtonClicked" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="关闭蓝牙"
android:onClick="onDisableButtonClicked" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="允许搜索"
android:onClick="onMakeDiscoverableButtonClicked" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="开始搜索"
android:onClick="onStartDiscoveryButtonClicked" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="客户端"
android:onClick="onOpenClientSocketButtonClicked" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="服务器端"
android:onClick="onOpenServerSocketButtonClicked" />
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="OBEX服务器"
android:onClick="onOpenOBEXServerSocketButtonClicked" />
</LinearLayout>
server_socket.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip">
<Button
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Stop server"
android:onClick="onButtonClicked" />
<ListView
android:id="@+id/android:list"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
obex_server_socket.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip">
</LinearLayout>
client_socket.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="10dip">
</LinearLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.yarin.android.Examples_08_09"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".Activity01"android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".DiscoveryActivity"android:theme="@style/Theme.Transparent">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".ClientSocketActivity"android:theme="@style/Theme.Transparent">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".ServerSocketActivity"android:theme="@style/Theme.Transparent">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name=".OBEXActivity"android:theme="@style/Theme.Transparent">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
<uses-sdk android:minSdkVersion="5" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.READ_CONTACTS"/>
</manifest>