Android无线网络技术——Bluetooth&WiFi编程(1501211015 杨超)
摘要 :自2008年10月发布第一部Android智能手机以来,其市场份额逐年提升,到2013年其全球市场占有率已达到78.1%,发展到当下Android设备已经成为应用最广泛的智能设备,其在电视、数码相机、游戏机、智能家居等领域也有非常多的应用。这些智能设备在使用过程中通常不可避免的需要进行网络互联,与Internet的连接、与其他设备之间的连接等。Android提供了多种交互方式的支持,包括Bluetooth、NFC、WiFi-Direct、USB和SIP,本文主要分析了Android在利用无线网络技术进行设备交互的特点和基本开发流程,分为AndroidBluetooth和WiFi两大部分。前者主要针对短距离内设备间的直接交互,后者可以通过互联网与远端设备进行交互,实际是网络通信方式的一种。
关键字:Android 蓝牙,WiFi,网络通信
一、Android Bluetooth通信
1.1、Bluetooth的功能与特性
安卓平台提供对蓝牙通讯栈的支持,它允许设备和其他的蓝牙设备进行无线传输数据。 应用程序层通过调用Android Bluetooth 相关的API来调用蓝牙相关功能。这些API使程序能够无线连接蓝牙设备,建立通信连接并进行信息传递,并拥有P2P或多端无线连接的特性。Adnroid Bluetooth API主要提供的功能有:扫描其他蓝牙设备、为可配对蓝牙设备查询蓝牙适配器、建立RFCOMM通道、通过服务搜索来连接其他设备、与其他设备进行数据传输、管理多个连接。
1.2、Bluetooth开发基本流程以及主要的类和接口
在进行蓝牙通信时其基本的也是关键的步骤主要有5步:
1、 开启蓝牙
2、 查找附近已配对或可用的设备
3、 创建通信连接
4、 获取输入输出流并读取或写入数据
5、 关闭连接、释放资源
常用的类和接口:
BluetoothAdapter类
表示本地蓝牙适配器,主要用来对蓝牙进行基本操作,它是所有蓝牙交互操作的入口,如搜索其他的蓝牙设备、查询可配对的设备集合、根据已知的MAC地址来初始化一个BluetoothDevice实例对象、创建一个BluetoothServerSocket类以监听其它设备对本机的连接请求等。
获取BluetoothAdapter实例,在Android4、4.2.2(对应的API级别为17)及之前的版本使用getDefaultAdapter()方法获取,在Android4.3(对应的API级别为18)及其以后的版本中可以使用getSystemService()和BLUETOOTH_SERVICE方式获取。该类常用的方法如下表:
方法名 | 功能 |
---|---|
BluetoothAdapter getDefaultAdapter() | 获取本地蓝牙适配器 |
boolean startDiscovery() | 开始搜索附近的蓝牙设备 |
Set<bluetoothdevice> getBondedDevice() | 获取已配过对的蓝牙设备集合 |
BluetoothServiceSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid) | 建立监听请求的Socket(用作服务端) |
BluetoothDevice getRemoteDevice() | 获取BluetoothDevice对象 |
BluetoothDevice类
该类表示一个远程的蓝牙设备,该对象包含了对蓝牙硬件的基本信息以及对蓝牙设备的操作,对于基本信息而言,这部分是硬件固有的属性,如MAC地址,其提供了外部访问的接口,另外还封装了一些逻辑操作,如建立通信连接、获取绑定状态等,这些都是与使用过程相关的信息。其提供的常用方法如下表:
方法名 | 功能 |
---|---|
BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) | 创建一个蓝牙Socket通信连接。 |
Int getBondState() | 获取设备的绑定状态 |
String getName() | 获取设备的名称 |
String getAddress() | 获取设备的硬件地址 |
BluetoothSocket类
与TCP通信的Socket类似,该类表示了蓝牙通信的逻辑连接,通过该类实现数据的读取与写入,控制通信过程。其提供的方法如下表:
方法名 | 功能 |
---|---|
BluetoothDevice getRemoteDevice() | 获取待连接或已连接的蓝牙设备 |
boolean isConnected() | 判断Socket的连接状态 |
InputStream getInputStream() | 获取与Socket关联的输入流 |
OutputStream getOutputStream() | 获取与Socket关联的输出流 |
Void connect() | 尝试连接一个远程的蓝牙设备 |
Void close() | 关闭连接并释放资源 |
BluetoothServerSocket类
该类主要用于当设备作为服务端时的操作,服务端一般会被动的等待来自客户端的连接或服务请求,因此其功能与提供的方法也非常简单,如下表:
方法名 | 功能 |
---|---|
BluetoothSocket accept(int timeout) | 等待建立连接,知道超过timeout规定的时间 |
BluetoothSocket accept() | 一直等待,直到建立连接 |
Void close() | 关闭连接并释放资源 |
上述介绍了Android Bluetooth中常用的几个类,除了这些还有许多其他的类或接口,如BluetoothClass,其描述了蓝牙设备的通用特性和功能;BluetoothProfile,用于定义设备间基于蓝牙的通信规范,目的是保持不同蓝牙设备之间的兼容,基本的规范有GAP、SDAP、SPP和GOEP;BluetoothHeadset,与头戴式蓝牙设备如蓝牙耳机相关的类;BluetoothHealth,用于定义蓝牙健康设备有关的类;BluetoothA2dp、BluetoothHealthCallback、BluetoothHealthAppConfiguration、BluetoothProfile.ServiceListener等。
1.3、蓝牙开发实例
本实例实现了两部Android手机利用蓝牙互相发送消息的功能。 开发客户端应用: 1、在AndroidManifest.xml中申请蓝牙相关操作权限
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH" />
2、请求用户开启蓝牙设备
if(!bluetoothAdapter.isEnabled()){
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
intent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION,600);
startActivity(intent);
}
如下图:
在此之前同过BluetoothAdapter.getDefaultAdapter()方法获取蓝牙适配器对象
3、搜索附近可用否设备 bluetoothAdapter.startDiscovery()
如下图:
可以看到搜索到了四个蓝牙设备
4、连接设备,连接设备时需要确保搜索过程已经结束,否则会出现异常。 另外利用createRfcommSocketToServiceRecord(UUID)的方式可能会连接异常,可以使用Method m = device.getClass().getMethod("createRfcommSocket", new Class[] {int.class}); bluetoothSocket = (BluetoothSocket) m.invoke(device, Integer.valueOf(1)); 的方式获取bluetoothSocket对象。
protected void connect(final BluetoothDevice device){
new Thread(){
@Override
public void run() {
super.run();
System.out.println("连接设备...");
bluetoothAdapter.cancelDiscovery();
Message message = new Message();
try {
bluetoothSocket = device.createRfcommSocketToServiceRecord(UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
bluetoothSocket.connect();
message.obj = device.getName();
message.what = 1;
}catch (Exception e) {
message.what = 0;
System.out.println("连接失败");
e.printStackTrace();
}
handler.sendMessage(message);
}
}.start();
}
5、发送信息,其数据的传递是通过字节流的方式进行的。
try {
outputStream = bluetoothSocket.getOutputStream();
byte[] buf = str.getBytes();
outputStream.write(buf);
outputStream.close();
}catch (Exception e){
e.printStackTrace();
}
开发服务端应用: 与客户端一样也需要申请蓝牙相关的权限、开启蓝牙设备等。不同的是在服务端需要监听来自客户端的请求。主要代码如下:
new Thread(){
@Override
public void run() {
super.run();
System.out.println("等待设备连接");
if(bluetoothAdapter!=null){
Message message = new Message();
try {
bluetoothServerSocket = bluetoothAdapter.listenUsingRfcommWithServiceRecord("BluetoothChat",UUID.fromString("00001101-0000-1000-8000-00805F9B34FB"));
bluetoothSocket = bluetoothServerSocket.accept();
bluetoothServerSocket.close();
if(bluetoothSocket!=null){
message.what = 1;
}
}catch (Exception e){
message.what = 0;
isConnected = false;
e.printStackTrace();
}finally {
handler.sendMessage(message);
}
}
}
}.start();
由于bluetoothServerSocket的accept方法会阻塞主线程,因此需要另外开辟新的线程来处理来自客户端的连接请求,在accept方法成功返回后,会得到一个与客户端连接的bluetoothSocket对象,通过该对象可以获取客户端传递过来的数据,也可以通过该对象想客户端发送数据,这样就实现了双方的互相通信。主要代码如下:
try {
String name = bluetoothSocket.getRemoteDevice().getName();
System.out.println(name+"设备连接");
tv_target.setText(name);
byte[] buf = new byte[1024];
inputStream = bluetoothSocket.getInputStream();
isConnected = true;
inputStream.read(buf);
String s = new String(buf);
System.out.println("服务端接收到:" + s);
String content = tv_content.getText().toString();
tv_content.setText(content + "\n" + s);
inputStream.close();
}catch (Exception e){
e.printStackTrace();
}
上述代码实现了简单的字符串数据传递,对于大容量的二进制数据其处理方式大致与此类似。需要注意的是,由于蓝牙版本的不同,也可能导致在建立连接时出现各种异常,因此,为了应用的通用性需要做更多的适配工作。
1.3、Bluetooth开发小结
从蓝牙的应用上看,现在市面上绝大部分Android设备都具有蓝牙模块,由于其通信距离的限制,对其实际的应用场景不是很多,在一些特定的场景,如健康设备、短距离互联才体现其应用,关于利用蓝牙进行应用程序相关开发,总体不是特别复杂,重点在于不同设备、不同版本的的蓝牙设备之间建立连接的过程,一旦成功建立连接,其信息传递过程还是相对比较容易的。
二、Android WIFI通信
2.1、WiFi的功能与特性
WiFi是一种可以将个人电脑、手持设备(如pad、手机)等终端以无线方式互相连接的技术,相对于有线网络,其具有便携特性,极大的简化的设备的设计,方便的人们的生活。常见的无线网络有无线路由器实现,如果该无线路由器连接了一条ADSL线路或者别的上网线路,则又被称为热点。
2.2、WiFi开发基本流程以及主要的类和接口
利用WiFi进行通信与直接进行网络通信没有太大的区别,其也是网络通信中的一种,相关的类集中在android.net.wifi包中,Android提供了许多针对WiFi通信的工具类,用于管理WiFi、传输信息等。
基于Android wifi的应用程序开发流程与网络程序开发流程相似,其支持B/S与C/S两种网络架构,即也支持基于Http协议和基于Socket的开发方式。Http方式是一种应答模式,即一方发起请求另一方做出反馈,通信过程中需要遵循一定的规范,Socket方式是比Http方式更加底层网络通信方式,其通信是在一条全双工的逻辑链路上进行通信,双方既可以作为客户端也可以作为服务端,其具有更高的通信效率和灵活性。Socket通信模型如下图:
基于Http协议的开发流程:
1、 利用HttpURLConnection连接到指定的URL
2、 向服务器发送请求参数,即Http请求头相关参数的设定
3、 向服务器发起请求或发送数据
4、 解析服务器返回的数据
基于Socket的开发流程:
1、 创建Socket连接
2、 传输数据
3、 关闭连接
常用的关于WiFi的工具类
ScanResult类: 描述检测到的WiFi接入点信息,包含网络名称、接入点地址、频率等信息。其只包含一个描述这些信息的toString方法。
WifiConfiguration类:主要提供了WiFi配置信息的访问接口,如安全协议、密码管理模式、认证方式等。
WifiInfo类:用于描述已经建立连接的或正在建立连接的WiFi状态信息。如IP地址、MAC地址、网络id等。
WifiManager类:提供针对WiFi连接的基本操作,管理WiFi连接。 常用的方法如下表:
方法名 | 功能 |
---|---|
WifiManager.WifiLock createWifiLock() | 创建保持WiFi连接一直持续的锁 |
boolean disconnect() | 断开wifi连接 |
WifiInfo getConnectionInfo() | 获取连接状态信息 |
List<ScanResult> getScanResults() | 获取扫描结果集 |
Int getWifiState() | 获取wifi启动状态 |
boolean startScan() | 开始进行接入点扫描 |
2.3、WiFi开发实例
本实例实现了通过Android手机控制电脑播放PPT的功能。
基本思路:分为服务端与App端,App端主要向服务端发送操作命令,同过Socket进行通信,在服务端利用java提供Robot对象模拟对机器的操作,如键盘按压,鼠标点击,模拟PPT快捷键实现对PPT的控制,PPT的打开快捷键为Shift+F5,上一张和下一张分别为键盘左右键,退出为ESC键。
主要代码:
App端
申请网络权限
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
主要代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private Button btn_open,btn_close,btn_last,btn_next;
private Socket socket;
private ObjectOutputStream objectOutputStream;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btn_open = (Button)findViewById(R.id.btn_open);
btn_close = (Button)findViewById(R.id.btn_close);
btn_last = (Button)findViewById(R.id.btn_last);
btn_next = (Button)findViewById(R.id.btn_next);
btn_open.setOnClickListener(this);
btn_close.setOnClickListener(this);
btn_last.setOnClickListener(this);
btn_next.setOnClickListener(this);
new Thread(){
@Override
public void run() {
super.run();
try {
socket = new Socket(InetAddress.getByName("192.168.14.170"),8888);
objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
}catch(Exception e) {
e.printStackTrace();
}
}
}.start();
}
@Override
public void onClick(View v){
try {
switch (v.getId()){
case R.id.btn_open:
objectOutputStream.writeObject(new String("open"));
break;
case R.id.btn_close:
objectOutputStream.writeObject(new String("close"));
break;
case R.id.btn_last:
objectOutputStream.writeObject(new String("last"));
break;
case R.id.btn_next:
objectOutputStream.writeObject(new String("next"));
break;
default:break;
}
}catch (Exception e){
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
try {
objectOutputStream.writeObject(new String("quit"));
super.onDestroy();
if(objectOutputStream!=null)objectOutputStream.close();
if(socket!=null)socket.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
可以看到在App端,通过传递String的方式传递操作命令,在测试时手机与PC在同一个局域网内,但如有需要,也可以改为远端设备的IP地址,通信的端口设置为8888. 布局文件内容:
<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:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<Button
android:id="@+id/btn_open"
android:text="开始"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_close"
android:text="结束"
android:layout_centerInParent="true"
android:layout_below="@+id/btn_open"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
<Button
android:id="@+id/btn_last"
android:text="上一张"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/btn_next"
android:text="下一张"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</RelativeLayout>
页面布局仅包含四个表示对PPT控制的基本操作。
服务端的代码为:
public class PPTControler {
private static ObjectInputStream inputStream;
public static void main(String[] args) throws IOException,ClassNotFoundException, AWTException, InterruptedException{
ServerSocket sSocket = new ServerSocket(8888);
System.out.println("等待连接...");
Robot robot = new Robot();
Socket socket = sSocket.accept();
System.out.println("接收到连接"+socket.getInetAddress().getHostAddress());
inputStream = new ObjectInputStream(socket.getInputStream());
while(true){
String opt = (String)inputStream.readObject();
if(opt.equals("open")){
robot.keyPress(KeyEvent.VK_SHIFT);
Thread.sleep(20);
robot.keyPress(KeyEvent.VK_F5);
Thread.sleep(10);
robot.keyRelease(KeyEvent.VK_F5);
robot.keyRelease(KeyEvent.VK_SHIFT);
Thread.sleep(10);
}else if(opt.equals("close")){
robot.keyPress(KeyEvent.VK_ESCAPE);
Thread.sleep(10);
robot.keyPress(KeyEvent.VK_ESCAPE);
Thread.sleep(10);
}else if(opt.equals("last")) {
robot.keyPress(KeyEvent.VK_LEFT);
Thread.sleep(10);
robot.keyRelease(KeyEvent.VK_LEFT);
Thread.sleep(10);
}else if (opt.equals("next")) {
robot.keyPress(KeyEvent.VK_RIGHT);
Thread.sleep(10);
robot.keyRelease(KeyEvent.VK_RIGHT);
Thread.sleep(10);
}else if(opt.equals("quit")) {
break;
}else {
System.out.println("无法识别的命令");
}
}
inputStream.close();
socket.close();
sSocket.close();
}
}
先运行服务端的程序,起开始处于监听状态,当打开App后,建立与PC的连接,然后在操作App的过程中将对应操作字符串传递到服务端,服务端获取命令后,根据操作类型模拟相应的操作。在服务端的代码中可以看到,模拟键盘操作时有许多的Thread.sleep(),这是模拟了键盘按压过程的时间间隔与按压与释放的时间间隔。
运行效果截图:
开启服务端程序
打开App时
服务端
App端
打开一个PPT文件
在App端点击开始按钮
在App端点击下一张
其他操作过程类似,可以实现对PPT的播放控制。
2.3、WiFi开发小结
WiFi是网络接入方式的一种,在我们日常生活中其应用也十分广泛,属于无线网络技术的一种,WiFi开发实际也是网络开发的一种,其开发流程比较固定,相对于蓝牙,其支持范围更广的通信,支持多种网络协议的通信。
三、总结和收获
通过对Android的无线网络技术的学习,基本了解了Android在利用无线网络技术进行通信的基本特点,以及相关的概念和实际开发问题。
进过对Android蓝牙和WiFi的相关实验操作,了解到了许多在实际开发过程中的问题,如蓝牙设备之间互相连接的问题,网络数据传递的问题等。对于实现手机控制PPT,实际是网络通信的过程,然后有PC端转化成相应的操作命令,利用java提供的Robot对象,还可以实现其他的一些控制,如定时关机、传输文件、远程桌面控制等。
四、感谢
非常感谢张老师这一个学期的辛勤指导,在此过程中让我收获颇丰,通过课程的学习以及课后大项目的练习,我了解了Android用于的基本架构,掌握了许多Android开发技巧,对Android在实际生产过程中的应用也有了一定的认识,具备了一定的开发能力,同时也对Android开发产生了较为浓烈的兴趣。非常希望有机会能再次学习类似实践型的课程。
参考文献
1 扶松柏;Android开发从入门到精通[D];兵器工业出版社;2012年
2 郭霖;第一行代码Android[D];人民邮电出版社;2014年
3 John Wiley & Sons, Inc., Beginning Android™ 4 Application Development[D];2012