Android Things正式接替Brillo亮相,名称的改变带来了什么新的内容,广大Android开发者如何进入这一新的领域,通过本文,你不仅会了解Android Things的来龙去脉,也会直接通过代码来体验开发带给你的魅力。

Android Everywhere

一张Google IO 2015上的旧图,清晰的展示了Android的历史和未来。

Android

处于中心的Android Mobile,已经占据了全球手机市场绝大多数份额,几十亿部Android手机,构成了Android生态系统最坚实的基础。

Android TV

随着着电视大屏发展的脚步,Android TV也成了所有电视盒子和智能电视的不二之选,主要归功于Android硬件系统的开放、庞大的开发者群体、完整的工具链。如果说手机端还有iOS将近20%的份额,在TV端,目测tvOS不到2%,Android TV也就代表了整个智能电视,国内更是100%的Android TV,Apple TV没有国行版本。

Android Wear

让你的应用跑在手表上,同样使用熟悉的开发工具,主要是面向海外的应用,由于国内Android Mobile严重分裂,而Android Wear需要依赖手机的支持。

Android Auto

针对汽车的使用场景进行优化,有了Android Auto就不用上车后就是找电源线,然后开导航,再把手机放到各种架子上,而是直接把Android Mobile放到原生底座上,直接使用语音和易操作的中控大屏。

Android Chromebook

从 Chrome 操作系统版本 M53 开始,可以直接使用Chromebook中的Google Play Store下载和使用Android应用,现在使用的Google Chrome版本是55,那么绝大多数已有Chromebook都已经支持Android应用,新的Chromebook自然都会支持。
针对 Chromebook 优化应用
支持Android应用的Chromebook列表
Android和Chrome两大系统的合并,除了带来了Android丰富的应用以外,对于用户最大的好处就是自动更新了,Chrome自动下载更新,下次重新打开/启动时自动应用更新,从Android Nougat开始,Android系统也将使用这种更新机制。
根据IDC报告,Chromebook在2016年第一季度的出货量已经在美国市场超越了Mac,特别是由于教育市场的大量需求。
这里,还要注意,Chromebook使用的Chrome OS和Google Chrome高度共享代码,既然Android应用可以在Chromebook上运行,当未来在Mac/Windows/Linux上可以时,你也不要感到惊讶。

Android Things

终于到了今天的主角登场,Android Things!先看外表。

再看内部核心硬件。

它的愿景就是将无数的的设备连接起来,Android Things作为物联网的大脑,使用公开协议Weave与广大的传感器/外部设备进行对话。
不像Android其它系统,Android Things大多数情况下只在后台以服务方式运行,没有显示屏,默默的与打印机、门锁、烤箱、灯泡、插座这些设备一起提供服务。

Android Things全解析

## Android Things架构

先看Brillo和Android Things的架构图进行对比。
这是Brillo,


这是Android Things,

可以很清楚的看出来:

  • Brillo使用C/C++基于NDK进行开发,Android Things通过Java API面向广大的Android和Java开发者,就算是新手,Android的也是极易上手的。各位苦于嵌入式开发各种工具坑的福音到了,对于性能和底层要求高的部分仍然可以用NDK编写,在Android Studio里调试NDK代码也和Java代码一样的简单。
  • Android Studio,Android SDK,Play service和Firebase,这些工具和Service形成了完整易用的工具链。
  • Android Things出生最晚,更新条件也是最好的,直接使用Android Nougat的自动后台更新机制,最大限度的提高系统的安全性。
    ##广泛的硬件平台支持
    现在支持以下3款硬件
    Intel Edison
    开发版中的贵族。
    NXP Pico
    中规中矩的中间阶层。

Raspberry Pi 3
少了草根精英树莓派怎么行。

Hello Android Things

买到的开发版都是没有装操作系统系统的,第一步先把Android Things刷到板子里。

Flash image(刷机)

官方刷机教程
以Intel Edison为例:
0.Android SDK Platform Tools 25.0.3以上,fastboot工具添加到PATH环境变量中,以便从任意目录运行。
1.下载后打Intel Flash Tool,加打开下载好的对应刷机包。

  1. 使用USB线链接Edison,如果Edison没有显示,换USB口和线试试。

  2. Start to Flash(开始刷机)

4.使用Fastboot刷入系统镜像,此时需要几十秒,光System.img就有500多M。



5.刷入Google Service镜像。

6.刷入OEM镜像。

7.重启

1
fastboot reboot

8.验证系统状态。


如果出现以下Error,把Intel Flash Tool关掉,重新连接下USB。
1
2
3
4
5
6
7
8
List of devices attached
adb server version (35) doesn't match this client (36); killing...
adb E 69469 2714428 usb_osx.cpp:327] Could not open interface: e00002c5
adb E 69469 2714428 usb_osx.cpp:289] Could not find device interface
error: could not install *smartsocket* listener: Address already in use
ADB server didn't ACK
* failed to start daemon *
error: cannot connect to daemon

Connecting WIFI(联网)

依然是熟悉的adb命令和服务启动参数

1
2
3
4
5
$ adb shell am startservice \
-n com.google.wifisetup/.WifiSetupService \
-a WifiSetupService.Connect \
-e ssid SSID \
-e passphrase password

用logcat查看网络状态

1
2
3
4
5
$ adb logcat -d | grep Wifi
...
V WifiWatcher: Network state changed to CONNECTED
V WifiWatcher: SSID changed: ...
I WifiConfigurator: Successfully connected to ...

Ping检测

1
2
3
4
5
6
$ adb shell ping 114.114.114.114
PING 114.114.114.114 (114.114.114.114) 56(84) bytes of data.
64 bytes from 114.114.114.114: icmp_seq=1 ttl=57 time=6.67 ms
64 bytes from 114.114.114.114: icmp_seq=2 ttl=57 time=55.5 ms
64 bytes from 114.114.114.114: icmp_seq=3 ttl=57 time=23.0 ms
64 bytes from 114.114.114.114: icmp_seq=4 ttl=57 time=245 ms

Hello Android Things项目

Android Studio中新建项目

在build.gralde中添加依赖com.google.android.things:androidthings

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.geekdev.alpha.androidthings"
minSdkVersion 24
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
provided 'com.google.android.things:androidthings:0.1-devpreview'
compile 'com.google.android.things.contrib:driver-button:0.1'
compile 'com.android.support:appcompat-v7:25.1.0'
testCompile 'junit:junit:4.12'
}

此处依赖方式是provided,让Android Things使用系统中的库。

添加activity

添加一个主activity并配置AndroidManifest.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.geekdev.alpha.androidthings">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<uses-library android:name="com.google.android.things"/>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.IOT_LAUNCHER"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
</manifest>

在Activity中输出Hello Android Things

1
2
3
4
5
6
7
8
9
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
@Override protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "Hello Android Things!");
}
}

运行输出

直接Command+R,可以在logcat窗口中看到结果了。

Peripheral I/O

不满足于Hello Android Things,继续来使用Android Things对外设进行操作。
使用Button driver对LED灯进行开关操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package com.geekdev.alpha.androidthings;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Button;
import com.google.android.things.contrib.driver.button.ButtonInputDriver;
import com.google.android.things.pio.Gpio;
import com.google.android.things.pio.PeripheralManagerService;
import java.io.IOException;
/**
* Created by Alpha.
* <p>
* Example of using Button driver for toggling a LED.
* <p>
* This activity initialize an InputDriver to emit key events when the button GPIO pin state change
* and flip the state of the LED GPIO pin.
* <p>
* You need to connect an LED and a push button switch to pins specified in {@link BoardDefaults}
* according to the schematic provided above.
*/
public class MainActivity extends Activity {
private static final String TAG = MainActivity.class.getSimpleName();
private Gpio mLedGpio;
private ButtonInputDriver mButtonInputDriver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "Hello Android Things!");
Log.i(TAG, "Starting ButtonActivity");
PeripheralManagerService pioService = new PeripheralManagerService();
try {
Log.i(TAG, "Configuring GPIO pins");
mLedGpio = pioService.openGpio(BoardDefaults.getGPIOForLED());
mLedGpio.setDirection(Gpio.DIRECTION_OUT_INITIALLY_LOW);
Log.i(TAG, "Registering button driver");
// Initialize and register the InputDriver that will emit SPACE key events
// on GPIO state changes.
mButtonInputDriver = new ButtonInputDriver(
BoardDefaults.getGPIOForButton(),
Button.LogicState.PRESSED_WHEN_LOW,
KeyEvent.KEYCODE_SPACE);
mButtonInputDriver.register();
} catch (IOException e) {
Log.e(TAG, "Error configuring GPIO pins", e);
}
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SPACE) {
// Turn on the LED
setLedValue(true);
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_SPACE) {
// Turn off the LED
setLedValue(false);
return true;
}
return super.onKeyUp(keyCode, event);
}
/**
* Update the value of the LED output.
*/
private void setLedValue(boolean value) {
try {
mLedGpio.setValue(value);
} catch (IOException e) {
Log.e(TAG, "Error updating GPIO value", e);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mButtonInputDriver != null) {
mButtonInputDriver.unregister();
try {
mButtonInputDriver.close();
} catch (IOException e) {
Log.e(TAG, "Error closing Button driver", e);
} finally {
mButtonInputDriver = null;
}
}
if (mLedGpio != null) {
try {
mLedGpio.close();
} catch (IOException e) {
Log.e(TAG, "Error closing LED GPIO", e);
} finally {
mLedGpio = null;
}
mLedGpio = null;
}
}
}

添加一个开发板的处理工具类BoardDefaults.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.geekdev.alpha.androidthings;
import android.os.Build;
import com.google.android.things.pio.PeripheralManagerService;
import java.util.List;
/**
* Created by Alpha.
*/
public class BoardDefaults {
private static final String DEVICE_EDISON_ARDUINO = "edison_arduino";
private static final String DEVICE_EDISON = "edison";
private static final String DEVICE_RPI3 = "rpi3";
private static final String DEVICE_NXP = "imx6ul";
private static String sBoardVariant = "";
/**
* Return the GPIO pin that the LED is connected on.
* For example, on Intel Edison Arduino breakout, pin "IO13" is connected to an onboard LED
* that turns on when the GPIO pin is HIGH, and off when low.
*/
public static String getGPIOForLED() {
switch (getBoardVariant()) {
case DEVICE_EDISON_ARDUINO:
return "IO13";
case DEVICE_EDISON:
return "GP45";
case DEVICE_RPI3:
return "BCM6";
case DEVICE_NXP:
return "GPIO4_IO21";
default:
throw new IllegalStateException("Unknown Build.DEVICE " + Build.DEVICE);
}
}
/**
* Return the GPIO pin that the Button is connected on.
*/
public static String getGPIOForButton() {
switch (getBoardVariant()) {
case DEVICE_EDISON_ARDUINO:
return "IO12";
case DEVICE_EDISON:
return "GP44";
case DEVICE_RPI3:
return "BCM21";
case DEVICE_NXP:
return "GPIO4_IO20";
default:
throw new IllegalStateException("Unknown Build.DEVICE " + Build.DEVICE);
}
}
private static String getBoardVariant() {
if (!sBoardVariant.isEmpty()) {
return sBoardVariant;
}
sBoardVariant = Build.DEVICE;
// For the edison check the pin prefix
// to always return Edison Breakout pin name when applicable.
if (sBoardVariant.equals(DEVICE_EDISON)) {
PeripheralManagerService pioService = new PeripheralManagerService();
List<String> gpioList = pioService.getGpioList();
if (gpioList.size() != 0) {
String pin = gpioList.get(0);
if (pin.startsWith("IO")) {
sBoardVariant = DEVICE_EDISON_ARDUINO;
}
}
}
return sBoardVariant;
}
}

运行到如下的Raspberry Pi 3中,使用按钮来控制LED灯。

Code

所有示例项目代码可有Github中找到.

更多

到这里,你已经了解Android Things的历史,特点和开发。更多关于Google技术的内容,欢迎加入G tech online meetup微信群进行交流。