Galaxy Gear车速表(基于Tizen)(译文)
By S.F.
本文链接 https://www.kyfws.com/news/speedometer-for-galaxy-gear-tizen-based/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 22 分钟阅读 - 10838 个词 阅读量 0Galaxy Gear车速表(基于Tizen)(译文)
原文地址:https://www.codeproject.com/Articles/830305/Speedometer-for-Galaxy-Gear-Tizen-Based
原文作者:Joel Ivory Johnson
译文由本站翻译
前言
这是为三星Galaxy Gear创建速度计的演练.
介绍
我的桌子上有一堆可穿戴设备.由Tizen驱动的Galaxy Gearm,由Android Wear驱动的Galaxy Live,谷歌眼镜,心率监测器,甚至还有2004年的Microsoft S.P.O.T.在项目中观看.除Microsoft S.P.O.T.注意,我可以通过自己的程序与所有这些设备进行交互.我认为对所有人进行某种类型的" Hello World"程序会很有趣.速度计似乎很简单,但功能却足够.获得速度并以文本形式显示给用户.尽管功能简单,但这样的程序确实触及了可穿戴设备的许多不同方面,而这些方面比平凡的要有用. 在本文中,我将使用Tizen驱动的Galaxy Gear制作车速表.我拥有于2013年末发布的原始版Galaxy Gear.在撰写本文时,已经发布了第二代齿轮(第二代有三种不同型号),第三代Galaxy Gear S已发布但尚未发布.第一代和第二代单元之间的差异(就功能而言)很小.他们中的大多数人只能视乎他们是否有摄像头,是否有心率监测器以及是否有红外发射器而有所不同.显着的区别在于,其中一个模型的屏幕像素大小与其他单元不同. 要开始使用本文,您需要完成以下工作:
可以通过使用模拟器来进行开发而无需拥有银河齿轮,但我没有.如果您拥有原始的银河齿轮,则需要应用固件更新,该更新将手表的操作系统从Android更改为Tizen.
什么是Tizen
Tizen是三星,英特尔和其他一些公司开发的开源移动操作系统.它自称为"万物的操作系统"(tizen.org),并计划为电视,电话,可穿戴设备和更多提供支持. org/about/devices).目前,唯一直接在硬件上运行的实现是针对Galaxy Gear(手表),以及即将推出的另一种设备(Galaxy Gear S).第一款Tizen手机预计将于明年发布. Tizen支持本机程序和用HTML编写的程序的开发.基于HTML的项目按照W3C打包的Web应用程序(小部件)-打包和XML配置进行打包.我将在第一个程序中使用HTML开发环境.
GPS在哪里
如果您看一下Tizen文档,就会提到GPS. Tizen操作系统确实支持并且已经为GPS定义了API.但是,操作系统中功能的支持并不意味着在使用该操作系统的产品中也存在硬件(例如:Windows支持具有GPS的计算机,但并非所有计算机都具有GPS接收器).我正在使用的手表没有GPS.为了使此程序正常工作,有必要向手机发送请求以打开其GPS并将信息中继回手表. 在有关如何执行此操作的研究中,我遇到了Samsung Remote Sensor SDK.它用于从一台设备获取传感器数据并将其发送到另一台设备.事实证明这不是我想的那样.此功能用于从手表中检索传感器数据并将其传回手机.我需要数据流向另一个方向.经过更多的研究,很明显,我需要做的是制作自己的Android应用程序来为我检索这些信息.我不需要android应用程序具有任何UI,因此它只需要作为服务运行.再深入一点,我发现我需要的是Samsung配件SDK. 一款支持GPS的基于Tizen的手表即将问世,但在撰写本文时尚未发布.我将在本文结尾讨论如何解决该问题.
三星配件SDK
三星配件SDK是三星为设备之间相互交互提供的解决方案.设备可以充当提供者或使用者的角色,并且可以连接到具有互补关系的多个应用程序实例.附件SDK负责发现应用程序的细节,这些应用程序充当设备上的提供者和服务,并抽象化它们之间的通信细节.通信可以通过多种传输方式(Wi-Fi,蓝牙,USB和其他一些方式)进行.消费者可以连接到许多提供者,或者提供者可以连接到许多消费者. 这些交互中涉及的设备通常称为附件对等体.对等方将公开一个或多个提供信息和功能或使用信息的软件实例.无论是提供者还是消费者,提供或使用的软件都称为附件对等代理(由Android端Java代码中的类" SAAgent"表示).对等代理通过服务连接(在Java代码中由" SASocket"类定义)连接.在服务连接中,可以存在许多服务通道.服务通道是连接内的逻辑单元.可以为每个服务通道分配不同的QoS(服务质量)参数,以用于传输的数据,以确保该连接是否可靠(未传送的消息将被重新传送)或不可靠(丢弃的数据包只会丢失) (而不重传),是否需要快速连接,以及是否需要建立连接所需的时间. 应用程序的连接信息必须在服务档案中注册.服务配置文件是用XML定义的,将XML作为资源包含在项目中是您要做的所有事情,以确保注册发生.此配置文件中的信息可唯一标识您的对等代理和通信信道的QoS参数.
创建可视界面
该项目的可视界面将完全在手表上运行.除了GPS处于活动状态的通知外,手机不会显示任何界面.在Tizen for Wearables IDE中,创建一个新的jQuery项目.当触摸屏幕上名为" content_text"的文本元素时,IDE构建的项目将在两个单词之间切换.删除此元素中的短语,并用其他替代它表示软件正在等待GPS连接.当应用程序启动时,这将指示尚未接收GPS信息. UI的代码如下所示.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
<meta name="description" content="A single-page template generated by Tizen Wearable Web IDE"/>
<title>Speedometer</title>
<script type="text/javascript" src="js/jquery-1.9.1.js"></script>
<script type="text/javascript" src="js/main.js"></script>
<link rel="stylesheet" href="css/style.css" />
</head>
<body>
<div class=contents>
<div style='margin:auto;'>
<span class=content_text id=textbox>waiting on gps</span>
</div>
</div>
</body>
</html>
注册消费者
如果引用包含概要文件定义的XML文件,则Tizen项目中的注册会自动发生.执行此操作的两部分是创建文件并让系统知道该文件位于何处.在项目根目录的Tizen项目中,创建一个名为res
的文件夹.在res
中创建一个名为xml
的子文件夹.在xml文件夹内创建一个XML文件.我称我的sapservices.xml.如果愿意,可以使用不同的名称,但切记要在我指定sapservices.xml的地方替换您选择的名称.
文档类型定义可以选择包含在配置文件定义的顶部.我鼓励这样做,因为它可以帮助识别定义中的错误.
<!DOCTYPE resources [
<!ELEMENT resources (application)>
<!ELEMENT application (serviceProfile)+>
<!ATTLIST application name CDATA #REQUIRED>
<!ELEMENT serviceProfile (supportedTransports, serviceChannel+) >
<!ATTLIST application xmlns:android CDATA #IMPLIED>
<!ATTLIST serviceProfile xmlns:android CDATA #IMPLIED>
<!ATTLIST serviceProfile serviceImpl CDATA #REQUIRED>
<!ATTLIST serviceProfile role (PROVIDER | CONSUMER | provider | consumer) #REQUIRED>
<!ATTLIST serviceProfile name CDATA #REQUIRED>
<!ATTLIST serviceProfile id CDATA #REQUIRED>
<!ATTLIST serviceProfile version CDATA #REQUIRED>
<!ATTLIST serviceProfile serviceLimit
(ANY | ONE_ACCESSORY | ONE_PEERAGENT | any | one_peeragent | one_accessory) #IMPLIED>
<!ATTLIST serviceProfile serviceTimeout CDATA #IMPLIED>
<!ELEMENT supportedTransports (transport)+>
<!ATTLIST supportedTransports xmlns:android CDATA #IMPLIED>
<!ELEMENT transport EMPTY>
<!ATTLIST transport xmlns:android CDATA #IMPLIED>
<!ATTLIST transport type (TRANSPORT_WIFI | TRANSPORT_BT | TRANSPORT_BLE | TRANSPORT_USB | transport_wifi | transport_bt | transport_ble | transport_usb) #REQUIRED>
<!ELEMENT serviceChannel EMPTY>
<!ATTLIST serviceChannel xmlns:android CDATA #IMPLIED>
<!ATTLIST serviceChannel id CDATA #REQUIRED>
<!ATTLIST serviceChannel dataRate (LOW | HIGH | low | high) #REQUIRED>
<!ATTLIST serviceChannel priority (LOW | MEDIUM | HIGH | low | medium | high) #REQUIRED>
<!ATTLIST serviceChannel reliability (ENABLE | DISABLE | enable | disable ) #REQUIRED>
]>
在文档类型定义之后出现注册信息.我们需要指定一些信息.这包括应用程序的名称,后跟一个或多个服务配置文件上的信息.一个应用程序(无论是"消费者"还是提供者)可以包含多个服务档案.对于我的应用程序,一个服务配置文件就足够了.服务配置文件需要具有友好的名称和ID.我使用的是Speedometer的名称,而ID是/system/`speedometer的名称.分配给监视应用程序的角色是"消费者"的角色,此应用程序的版本号是" 1.0".文档类型定义指定可以定义" serviceTimeout"值.我们不在需要指定此条件的情况下(我将在何时临时需要说明).所有这些信息将在XML文件中指定如下.
<resources>
<application name="SpeedService">
<serviceProfile
name="speedometer"
id="/system/speedometer"
role="consumer"
serviceTimeout="30"
serviceLimit="one_peeragent"
version="1.0"
>
</serviceProfile>
</application>
</resources>
该定义尚未完成.还需要另外两条信息:应用程序支持的通信传输以及至少一个" serviceChannel"的定义.服务至少需要在" serviceChannel"上进行通信,但可以有多个服务.对于此应用程序,仅需要一个.服务频道由数字ID标识(我已任意选择值" 149").服务质量参数(QoS)增强了其他三个属性.可以是"低"或"高"的" dataRate",可以是"高",“中"或"低"的"优先级”,以及可以设置为"启用"的"可靠性".或"禁用".如果将"可靠性"设置为"启用",如果通信中丢失了一条消息,它将自动重新发送.这有一些额外的开销.将这些元素添加到文件中后,我将进行以下操作.新部分以粗体显示.
<resources>
<application name="SpeedService">
<serviceProfile
name="speedometer"
id="/system/speedometer"
role="consumer"
serviceTimeout="30"
serviceLimit="any"
version="1.0"
>
<supportedTransports>
<transport type="TRANSPORT_BT" />
</supportedTransports>
<serviceChannel
id="149"
dataRate="low"
reliability="enable"
priority="low"
/>
<serviceChannel
id="150"
dataRate="low"
reliability="enable"
priority="low"
/>
</serviceProfile>
</application>
</resources>
既然已经定义了配置文件,我们需要指定它在配置文件(config.xml
)中的位置.在tizen:metadata
元素中使用key
名称d
AccessoryServicesLocation来指定位置,并将值设置为配置文件定义的路径(/res/xml/sapservices.xml).当我们修改
config.xml文件时,需要添加一个带有
tizen:privilege`标签的权限.此权限使应用程序可以访问Samsung附件协议相关的功能. " tizen:privilege"标签只有一个属性" name",其值必须为" http://developer.samsung.com/privilege/accessoryprotocol".
<?xml version="1.0" encoding="UTF-8"?>
<widget xmlns="http://www.w3.org/ns/widgets" xmlns:tizen="http://tizen.org/ns/widgets" id="http://yourdomain/Speedometer" version="1.0.0" viewmodes="maximized">
<tizen:application id="fWZfEb6GWJ.Speedometer" package="fWZfEb6GWJ" required_version="2.2"/>
<content src="index.html"/>
<feature name="http://tizen.org/feature/screen.size.all"/>
<icon src="icon.png"/>
<name>Speedometer</name>
<tizen:privilege name="http://developer.samsung.com/privilege/accessoryprotocol"/>
<tizen:metadata key="AccessoryServicesLocation" value="res/xml/sapservices.xml"/>
</widget>
编写消费者代码
消费者的代码将用JavaScript编写.打开/js/main.js
.该文件中已经有一些代码.在更改它之前,让我们在文件顶部添加一些变量.
手表/消费者将负责启动回电话/提供商的连接.只需几个步骤即可建立连接.手表需要实例化一个附件代理(SAAgent
)并有权访问该代理的连接(SASocket
).对于连接,我们需要使用先前在服务配置文件中定义的相同通道ID.我已经为ID号任意选择了值149.我需要知道应用程序在提供程序端使用的名称(注意:尚未编写提供程序的代码.我将其称为" SpeedService").最后,手机以米/秒为单位获取当前速度.我正在将表侧的速度单位转换为其他速度单位.因此,我正在定义一些将用于转换的值.
var SAAgent = null;
var SASocket = null;
var CHANNELID = 149;
var ProviderAppName = "SpeedService";
var MilesPerHour = {factor:2.23694, label:"MPH"};
var KilometersPerHour = {factor:3.6, label:"KPH"};
//default to Kilometer's per hour
var currentUnit = KilometersPerHour;
当有人点击屏幕时,已经存在的代码将更改屏幕上显示的文本.更改此名称,以使其调用尚未定义的connect()方法.
$(window).load(function(){
document.addEventListener('tizenhwkey', function(e) {
if(e.keyName == "back")
tizen.application.getCurrentApplication().exit();
});
connect();
});
接下来的几节代码都有助于建立连接.它们通过一系列由其他方法设置和触发的回调方法链接在一起.在connect()方法中,我们需要请求一个SAAgent的实例.当我们请求SAAgent
时,将为代码中定义的每个serviceProvider
返回一个.因为我们只定义了一个serviceProvider
,所以将返回一个元素的数组. connect方法将首先使用webapis.sa.requestSAAgent
()方法请求SAAgent.此方法采用两个函数作为参数.第一个函数是调用成功后要调用的函数.第二个函数是发生错误时要调用的函数.在成功功能中,我们将使用第一个(也是唯一一个)" SAAgent"继续连接过程,并要求它使用" SAAgent..findPeerAgents()在另一台设备上查找其对等代理. .在调用此方法之前,需要使用SAAgent.setPeerAgentFindListener()在SAAgent上设置回调对象.
function onsuccess (agents) {
try {
if(agents.length>0) {
SAAgent = agents[0];
SAAgent.setPeerAgentFindListener(peerAgentFindCallback);
SAAgent.findPeerAgents();
}
}catch(err) {
console.log("onsuccess exception [" + err.name + "] msg[" + err.message + "]");
}
}
function onerror(error) {
console.log("ONERROR: err [" + error.name + "] msg [" + error.message + "]");
}
function connect() {
try {
console.log("connect():requesting SAAgent (connect)");
webapis.sa.requestSAAgent(onsuccess , onerror)
} catch(err) {
console.log("onsuccess exception [" + err.name + "] msg[" + err.message + "]");
}
}
``peerAgentFindCallback
对象定义了两种方法.找到" peerAgent"后,将调用" onpeeragentfound"方法.如果找不到" peerAgent"的尝试失败,则调用另一个" onerror".找到对等代理后,我们会通过查看appName是否匹配我们正在寻找的名称来检查它是否为我们正在寻找的对等代理(我之前在变量ProviderAppName
变量中定义了它).如果我要查找的是对等代理,则我提供一个回调以接收与之的连接(agentCallback)并调用SAAgent.requestServiceConnection()来接收回调的实例.
var peerAgentFindCallback = {
onpeeragentfound : function(peerAgent) {
try {
if(peerAgent.appName === ProviderAppName) {
SAAgent.setServiceConnectionListener(agentCallback);
SAAgent.requestServiceConnection(peerAgent);
} else {
}
}
catch(err) {
console.log("onpeeragentfound exception: [" + err.name + "] msg [" + err.message + "]");
}
},
onerror: function(error) {
console.log("peerAgentFindCallback error: err [" + error.name + "] msg [" + error.message + "]" + error);
}
};
agentCallback
没有很多工作要做.它收到处于连接状态的" SASocket".如果套接字状态更改,将保存对其的引用,并将回调设置为断开连接.然后,我提供将接收数据的回调.
var agentCallback = {
onconnect: function (socket) {
SASocket = socket;
SASocket.setSocketStatusListener(function (reason) {
disconnect();
});
SASocket.setDataReceiveListener(onreceive); //start listening
},
onerror:onerror1
};
在继续之前,我们需要知道将要接收什么数据以及如何格式化和组织数据.让我们单独看手表项目,然后开始使用Android服务.
构建Android服务
我正在使用IntelliJ派生的Android Studio来构建Android服务.当内部"类"的构造函数必须访问其父"类"的"类"对象时,IntelliJ具有奇怪的依赖行为.因此,我的"类"组织结构与您在三星提供的示例代码中会发现的稍有不同,但是该代码与两个IDE都兼容.创建一个针对Android 4.3或更高版本的新Android项目.此应用程序不需要任何活动.这将是仅服务应用程序. 在添加任何代码之前,我想先向AndroidManifest.xml添加一些权限.这些权限是访问位置数据,使用蓝牙(与手表进行通信所必需的)以及访问某些其他与SDK相关的功能所必需的.
<!--jij:Communication occurs over Bluetooth. There are some bluetooth permissions that we need -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<!--jij:Permissions to access location information -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>
<!-- SDK and debugging related permissions -->
<uses-permission android:name="com.samsung.wmanager.APP" />
<uses-permission android:name="com.samsung.wmanager.ENABLE_NOTIFICATION" />
<uses-permission android:name="com.samsung.accessory.permission.ACCESSORY_FRAMEWORK"/>
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY"/>
<uses-permission android:name="android.permission.SET_DEBUG_APP" />
需要将引用添加到两个Samsung SDK,Samsung SDK和Samsung Accessory SDK.该项目需要为与手表的连接定义一个类,并为定位服务定义一个类.连接类将从SASocket扩展,而定位服务将从SAAgent扩展.将一个新类添加到名为" SAPServiceProviderConnection"的项目中,并使其继承自" SASocket".给该类一个名为connectionID
的整数字段和一个名为mParent
的SpeedService
类型的字段. SpeedService类尚未定义. IDE会为您提供任何警告,请不要担心.
扩展SASocket实现
在三星为每个连接赋予唯一的数字ID值之后,我一直保持着领先.但是我使用的方式有所不同.在三星的示例中,该ID用于唯一地标识要从连接列表中添加或删除的连接.通过引用传递对象,可以在不提供数字ID的情况下跟踪它.仅对象引用就足够了.在我的代码中分配了" connectionID",仅用于调试目的. 这个扩展的SASocket类不需要做很多事情.当它关闭时,它将请求从父对象的活动连接集合中删除.对于此项目,数据仅需要从手机流向手表.因此," SAPServiceProviderConnection"类会忽略" onReceive()“中的所有传入数据.完整的类如下.
package net.j2i.speedometer;
import android.util.Log;
import com.samsung.android.sdk.accessory.SASocket;
import java.io.IOException;
public class SAPServiceProviderConnection extends SASocket {
private int connectionID;
static int nextID = 1;
public final static String TAG = "SAPServiceProvider";
private SpeedService mParent;
public void setParent(SpeedService speedService) {
mParent = speedService;
}
public SAPServiceProviderConnection() {
super(SAPServiceProviderConnection.class.getName());
connectionID = ++nextID;
}
@Override
protected void onServiceConnectionLost(int reason) {
if(mParent!=null) {
mParent.removeConnection(this);;
}
}
@Override
public void onReceive(int channelID, byte[] data) {
}
@Override
public void onError(int channelID, String errorString, int errorCode) {
Log.e(TAG,"ERROR:"+errorString+ " | " + errorCode);
}
}
实施提供者服务
创建一个名为Speed服务的新类.该类需要从” SAAgent"扩展. SAAgent类是从Service扩展的.此类将包含Android程序的入口点,并负责获取和广播速度信息.类中有一些成员变量.有一个Binder对象(在服务中很常见),一个连接集合的成员以及要使用的服务通道的ID在服务中有一个LocationListener的实例,它将位置信息编码为JSON字符串被转发到电话.在交流中还有其他信息.速度,方位和位置与GPS侦听器的状态一起传输.此服务可用于制作高度表,指南针或其他手表应用程序.
SpeedBinder mBinder = new SpeedBinder();
public final static String TAG = "SpeedService";
public final static int SAP_SERVICE_CHANNEL_ID = 149;
AbstractCollection<SAPServiceProviderConnection> mConnectionBag = new Vector<SAPServiceProviderConnection>();
LocationManager mLocationManager;
boolean mIsListening = false;
public class SpeedBinder extends Binder {
SpeedService getService() {
return SpeedService.this;
}
}
LocationListener locationListener = new LocationListener() {
long mTime;
float mBearing, mSpeed;
double mLatitude, mLongitude, mAltitude;
boolean bHasAltitude, bHasBearing, bHasSpeed;
boolean bGpsEnabled = true;
@Override
public String toString() {
//In general this isn't how I would encode something in JSON, but the amount
//of data is small enough such that I've decided to use String.Format to
//produce what's needed.
final String returnValue =
String.format("{ \"gpsEnabled\" :%b,"+
"\"hasSpeed\":%b, \"speed\":%1.2f, \"hasBearing\":%b, \"bearing\":%1.4f,"+
"\"latitude\":%f, \"longitude\":%f,\"hasAltitude\":%b, \"altitude\":%1.3f}",
bGpsEnabled,
bHasSpeed, mSpeed, bHasBearing, mBearing,
mLatitude, mLongitude, bHasAltitude, mAltitude
);
return returnValue;
}
@Override
public void onLocationChanged(Location location) {
//not that the if conditions are also assignment operations
if(bHasSpeed = location.hasSpeed())
mSpeed = location.getSpeed();
if(bHasAltitude = location.hasAltitude())
mAltitude = location.getAltitude();
if(location.hasSpeed())
mSpeed = location.getSpeed();
if(bHasBearing = location.hasBearing())
mBearing = location.getBearing();
mLatitude = location.getLatitude();
mLongitude = location.getLongitude();
mTime = location.getTime();
transmitLocation();
}
@Override
public void onStatusChanged(String s, int i, Bundle bundle) { }
@Override
public void onProviderEnabled(String s) { bGpsEnabled = true; }
@Override
public void onProviderDisabled(String s) { bGpsEnabled = false; }
};
TransmissionLocation()方法将当前位置的JSON从"字符串"转换为"字节"数组,并将其发送到服务具有的每个活动连接.
public void transmitLocation() {
String locationString = locationListener.toString();
byte[] locationMessage = locationString.getBytes();
Log.i(TAG, locationString);
Iterator connectionIterator = mConnectionBag.iterator();
while(connectionIterator.hasNext()) {
SAPServiceProviderConnection connection = (SAPServiceProviderConnection)connectionIterator.next();
try {
connection.send(SAP_SERVICE_CHANNEL_ID, locationMessage);
} catch(IOException exc) {
//
}
}
}
启动或关闭连接时,会在连接集合中添加或删除它们.当" mConnectionBag"成员发生更改时,该类将评估GPS是否需要激活.如果没有活动的侦听器,则没有理由使GPS硬件保持活动状态.这样做会浪费电池电量,并且如果注意到电话正在监视用户的位置(而他/她没有执行任何与位置有关的操作),则可能会引起关注.每当将新连接添加到连接集合时,reevaluateLocationManager()就会调用startTracking().在已启用跟踪的情况下调用此方法不会产生负面影响;如果跟踪处于活动状态,则该方法不执行任何操作.一旦连接计数达到零,就会调用stopTracking().
public void removeConnection(SAPServiceProviderConnection connection) {
mConnectionBag.remove(connection);
reevaluateLocationManager();
}
public void addConnection(SAPServiceProviderConnection connection) {
mConnectionBag.add(connection);
transmitLocation();
}
void startTracking() {
if(!mIsListening) {
mIsListening = true;
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 1, locationListener);
}
}
void stopTracking() {
if(mIsListening) {
mLocationManager.removeUpdates(locationListener);
mIsListening = false;
}
}
void reevaluateLocationManager() {
if(mConnectionBag.size()==0)
stopTracking();
else
startTracking();
}
该服务应一直运行到明确停止为止.服务onStart
方法返回START_STICKY
以让android系统知道这一点.
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(getBaseContext(), "started", Toast.LENGTH_LONG).show();
Log.i(TAG, "started");
return START_STICKY;
}
配置提供者代理配置文件
提供商的代理商资料看起来与为"消费者"制作的资料几乎相同.像Tizen一样,我将配置文件放在/re/xml
中. XML文件的名称将为sapservices.xml
.该文件和Tizen项目中one
的内容差异不是分配给" consumer角色,而是分配给" provider
角色,并且提供了一个名为" serviceImpl"的新属性来指向包含代理实现的Java类.此配置文件的服务限制为" any",允许它为可能连接到该附件的任何对等端提供服务.消费者的限制设置为一个.它只会从单个设备获取速度信息.
<resources>
<application name="SpeedService">
<serviceProfile
name="speedometer"
id="/system/speedometer"
role="provider"
serviceLimit="any"
version="1.0"
serviceImpl="net.j2i.speedometer.SpeedService"
>
<supportedTransports>
<transport type="TRANSPORT_BT"/>
</supportedTransports>
<serviceChannel
id="149"
dataRate="low"
reliability="enable"
priority="low"
/>
</serviceProfile>
</application>
</resources>
最后需要进行的更改是AndroidManifest.xml必须具有一个条目,该条目指示定义服务配置文件的位置以及需要声明服务和响应的意图.
<service android:name=".SpeedService" />
<receiver android:name = "com.samsung.android.sdk.accessory.ServiceConnectionIndicationBroadcastReceiver">
<intent-filter>
<action android:name="android.accessory.service.action.ACCESSORY_SERVICE_CONNECTION_IND"/>
</intent-filter>
</receiver>
<receiver android:name = "com.samsung.android.sdk.accessory.RegisterUponInstallReceiver">
<intent-filter>
<action android:name="android.accessory.device.action.REGISTER_AFTER_INSTALL"/>
</intent-filter>
</receiver>
<meta-data android:name="AccessoryServicesLocation" android:value="/res/xml/sapservices.xml" />
更新手表的显示
此时,watch项目能够建立连接并从提供者接收数据,但是对接收到的内容不做任何事情.创建一个onReceive函数.它接受的参数将是通道ID的整数,以及保存接收到的数据的字符串.接收到数据后,我检查以确保接收到的数据位于我期望的" CHANNELID"上.这不是严格必要的,因为数据无法进入尚未定义的通道,而我仅定义了一个.但是我这样做是在以后决定更改程序以允许其他数据传输的情况下.当位置数据输入时,我将使用"速度"组件,将其传递给格式化程序/转换功能,然后将文本框的内容设置为格式化的"速度".
function onreceive(channelid, data) {
console.log("received:"+data);
switch(channelid) {
case CHANNELID: {
try {
var locationData = jQuery.parseJSON(data);
$('#textbox').html( formatSpeed(locationData.speed));
}
catch(e) {
$('#textbox').html("failed to parse");
}
}
break;
default:
break;
}
}
将Watch应用程序和Android应用程序打包在一起
应用程序的两个部分都可以打包在一起并部署到电话中.如果将已编译的手表应用程序复制到Android项目的路径"/res/assets",则Gear Manager将负责发现手表应用程序并将其复制到手表.启动调试器,以便将项目部署到电话.由于应用程序中没有声明任何活动,因此您可能会提示您部署后的操作.不必启动任何活动,因此可以部署应用程序.部署服务和监视应用程序之后,将发生一会儿.该应用程序将部署到手表中,并且服务将启动.
请注意,如果您更新监视应用程序,则需要将更新后的应用程序复制到服务项目的/res/assets
文件夹中.如果不这样做,则对该服务的任何重新部署都将导致手表具有该文件夹中已存在的手表应用程序的版本.
添加设置屏幕
手表应用程序设置为以公里每小时显示速度.显示了以其他单位显示速度所需的信息,但是无法选择使用哪种单位转换.在用户界面中,我想使用其他页面来更改此设置.让我们回顾一下watch应用程序的标签的内容.
<div class=contents>
<div style='margin:auto;'>
<span class=content_text id=textbox>waiting on gps</span>
</div>
</div>
有一些预定义的CSS类可用于导航.感兴趣的主要CSS类是ui-page''和
ui-page``-active.我的应用程序中的每个页面或视图都将是同一HTML文件的一部分,但由带有ui-page类的元素定义.我将使用ui-page和ui-page标记默认情况下应处于活动状态的页面-应用程序内的导航将通过更改分配给每个页面的类来实现.
最初,许多导航看起来都像是基于常规锚标签的导航. " href"属性的值将为其导航到的" “的” id".这是HTML内容的简单版本,其中两个div元素之间具有导航功能.
<body>
<div class="ui-page ui-page-active" id="main">
<div class="ui-header" data-position="fixed"><h2>Page One</h2></div>
<div class="ui-content">
<a href="#secondPage">to second page</a>
</div>
</div>
<div class="ui-page" id="secondPage">
<div class="ui-header" data-position="fixed"><h2>Page One</h2></div>
<div class="ui-content">
</div>
</div>
</body>
这种导航形式的部分机制是在Tizen Advanced UI框架(TAU)中实现的.为此,您需要在项目中存在TAU代码,并在HTML中引用该代码.该框架通常位于路径" lib/tau/wearable/js"中.如果您的项目中不存在此路径,则可以使用"基本"模板创建另一个项目,然后从那里复制文件.切记在页面标题中放置对TAU的CSS和JavaScript的引用
<link rel="stylesheet" href="lib/tau/wearable/theme/default/tau.min.css">
<script type="text/javascript" src="lib/tau/wearable/js/tau.js"></script>
设置屏幕将存在于同一HTML文件中.现在,我只想允许某人在SI和英制速度单位之间切换.速度单位已在页面的JavaScript中定义.我将以编程方式构建部分接口,而不是冗余定义.
function populateUnits() {
var unitList = document.getElementById('unitsList');
UnitList.forEach(function(e) {
var li = document.createElement('li');
var radio = document.createElement('input');
var label = document.createElement('label');
radio.setAttribute('type','radio' );
radio.setAttribute('name', 'units');
radio.setAttribute('value', e.label);
radio.setAttribute('id', 'unit_' + e.label);
if(e.label == currentUnit.label)
radio.setAttribute('checked','true');
radio.setAttribute('onclick', 'setUnit("'+ e.label + '");');
label.innerText = e.label;
label.setAttribute('for', 'unit_', e.label );
label.innerText = e.label
li.appendChild(radio);
li.appendChild(label);
radio.setAttribute('href','#');
unitList.appendChild(li);
});
}
显示可用单元的单选按钮.选中单选按钮时,将调用名为" setUnit"的函数,该函数将保存选择并更改用于显示速度的转换单元.
function setUnit(unit ) {
localStorage.setItem("speedUnit", unit);
applySpeedSetting();
}
function applySpeedSetting() {
var unit = localStorage.getItem('speedUnit');
if(unit!=null) {
UnitList.forEach( function(x) {
if(x.label == unit) {
currentUnit = x;
return;
}
});
}
}
W3C Web存储API中描述了" localStorage"对象,该对象可用于故事化键/值对. " applySettings"函数既响应于所选速度单位的更改而被调用,也将在程序寿命开始时被调用以应用保存的选择.它遍历代码中定义的速度单位,直到找到其标签与保存的内容相匹配的单位.找到匹配项后,它将将此对象设置为要使用的当前单位,并停止搜索.
与Gear S的兼容性
我最初是在Galaxy Gear S发布之前写的. Gear S的其他功能还拥有自己的GPS无线电;它应该能够在不与电话通信的情况下检测速度.我希望该程序能够利用即将推出的电话功能.对于基于HTML的项目,位置信息通过W3C Geolocation API规范公开. 在Tizen中,位置信息需要特权.由于此应用程序可以使用手机中的位置信息,而手表中的位置信息不是强制性的,因此我将其作为特权而不是要求列在程序清单中.将以下行添加到config.xml中以指定此特权.
<tizen:privilege name="http://tizen.org/privilege/location"/>
要使用Geolocation API,请首先检查" navigator"对象上是否存在" geolocation"字段.如果找不到该字段,那么我们将无法使用此方法,而只能使用Samsung Accessory SDK和Android服务.如果找到该对象,则尝试通过调用navigator
.geolocation`.watchwatchs()来请求更新位置. watchPosition方法正在接收将向其发送位置信息的回调函数,在出现错误时要调用的方法,以及指示接收到的位置信息的最大使用期限的参数.接收到位置信息后,它将与来自电话的信息一样对待.如果尝试使用手表的GPS时出现任何错误,程序将退回到手机上.
通过进行以下代码更改来实现这些更改
function locationCallbackSuccess(position ) {
if(position.speed)
$('#textbox').html( formatSpeed(position.speed));
}
function locationCallbackFailed( reason ) {
//connect to the phone and rely on it's GPS
connect();
}
$(window).load(function(){
document.addEventListener('tizenhwkey', function(e) {
if(e.keyName == "back") {
var currentPage = document.getElementsByClassName('ui-page-active')[0];
var pageId = (currentPage)?currentPage.id:' ';
if(pageId=='main')
tizen.application.getCurrentApplication().exit();
else {
tau.changePage("#main");
}
}
});
tau.defaults.pageTransition = "slideup";
if(navigator.geolocation) {
navigator.geolocation.watchPosition(
locationCallbackSuccess,
locationCallbackFailed,
{maximumAge:250}
);
}
else
connect();
populateUnits();
applySpeedSetting();
});
性能和电池
在购买Galaxy Gear S之后,我尝试将车速表用作独立应用程序.结果不是我想要的. Gear S似乎以比我想要的更低的频率更新位置和速度信息.即使我以半秒的间隔请求更新信息,我也只会在2到3秒内收到一次位置信息.虽然Gear S确实拥有自己的GPS硬件,但我建议的解决方案是首先尝试使用手机的硬件获取位置信息,并且仅当手机无法用于获取位置信息时才使用Gears硬件(例如:手表已断开连接)从手机上删除或位置服务已在手机上禁用).除了获得更好的更新频率外,对Gear S电池的需求也会更低.
改进空间
请记住,此处提供的代码要比" Hello World"程序超出某些步骤,因此仍有很大的改进空间.如果您想完善此代码以使其可在应用商店中使用,则可能需要考虑做一些事情.如果停止向手表发送位置信息,则表明没有信息丢失.指示位置信息的丢失将帮助并减少用户认为丢失位置信息是由于程序故障引起的机会.我已经以纯文本形式显示了速度信息.也有熟悉的图形方式可以显示它.用户可能还希望手表的屏幕在该程序运行时保持打开状态,而不是超时.最后,尝试自己使用该程序,以查看发现的改进机会(但是,如果您自己操作车辆,请不要使用它;只能将其用作乘客!).
获得协助
如果您有问题或需要帮助,请随时在下面发布问题.我会尽快解决.您还可以在Tizen开发者论坛和Samsung开发者论坛中发布问题.
历史
- 2014年10月16日
- 2014十二月3
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
Javascript Java HTML Mobile Intermediate Android Tizen 新闻 翻译