30、Android 蓝牙、网络与 Wi-Fi 开发全解析

Android 蓝牙、网络与 Wi-Fi 开发全解析

1. 移动电话通信基础概述

移动电话中的电话通信栈是一项基础技术。虽然并非所有 Android 设备都提供电话 API,但具备这些 API 的设备在人际通信方面功能十分强大。借助电话 API,我们可以直接发起通话或通过拨号器拨号,还能读取和监控手机、网络、数据以及 SIM 卡的状态。

同时,Android 允许我们利用 SMS 来开发设备间数据交换的应用,实现短信的收发。并且,我们可以使用 Intents 让手机上已有的 SMS 应用代我们发送 SMS 和 MMS 消息。

2. 蓝牙技术简介

蓝牙是一种专为短距离、低带宽的点对点通信设计的通信协议。自 Android 2.0(SDK API 级别 5)起,Android 开始支持蓝牙库,但并非所有 Android 设备都配备蓝牙硬件。从 Android 2.1 开始,仅支持加密通信,这意味着只能在配对设备之间建立连接。

在 Android 中,蓝牙设备和连接由以下几个重要类处理:
- BluetoothAdapter :代表本地蓝牙设备,即运行应用的 Android 设备。
- BluetoothDevice :表示每个我们想要与之通信的远程设备。
- BluetoothSocket :通过在远程蓝牙设备对象上调用 createRfcommSocketToServiceRecord 方法创建,用于向远程设备发起连接请求并启动通信。
- BluetoothServerSocket :在本地蓝牙适配器上使用 listenUsingRfcommWithServiceRecord 方法创建,用于监听来自远程设备蓝牙套接字的传入连接请求。

3. 访问本地蓝牙设备适配器

要访问主机设备上的默认蓝牙适配器,可以调用 getDefaultAdapter 方法,示例代码如下:

BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();

若要读取本地蓝牙适配器的属性、启动发现过程或查找已配对设备,需要在清单文件中添加 BLUETOOTH 权限;若要修改本地设备的属性,则还需要 BLUETOOTH_ADMIN 权限:

<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
4. 管理蓝牙属性和状态

蓝牙适配器提供了读取和设置本地蓝牙硬件属性的方法。不过,只有当蓝牙适配器处于开启状态(即设备状态为已启用)时,才能读取和更改这些属性;若设备关闭,这些方法将返回 null

若蓝牙适配器已开启,并且在清单文件中包含了 BLUETOOTH 权限,我们可以访问蓝牙适配器的友好名称(用户可设置的用于识别特定设备的任意字符串)和硬件地址,示例代码如下:

BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
String toastText;
if (bluetooth.isEnabled()) {
    String address = bluetooth.getAddress();
    String name = bluetooth.getName();
    toastText = name + " : " + address;
} else {
    toastText = "Bluetooth is not enabled";
}
Toast.makeText(this, toastText, Toast.LENGTH_LONG).show();

若还拥有 BLUETOOTH_ADMIN 权限,则可以使用 setName 方法更改蓝牙适配器的友好名称:

bluetooth.setName("Blackfang");

要获取当前蓝牙适配器状态的详细描述,可以使用 getState 方法,它将返回以下 BluetoothAdapter 常量之一:
- STATE_TURNING_ON
- STATE_ON
- STATE_TURNING_OFF
- STATE_OFF

默认情况下,蓝牙适配器是关闭的。为了节省电池电量和优化安全性,大多数用户在不使用蓝牙时会保持其禁用状态。要启用蓝牙适配器,可以使用 ACTION_REQUEST_ENABLE 常量作为 startActivityForResult 的动作字符串来启动一个系统子 Activity:

String enableBT = BluetoothAdapter.ACTION_REQUEST_ENABLE;
startActivityForResult(new Intent(enableBT), 0);

也可以直接使用 enable disable 方法来开启和关闭蓝牙适配器,但这需要在清单文件中包含 BLUETOOTH_ADMIN 权限。不过,这种方式应仅在必要时使用,并且要确保通知用户我们正在手动更改其蓝牙适配器状态。

启用和禁用蓝牙适配器是耗时的异步操作,我们的应用应该注册一个广播接收器来监听 ACTION_STATE_CHANGED ,示例代码如下:

BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter();
BroadcastReceiver bluetoothState = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String prevStateExtra = BluetoothAdapter.EXTRA_PREVIOUS_STATE;
        String stateExtra = BluetoothAdapter.EXTRA_STATE;
        int state = intent.getIntExtra(stateExtra, -1);
        int previousState = intent.getIntExtra(prevStateExtra, -1);
        String tt = "";
        switch (state) {
            case (BluetoothAdapter.STATE_TURNING_ON): {
                tt = "Bluetooth turning on";
                break;
            }
            case (BluetoothAdapter.STATE_ON): {
                tt = "Bluetooth on";
                unregisterReceiver(this);
                break;
            }
            case (BluetoothAdapter.STATE_TURNING_OFF): {
                tt = "Bluetooth turning off";
                break;
            }
            case (BluetoothAdapter.STATE_OFF): {
                tt = "Bluetooth off";
                break;
            }
            default:
                break;
        }
        Toast.makeText(context, tt, Toast.LENGTH_LONG).show();
    }
};
if (!bluetooth.isEnabled()) {
    String actionStateChanged = BluetoothAdapter.ACTION_STATE_CHANGED;
    String actionRequestEnable = BluetoothAdapter.ACTION_REQUEST_ENABLE;
    registerReceiver(bluetoothState, new IntentFilter(actionStateChanged));
    startActivityForResult(new Intent(actionRequestEnable), 0);
}
5. 设备可发现性和远程设备发现

两个设备相互找到对方以建立连接的过程称为发现。在建立用于通信的蓝牙套接字之前,本地蓝牙适配器必须与远程设备配对,而配对的前提是双方先发现彼此。

5.1 管理设备可发现性

为了让远程 Android 设备在发现扫描中找到本地蓝牙适配器,我们需要确保其可被发现。蓝牙适配器的可发现性由其扫描模式指示,通过调用 getScanMode 方法可以获取扫描模式,它将返回以下 BluetoothAdapter 常量之一:
- SCAN_MODE_CONNECTABLE_DISCOVERABLE :查询扫描和页面扫描均启用,意味着该设备可被任何执行发现扫描的蓝牙设备发现。
- SCAN_MODE_CONNECTABLE :页面扫描启用,但查询扫描未启用。这意味着之前已连接并配对的设备可以在发现过程中找到本地设备,但新设备无法找到。
- SCAN_MODE_NONE :可发现性关闭,在发现过程中没有远程设备可以找到本地适配器。

出于隐私原因,Android 设备默认禁用可发现性。要开启发现功能,需要获得用户的明确许可,通过使用 ACTION_REQUEST_DISCOVERABLE 动作启动一个新 Activity 来实现:

String aDiscoverable = BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
startActivityForResult(new Intent(aDiscoverable), DISCOVERY_REQUEST);

默认情况下,可发现性将启用两分钟。我们可以通过在启动 Intent 中添加 EXTRA_DISCOVERABLE_DURATION 额外参数来修改此设置,指定可发现性持续的秒数。

要了解用户是否允许或拒绝了发现请求,可以重写 onActivityResult 处理程序,示例代码如下:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == DISCOVERY_REQUEST) {
        boolean isDiscoverable = resultCode > 0;
        int discoverableDuration = resultCode;
    }
}

另外,我们也可以通过接收 ACTION_SCAN_MODE_CHANGED 广播动作来监控可发现性的变化,示例代码如下:

registerReceiver(new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String prevScanMode = BluetoothAdapter.EXTRA_PREVIOUS_SCAN_MODE;
        String scanMode = BluetoothAdapter.EXTRA_SCAN_MODE;
        int scanMode = intent.getIntExtra(scanMode, -1);
        int prevMode = intent.getIntExtra(prevScanMode, -1);
    }
}, new IntentFilter(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED));
5.2 发现远程设备

我们可以通过本地适配器发起发现过程来查找附近的可发现设备。发现过程可能需要一些时间才能完成(最长可达 12 秒),在此期间,蓝牙适配器的通信性能会严重下降。因此,我们可以使用 isDiscovering 方法检查本地蓝牙适配器是否正在进行发现扫描。

要启动发现过程,可以调用 startDiscovery 方法;若要取消正在进行的发现过程,可以调用 cancelDiscovery 方法:

bluetooth.startDiscovery();
bluetooth.cancelDiscovery();

发现过程是异步的,Android 使用广播 Intents 来通知发现的开始和结束以及扫描过程中发现的远程设备。我们可以创建广播接收器来监听 ACTION_DISCOVERY_STARTED ACTION_DISCOVERY_FINISHED 广播 Intents,示例代码如下:

BroadcastReceiver discoveryMonitor = new BroadcastReceiver() {
    String dStarted = BluetoothAdapter.ACTION_DISCOVERY_STARTED;
    String dFinished = BluetoothAdapter.ACTION_DISCOVERY_FINISHED;
    @Override
    public void onReceive(Context context, Intent intent) {
        if (dStarted.equals(intent.getAction())) {
            // 发现已开始
            Toast.makeText(context, "Discovery Started... ", Toast.LENGTH_SHORT).show();
        } else if (dFinished.equals(intent.getAction())) {
            // 发现已完成
            Toast.makeText(context, "Discovery Completed... ", Toast.LENGTH_SHORT).show();
        }
    }
};
registerReceiver(discoveryMonitor, new IntentFilter(dStarted));
registerReceiver(discoveryMonitor, new IntentFilter(dFinished));

发现的蓝牙设备通过 ACTION_FOUND 广播动作以广播 Intents 的形式返回。每个广播 Intent 包含远程设备的名称(存储在 BluetoothDevice.EXTRA_NAME 额外参数中)和一个不可变的远程蓝牙设备表示(作为 BluetoothDevice 可打包对象存储在 BluetoothDevice.EXTRA_DEVICE 额外参数中),示例代码如下:

BroadcastReceiver discoveryResult = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String remoteDeviceName = intent.getStringExtra(BluetoothDevice.EXTRA_NAME);
        BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        Toast.makeText(context, "Discovered: " + remoteDeviceName, Toast.LENGTH_SHORT).show();
        // TODO 对远程蓝牙设备进行操作
    }
};
registerReceiver(discoveryResult, new IntentFilter(BluetoothDevice.ACTION_FOUND));
if (!bluetooth.isDiscovering()) {
    bluetooth.startDiscovery();
}
6. 蓝牙通信

蓝牙通信 API 是围绕 RFCOMM(蓝牙射频通信协议)构建的,RFCOMM 支持在逻辑链路控制和适配协议(L2CAP)层上进行 RS232 串行通信。实际上,这为在两个配对的蓝牙设备之间打开通信套接字提供了一种机制。

在应用程序能够在设备之间进行通信之前,它们必须配对。在 Android API 级别 7 时,还没有手动发起本地蓝牙适配器与远程蓝牙设备配对的方法。如果两个设备要配对,用户需要通过蓝牙设置屏幕或在应用程序尝试在两个未配对设备之间连接蓝牙套接字时提示时明确允许。

我们可以使用以下类来建立用于双向通信的 RFCOMM 通信通道:
- BluetoothServerSocket :用于建立监听套接字,以启动设备之间的连接。为了建立握手,一个设备充当服务器,监听并接受传入的连接请求。
- BluetoothSocket :用于创建新的客户端套接字以连接到监听的蓝牙服务器套接字,并在连接建立后由服务器套接字返回。连接建立后,蓝牙套接字在服务器和客户端两侧都用于传输数据流。

6.1 打开蓝牙服务器套接字监听器

蓝牙服务器套接字用于监听来自远程蓝牙设备的蓝牙套接字连接请求。为了使两个蓝牙设备连接,一个设备必须充当服务器(监听并接受传入请求),另一个设备充当客户端(发起连接到服务器的请求)。连接建立后,服务器和主机设备之间的通信通过两端的蓝牙套接字处理。

要监听传入的连接请求,可以在蓝牙适配器上调用 listenUsingRfcommWithServiceRecord 方法,传入一个字符串“名称”来标识服务器和一个 UUID(通用唯一标识符),示例代码如下:

startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE), DISCOVERY_REQUEST);
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == DISCOVERY_REQUEST) {
        boolean isDiscoverable = resultCode > 0;
        int discoverableDuration = resultCode;
        if (isDiscoverable) {
            UUID uuid = UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c666");
            String name = "bluetoothserver";
            final BluetoothServerSocket btserver = bluetooth.listenUsingRfcommWithServiceRecord(name, uuid);
            Thread acceptThread = new Thread(new Runnable() {
                public void run() {
                    try {
                        // 阻塞直到客户端连接建立
                        BluetoothSocket serverSocket = btserver.accept();
                        // TODO 使用服务器套接字传输数据
                    } catch (IOException e) {
                        Log.d("BLUETOOTH", e.getMessage());
                    }
                }
            });
            acceptThread.start();
        }
    }
}

需要注意的是, accept 是一个阻塞操作,因此最好在后台线程中监听传入的连接请求,而不是阻塞 UI 线程直到连接建立。并且,本地蓝牙适配器必须可被发现,远程蓝牙设备才能连接到它。

6.2 选择用于通信的远程蓝牙设备

BluetoothSocket 类用于在客户端设备上从应用程序内部发起与监听的蓝牙服务器套接字的通信通道。通过在 BluetoothDevice 对象上调用 createRfcommSocketToServiceRecord 方法创建客户端蓝牙套接字,该对象代表目标远程服务器设备,该设备应该有一个蓝牙服务器套接字监听连接请求。

要使蓝牙套接字与远程蓝牙设备建立连接,必须满足以下条件:
- 远程设备必须可被发现。
- 远程设备必须使用蓝牙服务器套接字接受连接。
- 本地和远程设备必须配对(或绑定)。如果设备未配对,在发起连接请求时将提示用户进行配对。

获取 BluetoothDevice 对象的方法有多种:
- 使用 startDiscovery 方法并监控 ACTION_FOUND 广播,每个接收到的广播包含一个 BluetoothDevice.EXTRA_DEVICE 额外参数,其中包含发现的蓝牙设备。
- 使用本地蓝牙适配器的 getRemoteDevice 方法,指定要连接的远程蓝牙设备的硬件地址,示例代码如下:

BluetoothDevice device = bluetooth.getRemoteDevice("01:23:77:35:2F:AA");
  • 调用本地蓝牙适配器的 getBondedDevices 方法获取当前配对设备的集合,并查询该集合以确定目标蓝牙设备是否与本地适配器配对,示例代码如下:
Set<BluetoothDevice> bondedDevices = bluetooth.getBondedDevices();
if (bondedDevices.contains(remoteDevice)) {
    // TODO 目标设备已与本地设备配对
}
6.3 打开客户端蓝牙套接字连接

要发起与远程设备的通信通道,可以从代表该设备的 BluetoothDevice 对象创建蓝牙套接字。通过在要连接的蓝牙设备上调用 createRfcommSocketToServiceRecord 方法,传入接受请求的蓝牙服务器套接字的 UUID,示例代码如下:

try {
    BluetoothDevice device = bluetooth.getRemoteDevice("00:23:76:35:2F:AA");
    BluetoothSocket clientSocket = device.createRfcommSocketToServiceRecord(uuid);
    clientSocket.connect();
    // TODO 使用蓝牙套接字传输数据
} catch (IOException e) {
    Log.d("BLUETOOTH", e.getMessage());
}

如果尝试连接到尚未与主机设备配对的蓝牙设备,在 connect 调用完成之前将提示用户接受配对。用户必须在主机和远程设备上都接受配对请求,连接才能建立。

需要注意的是, connect 是一个阻塞操作,因此最好在后台线程中发起连接请求,而不是阻塞 UI 线程直到连接建立。

6.4 使用蓝牙套接字传输数据

连接建立后,客户端和服务器设备上都会有一个蓝牙套接字。从这一点开始,它们之间没有显著区别,我们可以使用两端的蓝牙套接字发送和接收数据。

蓝牙套接字之间的数据传输通过标准的 Java InputStream OutputStream 对象处理,我们可以分别使用蓝牙套接字的 getInputStream getOutputStream 方法获取这些对象。示例代码如下,展示了如何使用输出流发送字符串和使用输入流监听传入字符串:

private void sendMessage(String message) {
    OutputStream outStream;
    try {
        outStream = socket.getOutputStream();
        // 添加停止字符
        byte[] byteArray = (message + " ").getBytes();
        byteArray[byteArray.length - 1] = 0;
        outStream.write(byteArray);
    } catch (IOException e) {
    }
}

private String listenForMessage() {
    String result = "";
    int bufferSize = 1024;
    byte[] buffer = new byte[bufferSize];
    try {
        InputStream instream = socket.getInputStream();
        int bytesRead = -1;
        while (true) {
            bytesRead = instream.read(buffer);
            if (bytesRead != -1) {
                while ((bytesRead == bufferSize) && (buffer[bufferSize - 1] != 0)) {
                    result = result + new String(buffer, 0, bytesRead);
                    bytesRead = instream.read(buffer);
                }
                result = result + new String(buffer, 0, bytesRead - 1);
                return result;
            }
        }
    } catch (IOException e) {
    }
    return result;
}
7. 蓝牙数据传输示例

以下示例使用 Android 蓝牙 API 构建了一个简单的点对点消息系统,该系统可在两个配对的蓝牙设备之间工作。由于 Android 模拟器目前无法用于测试蓝牙功能,因此需要两个物理设备来测试此应用。

7.1 创建项目和配置清单文件

首先,创建一个新的 BluetoothTexting 项目,其中包含一个 BluetoothTexting Activity。修改清单文件以包含 BLUETOOTH BLUETOOTH_ADMIN 权限,示例代码如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.paad.chapter13_bluetoothtexting"
    android:versionCode="1"
    android:versionName="1.0">
    <application
        android:icon="@drawable/icon"
        android:label="@string/app_name">
        <activity
            android:name=".BluetoothTexting"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
    <uses-sdk android:minSdkVersion="5" />
    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
</manifest>
7.2 修改布局资源文件

修改 main.xml 布局资源文件,使其包含一个 ListView 用于显示发现的蓝牙设备,以及两个按钮:一个用于启动服务器套接字监听器,另一个用于发起与监听服务器的连接。同时,包含 TextView EditText 控件,用于读取和写入连接上的消息,示例代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    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/text_message"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:enabled="false" />
    <Button
        android:id="@+id/button_search"
        android:text="Search for listener"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_above="@id/text_message" />
    <Button
        android:id="@+id/button_listen"
        android:text="Listen for connection"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_above="@id/button_search" />
    <ListView
        android:id="@+id/list_discovered"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_above="@id/button_listen"
        android:layout_alignParentTop="true" />
    <TextView
        android:id="@+id/text_messages"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_above="@id/button_listen"
        android:layout_alignParentTop="true"
        android:visibility="gone" />
</RelativeLayout>
7.3 重写 onCreate 方法

重写 BluetoothTexting Activity 的 onCreate 方法,调用一系列存根方法来访问蓝牙设备并连接 UI 控件,示例代码如下:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.UUID;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;

public class BluetoothTexting extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        // 获取蓝牙适配器
        configureBluetooth();
        // 设置发现设备的 ListView
        setupListView();
        // 设置搜索按钮
        setupSearchButton();
        // 设置监听按钮
        setupListenButton();
    }

    private void configureBluetooth() {
    }

    private void setupListenButton() {
    }

    private void setupListView() {
    }

    private void setupSearchButton() {
    }
}
7.4 填充 configureBluetooth 方法

填充 configureBluetooth 存根方法,以访问本地蓝牙适配器并将其存储在字段变量中。同时,创建一个用于存储蓝牙套接字的字段变量,以及一个用于在建立连接时标识应用程序的 UUID,示例代码如下:

private BluetoothAdapter bluetooth;
private BluetoothSocket socket;
private UUID uuid = UUID.fromString("a60f35f0-b93a-11de-8a39-08002009c666");

private void configureBluetooth() {
    bluetooth = BluetoothAdapter.getDefaultAdapter();
}
7.5 创建 switchUI 方法

创建一个新的 switchUI 方法,该方法将在建立连接后调用,以启用用于读取和写入消息的视图,示例代码如下:

private void switchUI() {
    final TextView messageText = (TextView) findViewById(R.id.text_messages);
    final EditText textEntry = (EditText) findViewById(R.id.text_message);
    messageText.setVisibility(View.VISIBLE);
    list.setVisibility(View.GONE);
    textEntry.setEnabled(true);
}
7.6 创建服务器监听器

填充 setupListenButton 存根方法,使监听按钮提示用户启用发现功能。当发现窗口返回时,打开一个蓝牙服务器套接字,在发现持续时间内监听连接请求。连接建立后,调用 switchUI 方法,示例代码如下:

private static int DISCOVERY_REQUEST = 1;

private void setupListenButton() {
    Button listenButton = (Button) findViewById(R.id.button_listen);
    listenButton.setOnClickListener(new OnClickListener() {
        public void onClick(View view) {
            Intent disc = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
            startActivityForResult(disc, DISCOVERY_REQUEST);
        }
    });
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == DISCOVERY_REQUEST) {
        boolean isDiscoverable = resultCode > 0;
        if (isDiscoverable) {
            String name = "bluetoothserver";
            try {
                final BluetoothServerSocket btserver = bluetooth.listenUsingRfcommWithServiceRecord(name, uuid);
                AsyncTask<Integer, Void, BluetoothSocket> acceptThread = new AsyncTask<Integer, Void, BluetoothSocket>() {
                    @Override
                    protected BluetoothSocket doInBackground(Integer... params) {
                        try {
                            socket = btserver.accept(params[0] * 1000);
                            return socket;
                        } catch (IOException e) {
                            Log.d("BLUETOOTH", e.getMessage());
                        }
                        return null;
                    }

                    @Override
                    protected void onPostExecute(BluetoothSocket result) {
                        if (result != null) {
                            switchUI();
                        }
                    }
                };
                acceptThread.execute(resultCode);
            } catch (IOException e) {
                Log.d("BLUETOOTH", e.getMessage());
            }
        }
    }
}
7.7 创建客户端连接代码

创建客户端连接代码,通过执行发现并显示所有可能的设备,为客户端设备提供搜索监听服务器的方法。具体步骤如下:
1. 创建一个字段变量来存储发现的蓝牙设备的数组列表:

private ArrayList<BluetoothDevice> foundDevices;
  1. 填充 setupListView 存根方法,创建一个新的数组适配器,将 ListView 绑定到发现的设备数组:
private ArrayAdapter<BluetoothDevice> aa;
private ListView list;

private void setupListView() {
    aa = new ArrayAdapter<BluetoothDevice>(this, android.R.layout.simple_list_item_1, foundDevices);
    list = (ListView) findViewById(R.id.list_discovered);
    list.setAdapter(aa);
}
  1. 创建一个新的广播接收器,监听蓝牙设备发现广播,将每个发现的设备添加到步骤 1 中创建的发现设备数组中,并通知步骤 2 中创建的数组适配器:
BroadcastReceiver discoveryResult = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        BluetoothDevice remoteDevice = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
        if (bluetooth.getBondedDevices().contains(remoteDevice)) {
            foundDevices.add(remoteDevice);
            aa.notifyDataSetChanged();
        }
    }
};
  1. 完成 setupSearchButton 存根方法,注册上一步的广播接收器并启动发现会话:
private void setupSearchButton() {
    Button searchButton = (Button) findViewById(R.id.button_search);
    searchButton.setOnClickListener(new OnClickListener() {
        public void onClick(View view) {
            registerReceiver(discoveryResult, new IntentFilter(BluetoothDevice.ACTION_FOUND));
            if (!bluetooth.isDiscovering()) {
                foundDevices.clear();
                bluetooth.startDiscovery();
            }
        }
    });
}
7.8 完成连接处理代码

扩展 setupListView 方法,添加一个 onItemClickListener ,尝试异步发起与所选远程蓝牙设备的客户端连接。如果成功,保存创建的套接字引用并调用 switchUI 方法,示例代码如下:

private void setupListView() {
    aa = new ArrayAdapter<BluetoothDevice>(this, android.R.layout.simple_list_item_1, foundDevices);
    list = (ListView) findViewById(R.id.list_discovered);
    list.setAdapter(aa);
    list.setOnItemClickListener(new OnItemClickListener() {
        public void onItemClick(AdapterView<?> arg0, View view, int index, long arg3) {
            AsyncTask<Integer, Void, Void> connectTask = new AsyncTask<Integer, Void, Void>() {
                @Override
                protected Void doInBackground(Integer... params) {
                    try {
                        BluetoothDevice device = foundDevices.get(params[0]);
                        socket = device.createRfcommSocketToServiceRecord(uuid);
                        socket.connect();
                    } catch (IOException e) {
                        Log.d("BLUETOOTH_CLIENT", e.getMessage());
                    }
                    return null;
                }

                @Override
                protected void onPostExecute(Void result) {
                    switchUI();
                }
            };
            connectTask.execute(index);
        }
    });
}
7.9 运行应用程序

如果在两个设备上运行该应用程序,可以在一个设备上点击“Listen for connection”按钮,在另一个设备上点击“Search for listener”按钮。此时, ListView 应会显示范围内的所有配对设备。如果从列表中选择另一个运行此应用程序的 Android 设备,两个设备之间将建立连接。接下来,我们可以使用这个通信通道在设备之间发送简单的文本消息。

7.10 扩展 switchUI 方法

扩展 switchUI 方法,为文本输入 EditText 添加一个新的按键监听器,监听 D-pad 点击事件。当检测到点击时,读取其内容并通过蓝牙通信套接字发送,示例代码如下:

private void switchUI() {
    final TextView messageText = (TextView) findViewById(R.id.text_messages);
    final EditText textEntry = (EditText) findViewById(R.id.text_message);
    messageText.setVisibility(View.VISIBLE);
    list.setVisibility(View.GONE);
    textEntry.setEnabled(true);
    textEntry.setOnKeyListener(new OnKeyListener() {
        public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
            if ((keyEvent.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_DPAD_CENTER)) {
                sendMessage(socket, textEntry.getText().toString());
                textEntry.setText("");
                return true;
            }
            return false;
        }
    });
}

private void sendMessage(BluetoothSocket socket, String msg) {
    OutputStream outStream;
    try {
        outStream = socket.getOutputStream();
        byte[] byteString = (msg + " ").getBytes();
        byteString[byteString.length - 1] = 0;
        outStream.write(byteString);
    } catch (IOException e) {
        Log.d("BLUETOOTH_COMMS", e.getMessage());
    }
}
7.11 创建异步监听器

为了接收消息,我们需要创建一个异步监听器来监控蓝牙套接字的传入消息。具体步骤如下:
1. 创建一个新的 MessagePoster 类,实现 Runnable 接口,接受一个 TextView 和一个消息字符串作为参数,将接收到的消息插入到 TextView 中:

private class MessagePoster implements Runnable {
    private TextView textView;
    private String message;

    public MessagePoster(TextView textView, String message) {
        this.textView = textView;
        this.message = message;
    }

    public void run() {
        textView.setText(message);
    }
}
  1. 创建一个新的 BluetoothSocketListener 类,实现 Runnable 接口,接受一个蓝牙套接字、一个 TextView 和一个 Handler 作为参数。当接收到新消息时,使用上一步创建的 MessagePoster 可运行对象将新消息发布到 TextView 中:
private class BluetoothSocketListener implements Runnable {
    private BluetoothSocket socket;
    private TextView textView;
    private Handler handler;

    public BluetoothSocketListener(BluetoothSocket socket, Handler handler, TextView textView) {
        this.socket = socket;
        this.textView = textView;
        this.handler = handler;
    }

    public void run() {
        int bufferSize = 1024;
        byte[] buffer = new byte[bufferSize];
        try {
            InputStream instream = socket.getInputStream();
            int bytesRead = -1;
            String message = "";
            while (true) {
                message = "";
                bytesRead = instream.read(buffer);
                if (bytesRead != -1) {
                    while ((bytesRead == bufferSize) && (buffer[bufferSize - 1] != 0)) {
                        message = message + new String(buffer, 0, bytesRead);
                        bytesRead = instream.read(buffer);
                    }
                    message = message + new String(buffer, 0, bytesRead - 1);
                    handler.post(new MessagePoster(textView, message));
                }
            }
        } catch (IOException e) {
            Log.d("BLUETOOTH_COMMS", e.getMessage());
        }
    }
}
  1. 最后,再次扩展 switchUI 方法,创建并启动上一步创建的新 BluetoothSocketListener
private Handler handler = new Handler();

private void switchUI() {
    final TextView messageText = (TextView) findViewById(R.id.text_messages);
    final EditText textEntry = (EditText) findViewById(R.id.text_message);
    messageText.setVisibility(View.VISIBLE);
    list.setVisibility(View.GONE);
    textEntry.setEnabled(true);
    textEntry.setOnKeyListener(new OnKeyListener() {
        public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
            if ((keyEvent.getAction() == KeyEvent.ACTION_DOWN) && (keyCode == KeyEvent.KEYCODE_DPAD_CENTER)) {
                sendMessage(socket, textEntry.getText().toString());
                textEntry.setText("");
                return true;
            }
            return false;
        }
    });
    new Thread(new BluetoothSocketListener(socket, handler, messageText)).start();
}

通过以上步骤,我们详细介绍了 Android 中蓝牙技术的使用,包括访问本地蓝牙设备、管理蓝牙属性和状态、发现远程设备、建立蓝牙通信以及实现简单的蓝牙数据传输示例。希望这些内容能帮助你更好地理解和应用 Android 蓝牙开发。

Android 蓝牙、网络与 Wi-Fi 开发全解析

8. 网络与 Wi-Fi 管理概述

除了蓝牙,Android 还提供了完整的网络和 Wi-Fi 管理功能。利用相关 API,我们可以扫描热点、创建和修改 Wi-Fi 配置设置、监控互联网连接状态,以及控制和监控互联网设置与偏好。

9. 监控互联网连接状态

在开发过程中,我们常常需要监控设备的互联网连接状态,以确保应用在合适的网络环境下运行。可以通过以下步骤实现:
1. 获取连接管理器 :使用 getSystemService 方法获取 ConnectivityManager 实例。

ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
  1. 检查网络连接信息 :通过 getActiveNetworkInfo 方法获取当前活动的网络连接信息。
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
  1. 判断连接状态 :根据 NetworkInfo 的状态判断设备是否连接到互联网。
if (networkInfo != null && networkInfo.isConnected()) {
    // 设备已连接到互联网
} else {
    // 设备未连接到互联网
}
10. 遵循用户的后台数据传输偏好

为了节省用户的流量和电量,我们应该遵循用户的后台数据传输偏好。可以通过以下步骤实现:
1. 获取连接管理器 :同样使用 getSystemService 方法获取 ConnectivityManager 实例。

ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
  1. 检查后台数据是否启用 :使用 getBackgroundDataSetting 方法检查用户是否允许后台数据传输。
boolean isBackgroundDataEnabled = connectivityManager.getBackgroundDataSetting();
if (isBackgroundDataEnabled) {
    // 可以进行后台数据传输
} else {
    // 不允许进行后台数据传输
}
11. 监控 Wi-Fi 和网络详细信息

我们可以通过广播接收器来监控 Wi-Fi 和网络的详细信息。以下是一个示例:

BroadcastReceiver networkReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (ConnectivityManager.CONNECTIVITY_ACTION.equals(action)) {
            ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
            if (networkInfo != null && networkInfo.isConnected()) {
                // 网络已连接
                if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                    // 当前使用 Wi-Fi 连接
                } else {
                    // 当前使用其他网络连接
                }
            } else {
                // 网络未连接
            }
        }
    }
};
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
registerReceiver(networkReceiver, filter);
12. 配置网络和 Wi-Fi 配置

可以通过以下步骤来配置网络和 Wi-Fi 配置:
1. 获取 Wi-Fi 管理器 :使用 getSystemService 方法获取 WifiManager 实例。

WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
  1. 开启或关闭 Wi-Fi :使用 setWifiEnabled 方法开启或关闭 Wi-Fi。
wifiManager.setWifiEnabled(true); // 开启 Wi-Fi
wifiManager.setWifiEnabled(false); // 关闭 Wi-Fi
  1. 扫描可用的 Wi-Fi 热点 :使用 startScan 方法启动 Wi-Fi 扫描。
wifiManager.startScan();
  1. 获取扫描结果 :通过 getScanResults 方法获取扫描到的 Wi-Fi 热点列表。
List<ScanResult> scanResults = wifiManager.getScanResults();
for (ScanResult scanResult : scanResults) {
    // 处理每个扫描结果
}
  1. 连接到指定的 Wi-Fi 热点 :创建 WifiConfiguration 对象,并设置相关参数,然后使用 addNetwork enableNetwork 方法连接到指定的 Wi-Fi 热点。
WifiConfiguration wifiConfig = new WifiConfiguration();
wifiConfig.SSID = "\"your_wifi_ssid\"";
wifiConfig.preSharedKey = "\"your_wifi_password\"";
int netId = wifiManager.addNetwork(wifiConfig);
wifiManager.enableNetwork(netId, true);
13. 总结

通过本文,我们详细介绍了 Android 中蓝牙、网络和 Wi-Fi 的开发知识。包括蓝牙设备的管理、发现和通信,以及网络和 Wi-Fi 的监控、配置等功能。以下是一个总结表格:
| 功能模块 | 主要操作 | 关键代码示例 |
| ---- | ---- | ---- |
| 蓝牙 | 访问本地蓝牙设备、管理蓝牙属性和状态、发现远程设备、建立通信 | BluetoothAdapter.getDefaultAdapter() bluetooth.startDiscovery() bluetooth.listenUsingRfcommWithServiceRecord() |
| 网络与 Wi-Fi | 监控互联网连接状态、遵循用户后台数据传输偏好、监控 Wi-Fi 和网络详细信息、配置网络和 Wi-Fi 配置 | ConnectivityManager.getActiveNetworkInfo() WifiManager.setWifiEnabled() WifiManager.startScan() |

希望这些内容能帮助开发者更好地掌握 Android 中的蓝牙、网络和 Wi-Fi 开发技术,开发出更加优秀的应用程序。同时,在实际开发过程中,我们应该根据具体需求合理使用这些功能,为用户提供更好的体验。

下面是一个简单的 mermaid 流程图,展示了蓝牙通信的基本流程:

graph TD;
    A[开启蓝牙] --> B[使设备可发现];
    B --> C[发现远程设备];
    C --> D[选择远程设备并配对];
    D --> E[建立蓝牙连接];
    E --> F[数据传输];
    F --> G[关闭连接];

通过这个流程图,我们可以更清晰地了解蓝牙通信的整个过程。在实际开发中,我们可以根据这个流程逐步实现蓝牙通信功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值