在本系列的上一篇文章中 ,您了解了可用于Android Things的各种类型的外围设备输入和输出设备以及连接。 接下来,您可以扩展此知识来编写称为驱动程序的新类,这些类使与外围设备的连接更加容易。 本文将重点介绍可为Android Things编写的驱动程序类型。
Android Things用户空间驱动程序
用户空间驱动程序允许开发人员向Android框架中注入新硬件,从而使他们可以与已经建立的Android API进行交互。 尽管您可以使用标准输入/输出API与设备直接通信,但是编写自定义驱动程序将使您的应用程序支持各种硬件配置文件,并可以直接与Android OS一起使用。 此外,您的代码将更加结构化,并轻松支持代码重用。
您将在本文中了解三种主要的驱动程序类别:GPS驱动程序,人机输入设备(HID)驱动程序和传感器驱动程序。
GPS驱动器
如果您的设备需要位置信息,则可能需要将GPS设备添加到您的应用中。 通过向UserDriverManager
注册GPS外围设备,您将能够将设备的位置数据注入Android框架,从而允许Android的位置服务使用它。 这会将GPS数据与WiFi以及其他任何位置信息源结合起来,为您的应用提供更准确的数据结果。
通常,您的GPS模块将通过UART连接连接到Android Things设备。 我们不会太深入的了解UART在本教程中,但你可以学习所有关于它是如何工作的外设在本系列前面的教程 。

要使用GPS模块,您需要创建一个新的Java组件以与新设备进行通信。 我们将此类GpsDriverService
。
为了与位置数据的Android框架注册您的GPS模块,您首先需要创建一个GpsDriver
在你的对象GpsDriverService
。 可以通过registerGpsDriver()
调用向UserDriverManager
注册该对象。
private GpsDriver mDriver;
@Override
public void onCreate() {
super.onCreate();
mDriver = new GpsDriver();
UserDriverManager manager = UserDriverManager.getManager();
manager.registerGpsDriver( mDriver );
}
GPS模块收到位置数据并将其通过串行UART连接发送到Android Things设备后,您将需要解析它并将其添加到Location
对象。
大多数GPS模块将以NMEA格式返回位置数据,尽管解析该数据超出了本教程的范围。 您的Location对象有四个必需的数据项:准确性,时间,纬度和经度。 您还可以选择包括海拔高度,方位角和速度(如果设备正在移动)。
private Location parseLocationFromString(String gpsData) {
Location result = new Location(LocationManager.GPS_PROVIDER);
//parse gpsData
//required
result.setAccuracy( getAccuracyFromGpsData( gpsData ) );
result.setTime( getTimeFromGpsData( gpsData ) );
result.setLatitude( getLatitudeFromGpsData( gpsData ) );
result.setLongitude( getLongitudeFromGpsData( gpsData ) );
//optional
result.setAltitude( getAltitudeFromGpsData( gpsData ) );
result.setBearing( getBearingFromGpsData( gpsData ) );
result.setSpeed( getSpeedFromGpsData( gpsData ) );
return result;
}
填充Location
对象后,可以通过调用reportLocation()
将其传递给GpsDriver
。
Location location = parseLocationFromString( rawGpsData );
mDriver.reportLocation( location );
创建组件后,您将需要实例化它,开始读取数据并收听应用程序中的更新。
private LocationListener mLocationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
Log.v("Test", "Location update: " + location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
Log.e("Test", "onstatuschanged");
}
@Override
public void onProviderEnabled(String provider) {
Log.e("Test", "onproviderenabled");
}
@Override
public void onProviderDisabled(String provider) {
Log.e("Test", "onproviderdisabled");
}
};
...
mLocationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
// Create and start the GPS component
mGpsDriver = new GpsDriverService();
mGpsDriver.register();
// Register for location updates
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mLocationListener);
应用程序完成后,您将需要注销驱动程序并删除位置侦听器。
@Override
protected void onDestroy() {
super.onDestroy();
if (mGpsDriver != null) {
mGpsDriver.unregister();
mLocationManager.removeUpdates(mLocationListener);
try {
mGpsDriver.close();
} catch (IOException e) {
}
}
}
首次使用在Android Things中使用新权限的应用程序时,您需要在安装应用程序后重新启动设备,以确保已授予该权限。
人工输入设备驱动程序
Android框架带有一个内置管道,用于处理来自用户按钮和动作事件的输入,该管道用于诸如媒体按钮,控制器操纵杆和键盘按键之类的操作。 通过创建InputDriver
,您可以将自己的人机交互绑定到标准的Android输入管道,以便您的设备可以对用户做出正确的反应。
为简单起见,我们将仅研究按钮输入事件以及如何将它们绑定到Android框架中,尽管动作事件的处理方式非常相似。 与本教程的最后一部分类似,我们将忽略输入设备的更具体的实现细节,而将重点放在将收到的事件绑定到Android平台上。
按钮事件
当您已附加到Android Things设备的按钮发生按钮事件时,您将要记录该事件并将其通过Android管道发送。
您需要做的第一件事是在新Service
创建一个InputDriver
对象并对其进行初始化。 可以使用接受输入类型,输入名称,版本和按钮代表的键代码的构建器来初始化驱动程序。
private InputDriver mDriver;
@Override
public void onCreate() {
super.onCreate();
mDriver = InputDriver.builder(InputDevice.SOURCE_CLASS_BUTTON)
.setName("ButtonInputDriver")
.setVersion(1)
.setKeys(new int[] {KeyEvent.KEYCODE_SPACE})
.build();
}
一旦你的InputDriver
被初始化,您可以用它注册UserDriverManager
使用registerInputDriver()
调用。
UserDriverManager manager = UserDriverManager.getManager();
manager.registerInputDriver(mDriver);
注册InputDriver
,驱动程序服务可以等待按钮实现类将事件发送给它。 如果按下了自定义按钮,则可以通知该服务并创建一个新的KeyEvent
,可以使用emit(KeyEvent)
方法将其放置在Android输入管道上。 如果可以将KeyEvent
发送到Android框架,则此方法将返回true;如果发生错误,则返回false。
if( buttonPressed ) {
KeyEvent[] events = new KeyEvent[] {new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SPACE)};
if (!mDriver.emit(events)) {
//something went wrong
}
}
你需要做的最后一件事就是取消注册InputDriver
从对象UserDriverManager
当你的应用程序已经完成运行。
@Override
public void onDestroy() {
super.onDestroy();
UserDriverManager manager = UserDriverManager.getManager();
manager.unregisterInputDriver(mDriver);
}
监听输入事件
现在您可以将按钮输入事件发送到Android输入管道,是时候监听它们了。 在这里,将新按钮事件集中到Android框架中的所有工作都会得到回报。 在应用程序的“活动”中,您只需要添加一个用于KeyEvent
何时关闭的方法,以及一个用于KeyEvent
何时启动的方法。 在这些方法中,您可以检查KeyEvent
的键代码并适当地处理事件。
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if( event.getKeyCode() == KeyEvent.KEYCODE_SPACE ) {
//handle it
}
return true;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return true;
}
传感器驱动器
您将在Android Things板上使用的一些最常见的组件是传感器。 由于Android具有相当健壮的传感器框架,因此我们只希望能够将来自外部组件的数据添加到该管道中就有意义。
首先,您需要创建一个新的Java类,该类与您的硬件传感器进行交互。 此类将需要扩展UserSensorDriver
并实现read()
方法。 另外,如果您的传感器支持低功耗或睡眠模式,则可以覆盖setEnabled()
方法并采取相应的措施。
以下代码段是组件类的简化版本,它将通过外围I / O API读取数据以检索X,Y和Z数据值并返回一个新的UserSensorReading
。 如果数据当前不可用,则您的类可以引发新的IOException
。
public class ExampleSensorComponent extends UserSensorDriver {
float x, y, z;
@Override
public UserSensorReading read() throws IOException{
try {
// Read data from the sensor hardware and return it
x = getXValueFromHardware();
y = getYValueFromHardware();
z = getZValueFromHardware();
return new UserSensorReading(new float[]{x, y, z});
} catch (Exception e) {
// Error occurred reading the sensor hardware
throw new IOException("Unable to read sensor");
}
}
//Used if supporting low power/sleep mode
@Override
public void setEnabled(boolean enabled) throws IOException {
super.setEnabled(enabled);
}
}
创建组件类后,您可以创建将其实例化的新Service
,还可以创建新的UserSensor
对象并将其附加到Android传感器管道。
您可以将两种传感器添加到该管道中。 第一种是预定义的类型,例如陀螺仪,加速计,光和接近度,可以像这样将其添加到管道中:
private ExampleSensorComponent mExampleSensor;
private UserSensor mSensor;
private SensorManager mSensorManager;
@Override
public void onCreate() {
super.onCreate();
mExampleSensor = new ExampleSensorComponent();
mSensor = UserSensor.builder()
.setName("ExampleSensorComponent")
.setVendor("VendorName")
.setType(Sensor.TYPE_ACCELEROMETER)
.setDriver(mExampleSensor)
.build();
UserDriverManager manager = UserDriverManager.getManager();
// Register the new driver with the framework
manager.registerSensor(mSensor);
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mSensorManager.registerDynamicSensorCallback(new SensorCallback());
}
private class SensorCallback extends SensorManager.DynamicSensorCallback {
@Override
public void onDynamicSensorConnected(Sensor sensor) {
//Sensor connected
mSensorManager.registerListener(SensorDriverService.this, sensor,
SensorManager.SENSOR_DELAY_NORMAL);
}
@Override
public void onDynamicSensorDisconnected(Sensor sensor) {
//Sensor disconnected
mSensorManager.unregisterListener(SensorDriverService.this);
}
}
您会注意到上面的示例中使用了SensorManager.DynamicSensorCallback
。 当传感器可用于框架时,这会通知您的应用程序,因为注册可能需要一些时间,因此框架不会尝试从不可用的设备读取数据。

第二种类型是custom ,它涵盖了Android中不支持的所有内容。 一些示例包括水的pH值水平,风速,运动检测或您可以使用新硬件测量的其他任何东西。
通过更换setType()
与建设者参数setCustomType()
您可以将您的设备的名称和报告模式,以控制将如何引发的管道。
mSensor = UserSensor.builder()
.setName("ExampleSensorComponent")
.setVendor("VendorName")
.setType(Sensor.TYPE_ACCELEROMETER)
.setCustomType(Sensor.TYPE_DEVICE_PRIVATE_BASE,
"com.tutsplus.examplesensor",
Sensor.REPORTING_MODE_ON_CHANGE)
.setDriver(mExampleSensor)
.build();
最后,当您的应用程序完成运行时,您将需要从UserDriverManager
注销新组件。
@Override
public void onDestroy() {
super.onDestroy();
UserDriverManager manager = UserDriverManager.getManager();
manager.unregisterSensor(mSensor);
}
结论
在本教程中,您学习了如何获取使用外围I / O API构建的组件,并将它们绑定到适当的Android框架中,以便在Android Things应用中使用。
在本系列的这一点上,您已经拥有创建一些更深入的Android Things项目所需的所有工具。 除了编写自己的驱动程序外,您还可以找到编写的驱动程序并将其实现到自己的项目中。 您可以从Android Things GitHub存储库中找到这些驱动程序的源 ,或者使用这些驱动程序查看一些可用的 示例 。