Android开发 - 蓝牙、NFC、网络和Wi-Fi

关键词:BluetoothAdapter、ConnectivityManager、WifiManager、NfcMessage、NfcAdapter、NdefMessage、NdefRecord

 

  • 管理蓝牙设备和发现模式
  • 发现远程蓝牙设备
  • 通过蓝牙通信
  • 监视Internet连接
  • 监视Wi-Fi和网路详细信息
  • 配置Wi-Fi和扫描接入点
  • 使用Wi-Fi Direct传输数据
  • 扫描NFC标签
  • 使用Android Bean传输数据

 

管理本地蓝牙设备适配器

通过BluetoothAdapter类来控制本地蓝牙设备,该类代表运行应用程序的Android设备。

BluetoothAdapter bluetooth = BluetoothAdapter.getDefaultAdapter(); 

为了读取任何一种本地BluetoothAdapter属性、启动发现过程或者找到绑定的设备,需要在应用程序的manifest文件中包含BLUETOOTH权限。

为了修改任何一种本地设备属性,还需要BLUETOOTH_ADMIN使用权限。

BluetoothAdapter bluetoothAdapter=BluetoothAdapter.getDefaultAdapter();
//使用isEnable方法确认设备已经启用
if(bluetoothAdapter.enable()){
    //访问BluetoothAdapter的"友好的"(用户设置的,用于标识特定设备的任意字符串)名称
    bluetoothAdapter.getName();
    //获取硬件地址
    bluetoothAdapter.getAddress();
}

如果拥有BLUETOOTH_ADMIN权限,那么就能够使用setName方法更改BluetoothAdapter的"友好的"名称。

使用getState方法,获取当前BluetoothAdapter的状态。

STATE_OFF

Indicates the local Bluetooth adapter is off.

STATE_ON

Indicates the local Bluetooth adapter is on, and ready for use.

STATE_TURNING_OFF

Indicates the local Bluetooth adapter is turning off.

STATE_TURNING_ON

Indicates the local Bluetooth adapter is turning on.

为了启用BluetoothAdapter,可以使用startActivityForResult启动一个系统Referrence Activity并将ACTION_REQUEST_ENABLE静态常量作为其动作字符串。

private static final int ENABLE_BLUETOOTH = 1;

private void initBluetooth() {
    if (!bluetooth.isEnabled()) { 
        // 蓝牙设备未启动,提示用户启用
        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(intent, ENABLE_BLUETOOTH);
          } else {
        // 蓝牙设备已启用,初始化UI
        initBluetoothUI();
    }
}

protected void onActivityResult(int requestCode,
    int resultCode, Intent data) {
        if (requestCode == ENABLE_BLUETOOTH)
    if (resultCode == RESULT_OK) {
        // 蓝牙设备已启用,初始化UI
        initBluetoothUI();
    }
  
    if (requestCode == DISCOVERY_REQUEST) {
        if (resultCode == RESULT_CANCELED) {
            Log.d(TAG, "Discovery cancelled by user");
        }
    }

}

启用和禁用BluetoothAdapter是比较耗时的异步操作。

应用程序不应该轮训BluetoothAdapter,而是应当注册一个BroadcastReceiver用于监听ACTION_STATE_CHANGED。BroadcastReceiver包含以下两个extra。

EXTRA_PREVIOUS_STATE

Used as an int extra field in ACTION_STATE_CHANGED intents to request the previous power state.

EXTRA_STATE

Used as an int extra field in ACTION_STATE_CHANGED intents to request the current power state.

如果在manifest文件中包含了BLUETOOTH_ADMIN权限,还可以使用enable和disable方法直接打开和关闭BluetoothAdapter。

 

可发现性和远程设备发现

两个设备互相查找以进行连接的过程叫做发现。

在建立一个BluetoothSocket用于通信之前,本地BluetoothAdapter必须与远程设备绑定。而在两个设备能够绑定并连接之前,它们首先需要相互发现。

管理设备的可发现性

 

发现远程设备

 

蓝牙通信

 

 

管理网络和Internet连接

ConnectivityManager

为了使用ConnectivityManager,应用程序需要读取和写入网络状态访问权限:ACCESS_NETWORK_STATE,CHANGE_NETWORK_STATE

final ConnectivityManager connectivity = (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);

支持用户首选项以进行后台数据传输

为了获得后台数据设置,需要调用getBackgroundDataSetting方法(API Level 14之前)。

如果禁用了后台数据设置,那么你的应用程序应当仅在它处于活动状态并位于前台时传输数据。

如果你的应用程序需要后台数据传输以实现某些功能,则最佳实践是通知用户这一需求,并提供进入设置页面以允许他们更改其首选项。

如果用户确实更改了后台数据首选项,那么系统将会发送带有ACTION_BACKGROUND_DATA_SETTING_CHANGED动作的BroadcastIntent。

registerReceiver(
    new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        boolean backgroundEnabled = connectivity.getBackgroundDataSetting();
        setBackgroundData(backgroundEnabled);
        }
    },new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED)
);

在API Level 14以后,getBackgroundDataSetting方法已被弃用,并且总是返回true。

将应用程序的数据使用首选项添加到系统设置中

<activity android:name=".MyPreferences" 
          android:label="@string/preference_title">
  <intent-filter>
    <action android:name="android.intent.action.MANAGE_NETWORK_USAGE"/>
    <category android:name="android.intent.category.DEFAULT" />   
  </intent-filter>
</activity>

设置好以后,系统设置中的ViewApplicationSetting按钮可启动你的PreferenceActivity,让用户调整应用程序的数据使用方法,而不是限制或禁用它。

查找和监视网络连接

getActiveNetworkInfo方法会返回一个NetworkInfo对象,其中包含了当前活动网络的详细信息。

也可以使用getNetworkInfo方法获得关于指定类型的非活动网络的详细信息。

使用返回的NetworkInfo了解所返回的网络的连接状态、网络类型以及详细的状态信息。

在尝试传输数据前,应该配置一个重复报警,或者调度一个执行数据传输的后台服务,并使用ConnectivityManager检查是否连接到了Intent,如果是,则确认连接的类型。

NetworkInfo activeNetwork = connectivity.getActiveNetworkInfo();

boolean isConnected = ((activeNetwork  != null) && (activeNetwork.isConnectedOrConnecting()));

boolean isWiFi = activeNetwork.getType() == ConnectivityManager.TYPE_WIFI;

为了监视网络连接,可以创建一个BroadcastReceiver来监听CONNECTIVITY_ACTION Broadcast Intent。

<!-- Monitoring connectivity -->
<receiver android:name=".ConnectivityChangedReceiver" >
    <intent-filter >
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
    </intent-filter>
</receiver>    

 

管理Wi-Fi

为了使用WifiManager,应用程序需要以下权限:ACCESS_WIFI_STATECHANGE_WIFI_STATE

final WifiManager wifi = (WifiManager)getSystemService(Context.WIFI_SERVICE);

getWifiState() Gets the Wi-Fi enabled state.

setWifiEnabled(boolean enabled)   Enable or disable Wi-Fi.

监视Wi-Fi连接

每当Wi-Fi网络连接状态发生变化时,WifiManager会广播Intent,它会使用在WifiManager勒种定义的下列常量:

WIFI_STATE_CHANGED_ACTION

Broadcast intent action indicating that Wi-Fi has been enabled, disabled, enabling, disabling, or unknown.

SUPPLICANT_STATE_CHANGED_ACTION

Broadcast intent action indicating that the state of establishing a connection to an access point has changed.One extra provides the newSupplicantState.

SUPPLICANT_CONNECTION_CHANGE_ACTION

Broadcast intent action indicating that a connection to the supplicant has been established (and it is now possible to perform Wi-Fi operations) or the connection to the supplicant has been lost.

NETWORK_STATE_CHANGED_ACTION

Broadcast intent action indicating that the state of Wi-Fi connectivity has changed.

RSSI_CHANGED_ACTION

The RSSI (signal strength) has changed.

监视活动的Wi-Fi连接的详细信息

WifiInfo getConnectionInfo()

Return dynamic information about the current Wi-Fi connection, if any is active.

 

 

WifiInfo info = wifi.getConnectionInfo();
if (info.getBSSID() != null) {
    int strength = WifiManager.calculateSignalLevel(info.getRssi(), 5);
    int speed = info.getLinkSpeed();
    String units = WifiInfo.LINK_SPEED_UNITS;
    String ssid = info.getSSID();

    String cSummary = String.format("Connected to %s at %s%s. " +
                                    "Strength %s/5",
                                    ssid, speed, units, strength);
    Log.d(TAG, cSummary);
}

扫描热点

可以使用WifiManager的startScan()方法进行接入点扫描。

一个带有SCAN_RESULTS_AVAILABLE_ACTION动作的Intent奖杯广播以便异步宣布扫描完成并且结果可用。

调用getScanResults()以获得扫描结果(List<ScanResult>)

每一个ScanResult包含了为每个检测到的接入点所检索到的详细信息,包括链路速度、信号强度、SSID以及所支持的身份验证技术。

    // Register a broadcast receiver that listens for scan results.
    registerReceiver(new BroadcastReceiver() {
      @Override
      public void onReceive(Context context, Intent intent) {
        List<ScanResult> results = wifi.getScanResults();
        ScanResult bestSignal = null;
        for (ScanResult result : results) {
          if (bestSignal == null ||
              WifiManager.compareSignalLevel(
                bestSignal.level,result.level) < 0)
            bestSignal = result;
        }

        String connSummary = String.format("%s networks found. %s is" +
                                           "the strongest.",
                                           results.size(),
                                           bestSignal.SSID);

        Toast.makeText(MyActivity.this, 
                       connSummary, Toast.LENGTH_LONG).show();
      }
    }, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));

    // Initiate a scan.
    wifi.startScan();

管理Wi-Fi配置

getConfiguredNetworks()获取当前网络配置的列表(List<WifiConfiguration>)

WifiConfiguration对象包含了网络ID、SSID和其他详细信息。

为了使用某个特定的网络配置,需要使用enableNetwork(int netId, boolean disableOthers)方法,并传入要使用的网络Id,同时将disableOthers设置为true。

WifiManager wifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
List<WifiConfiguration> configurations = wifiManager.getConfiguredNetworks();

if (configurations.size() > 0) {
    int netId = configurations.get(0).networkId;
    wifiManager.enableNetwork(netId, true);
}

创建Wi-Fi网络配置

 

使用Wi-Fi Direct传输数据

 

近场通信(NFC)

API Level 9引入了近场通信(Near Field Communication,NFC)API。NFC是一种非接触式的技术,用于在短距离(通常小于4厘米)内少量数据的传输。

NFC传输可以在两个支持NFC的设备或者一个设备和一个NFC“标签”之间进行。

为读取、写入或者广播NFC消息,应用程序需要具有NFC manifest权限。

NFC

Allows applications to perform I/O operations over NFC.

Constant Value: "android.permission.NFC"

 

NfcManager  This is the high level manager, used to obtain this device's NfcAdapter. You can acquire an instance using getSystemService(Class).

NfcAdapter   This represents the device's NFC adapter, which is your entry-point to performing NFC operations. You can acquire an instance withgetDefaultAdapter(), or getDefaultAdapter(android.content.Context).

 

读取NFC标签

当一个Android设备用于扫描一个NFC标签时,其系统将使用自己的标签分派系统解码传入的有效荷载。这个标签分派系统会分析标签,将数据归类,并使用Intent启动一个应用程序来接收数据。

为使应用程序能够接收NFC数据,需要添加一个Activity Intent Filter来监听以下的某个Intent动作:

ACTION_NDEF_DISCOVERED

优先级最高、也是最具体的NFC消息动作。使用这个动作的Intent包括MEME类型和/或URI数据。最好的做法是只要有可能就监听这个广播,因为其extra数据允许更加具体地定义要响应的标签。

ACTION_TAG_DISCOVERED

如果从未知技术收到一个标签,则使用此Intent动作广播该标签。

ACTION_TECH_DISCOVERED

当NFC技术已知,但是标签不包含数据(或者包含的数据不能被映射为MIME类型或者URI)时广播这个动作。

监听NFC标签,注册一个Activity,其只响应对于NFC.xxx.com的URI的NFC标签。

<activity android:name=".NFCViewer">
    <intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:scheme="http"
              android:host="NFC.xxx.com"/>
    </intent-filter>
</activity>

提取NFC标签的有效荷载:

public class BeamerActivity extends Activity {
  
  PendingIntent nfcPendingIntent;
  IntentFilter[] intentFiltersArray;
  String[][] techListsArray;
  NfcAdapter nfcAdapter;

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    
    nfcAdapter = NfcAdapter.getDefaultAdapter(this);
    
    // Create the Pending Intent.  
    int requestCode = 0;
    int flags = 0;

    Intent nfcIntent = new Intent(this, getClass());
    nfcIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
     
    nfcPendingIntent = 
      PendingIntent.getActivity(this, requestCode, nfcIntent, flags);
    
    // Create an Intent Filter limited to the URI or MIME type to
    // intercept TAG scans from.
    IntentFilter tagIntentFilter = 
      new IntentFilter(NfcAdapter.ACTION_NDEF_DISCOVERED);
    tagIntentFilter.addDataScheme("http");
    tagIntentFilter.addDataAuthority("blog.radioactiveyak.com", null);
    intentFiltersArray = new IntentFilter[] { tagIntentFilter };

    // Create an array of technologies to handle.
    techListsArray = new String[][] {
      new String[] { 
        NfcF.class.getName() 
      } 
    };
  }
  
  private void processIntent(Intent intent) {
    String action = getIntent().getAction();

    if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
      Parcelable[] messages = 
        intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);

      for (int i = 0; i < messages.length; i++) {
        NdefMessage message = (NdefMessage)messages[i];
        NdefRecord[] records = message.getRecords();

        for (int j = 0; j < records.length; j++) {
          NdefRecord record = records[j];
          // TODO Process the individual records.
        }
      }
    }
  }
  
  public void onPause() {
    super.onPause();

    nfcAdapter.disableForegroundDispatch(this);
  }

  @Override
  public void onResume() {
    super.onResume();
    nfcAdapter.enableForegroundDispatch(
      this, 
      // Intent that will be used to package the Tag Intent.
      nfcPendingIntent, 
      // Array of Intent Filters used to declare the Intents you
      // wish to intercept.
      intentFiltersArray, 
      // Array of Tag technologies you wish to handle.
      techListsArray);

    String action = getIntent().getAction();
    if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
      processIntent(getIntent());
    }
  }

}

使用前台分派系统

 

Android Beam

API Level 14中引入的Android Beam提供了一个简单的API。应用程序可以使用该API在使用NFC的两个设备之间传输数据,只要将这两个设备背靠背放在一起即可。

为了使AndroidBeam传输消息,应用程序必须位于前台,而且接收数据的设备不能处于锁定状态。

通过将两个支持NFC的Android设备放在一起,可以启动Android Beam。用户会看到一个“touch to beam”(触摸以传输)UI,此时他们可以选择把前台应用程序“beam”(传输)到另外一个设备。

通过在应用程序内启用Android Beam,可以定义所传输的消息的有效荷载。如果没有自定义消息,应用程序的默认动作会在目标设备上启动它。如果目标设备上没有安装应用程序,那么GooglePlay就会启动,并显示应用程序的详细信息页面。

创建AndroidBeam消息

要创建一个新的NdefMessage对象,在其中创建至少一个NdefRecord,用于包含你想要传输给目标设备上应用程序的有效载荷。

创建新的NdefRecord时,必须制定它表示的记录类型、一个MIME类型、一个ID和有效载荷。

包含Android Application Record(AAR)形式的NdefRecord是一种很好的做法。这可以保证应用程序会在目标设备上启动;如果目标设备上没有安装应用程序,则会启动Google Play Store,让用户安装它。

要创建一个AAR NdefRecord,需要使用NdefRecord类的createApplicationRecord(String packageName)静态方法,并指定应用程序的包名。

String payload = "Two to beam across";

String mimeType = "application/com.paad.nfcbeam";
byte[] mimeBytes = mimeType.getBytes(Charset.forName("US-ASCII"));

NdefMessage nfcMessage = new NdefMessage(new NdefRecord[] { 
    // Create the NFC payload.
    new NdefRecord(
        NdefRecord.TNF_MIME_MEDIA, 
        mimeBytes, 
        new byte[0],
        payload.getBytes()),

    // Add the AAR (Android Application Record)
    NdefRecord.createApplicationRecord("com.paad.nfcbeam")
});

分配AndroidBeam有效载荷

NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);

有两种方式可以吧NdefMessage指定为应用程序的AndroidBeam有效载荷:

最简单的方法是使用setNdefPushMessage(NdefMessage message, Activity activity, Activity... activities)方法来分配当AndroidBeam启动时总是应该从当前Activity发送的消息。通常,这种分配只需要在Activity的onResume方法中完成一次。

更好的方法是使用setNdefPushMessageCallback(NfcAdapter.CreateNdefMessageCallback callback, Activity activity, Activity...activities)方法。该处理程序在消息被传输之前立即触发,允许你根据应用程序当前的上下文动态设置有效载荷内容。

如果使用回调处理程序同时设置了静态消息和动态消息,那么只有动态消息会被传输。

接收AndroidBeam消息

为了接收打包的有效载荷,首先要在Activity中添加一个新的Intent Filter。

<activity
    android:label="@string/app_name"
    android:name=".BeamerActivity" >
    <intent-filter>
        <action android:name="android.nfc.action.NDEF_DISCOVERED"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <data android:mimeType="application/com.paad.nfcbeam"/>
    </intent-filter>
</activity>

AndroidBeam启动后,接收设备上的Activity就会被启动;如果接收设备上没有安装应用程序,那么GooglePlayStore就会启动,以允许用户下载你的应用程序。

传输的数据会使用一个具有ACTION_TECH_DISCOVERED动作的Intent传输给Activity,其有效载荷可作为一个NdefMessage数组用于存储对应的EXTRA_NDEF_MESSAGES

Parcelable[] messages = intent.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES);

NdefMessage message = (NdefMessage)messages[0];
NdefRecord record = message.getRecords()[0];

//Returns the variable length payload.
String payload = new String(record.getPayload());

通常,有效载荷字符串是一个URI的形式,可以像对待Intent内封装的数据一样提取和处理它,以显示合适的视频、网页或地图坐标。

 

posted @ 2016-10-29 10:43  guqiangjs  阅读(2051)  评论(0编辑  收藏  举报