目的
様々なサービス上のAPIで取得できる数値データ等を一元的に管理した上でAndroidWearに表示したい。
新たに利用したAPIが増えたり、変更された時にメンテナンスコストを低く、セキュリティ面を担保した上でAndroid側のプログラムを簡単な物にするために今年の7月に使えるようになったAmazon API Gatewayを利用する。(日本リージョンはまだ未対応)
関連記事
- AndroidWear Huawei Watchの簡易レビュー
- AndroidWear Huawei WatchでWatchFaceを作成
- AndroidWearとAmazon API GatewayでGithub Followerを見える化 ← 現在こちらの記事
- Amazon API Gatewayを利用しAndroidアプリから簡単にWeb上のAPIを叩く方法
動作概要図
AndroidWearがインターネット上のデータを取得するには、親機を経由しなければならないため、親機でAmazon API Gatewayへアクセス -> Wearに取得したデータをmessageとして送信という全体像となります。
本来であればwear側から定期的にmobile側へ情報更新要求を出し、wear側へ送信や mobile側からnodeが存在すればwear側へ定期的に送信等処理を実装して完成に至ると思いますが、通信可能なWatchFace作成方法の情報が英語・日本語共に少なかったので、土台部分の実装のみひとまずデモ的に動いたので公開致します。
Amazon API Gatewayの利用
Android側のアプリケーションでHTTPリクエスト組み立てたり複数のAPI管理が面倒だな、、、と考えた時にAPIをまとめて管理出来て、Android/iOS/Javascript用のAPIアクセスライブラリ/SDKを吐き出してくれる「Amazon API Gateway」が頭をよぎりましたので利用してみました、ほんの触りでプロクシ機能しか使っておりませんが非常に短いコードで行けたので利用しない手は無いと思いました。
これまでイマイチ使い処が見えにくかった Lambda がAPI Gatewayとの組み合わせで色々使えるかなぁと感じております。下記を今後実施してみる予定、、、
API Gateway -> Lambda -> SNS -> Android端末等へ通知/Twilio 等々
Android Wearへのデータ送信
下記方法で mobile側から wearにデータを送信し WatchFaceを更新しています
mobile側処理
- アプリ起動時にGoogleApiClientを利用可能にする
- 通信可能な端末を getNodes で取得
- GoogleApiClient の sendMessage で Wearにメッセージを送信
wear側処理
- Messageを受信すると DataLayerListenerService の onMessageReceived が呼び出される
- Preferencesに受信したメッセージ内容を書き込み
- WatchFace の 描画部分でPreferenceより取得した値を表示
完成品
母艦でRefreshボタンを押すと、弊社の現在のgithub follower数がWatchFaceに表示されます
![]()
Android Studioでプロジェクトを作成
Mobile / Wear両方のチェックボックスを付けたプロジェクトを作成します。
2つのモジュールを跨ぐクラスの作成については、New Module -> Common等の名前で作成しdependenciesの設定を下記のように行います。
Amazon API Gatewayの設定
API Gateway の詳細設定は別記事としました下記を参考に下さい
Amazon API Gatewayを利用しAndroidアプリから簡単にWeb上のAPIを叩く方法
下記GitHub APIを API Gatewayに登録して利用しました。
https://api.github.com/users/xxxx
Android Studioに下記コードを記載
common module
WearConstants.java
package net.skyarch.common;
public class WearConstants {
public static final String PREFS = "net.skyarch.test";
public static final String PREFS_KEY_MESSAGE_TEXT = "net.skyarch.test.message";
public static final String DATA_CHANGED_ACTION = "net.skyarch.test.data.changed";
}
mobile (スマホ/タブレット)
res/layout/activity_main.xmlにButton btRefreshを配置して下さい。
MainActivity.java
package net.skyarch.testapp;
import java.util.Collection;
import java.util.HashSet;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.StrictMode;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
//Wearとの通信に必要
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import com.google.android.gms.common.api.GoogleApiClient.OnConnectionFailedListener;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.DataApi.DataItemResult;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;
//MobileとWearで共通で利用するクラス
import net.skyarch.common.WearConstants;
//Amazon API Gatewayの利用に必要
import com.amazonaws.mobileconnectors.apigateway.ApiClientFactory;
import net.skyarch.api.SkyarchKPIClient;
public class MainActivity extends Activity implements OnClickListener,
ConnectionCallbacks, ResultCallback<DataItemResult>,
OnConnectionFailedListener {
private final String TAG = "Mobile";
//Google Play Service
private GoogleApiClient mGoogleApiClient;
//Wear Node
private Collection<String> mNodes;
//Button
private Button _btRefresh;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//GoogleApiClientの作成
mGoogleApiClient = new GoogleApiClient.Builder(this)
.addApi(Wearable.API)
.addConnectionCallbacks(this)
.addOnConnectionFailedListener(this)
.build();
mGoogleApiClient.connect();
//Refreshボタン
_btRefresh = (Button) findViewById(R.id.btRefresh);
_btRefresh.setOnClickListener(this);
_btRefresh.setEnabled(true);
}
@Override
public void onClick(View v) {
Log.d(TAG, "Clicked");
switch (v.getId()) {
case R.id.btRefresh:
Log.d(TAG, "Refresh Button Pushed");
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
//Githubから follower数を取得
ApiClientFactory factory = new ApiClientFactory();
final SkyarchKPIClient client = factory.build(SkyarchKPIClient.class);
int followerCount = client.githubGet().getFollowers();
Log.d(TAG, "git followers" + Integer.toString(followerCount));
//Wear Nodeを取得
mNodes = getNodes();
//メッセージをWearに送信
sendMessageToWear(mNodes,
WearConstants.DATA_CHANGED_ACTION,
Integer.toString(followerCount));
return null;
}
}.execute();
break;
}
}
@Override
public void onConnected(Bundle connectionHint) {
Log.d(TAG, "onConnected:");
}
@Override
public void onConnectionSuspended(int cause) {
Log.d(TAG, "onConnectionSuspended:");
}
@Override
public void onConnectionFailed(ConnectionResult result) {
Log.d(TAG, "onConnectionFailed:");
}
@Override
public void onResult(DataItemResult result) {
Log.d(TAG, "Result:" + result.getStatus().isSuccess());
}
/**
* Wear nodeを取得
*
* @return WearNode
*/
private Collection<String> getNodes() {
HashSet<String> results = new HashSet<String>();
NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi
.getConnectedNodes(mGoogleApiClient).await();
String nodesStr = "";
for (Node node : nodes.getNodes()) {
Log.d(TAG, "node.getId():" + node.getId());
nodesStr += node.getId() + ",";
results.add(node.getId());
}
final String nodesTmp = nodesStr;
runOnUiThread(new Runnable() {
public void run() {
//UIの更新
}
});
return results;
}
/**
* Wearにメッセージを送信
*/
public void sendMessageToWear(Collection<String> nodes,
String action, String message) {
for (String node : nodes) {
Wearable.MessageApi.addListener(mGoogleApiClient, new MessageApi.MessageListener() {
@Override
public void onMessageReceived(MessageEvent messageEvent) {
final String data = new String(messageEvent.getData());
Log.d(TAG, "Message received: " + messageEvent);
Log.d(TAG, "Data: " + data);
runOnUiThread(new Runnable() {
public void run() {
//UIの更新
}
});
}
});
MessageApi.SendMessageResult result = Wearable.MessageApi
.sendMessage(mGoogleApiClient, node, action,
message.getBytes()).await();
if (!result.getStatus().isSuccess()) {
Log.d(TAG, "ERROR: failed to send Message: " + result.getStatus());
runOnUiThread(new Runnable() {
public void run() {
//UIの更新
}
});
} else {
Log.d(TAG, "result.getStatus():" + result.getStatus());
runOnUiThread(new Runnable() {
public void run() {
//UIの更新
}
});
}
}
}
}
Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.skyarch.testapp" >
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<uses-permission android:name="android.permission.INTERNET"/>
<meta-data android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version"/>
<activity
android:name=".MainActivity"
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>
</manifest>
build.gradle
...
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
wearApp project(':wear')
compile 'com.android.support:appcompat-v7:23.0.1'
compile 'com.google.android.gms:play-services:7.8.0'
compile project(':common')
}
wear (スマートウォッチ)
res/drawable に test.png を入れて下さい
DataLayerListenerService.java
package net.skyarch.testapp;
import android.content.Intent;
import android.util.Log;
import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.WearableListenerService;
import net.skyarch.common.WearConstants;
public class DataLayerListenerService extends WearableListenerService {
private static final String TAG = "DataLayerService";
@Override
public void onMessageReceived(MessageEvent messageEvent) {
super.onMessageReceived(messageEvent);
String strMessage = new String(messageEvent.getData());
Log.d(TAG, "onMessageReceived");
Log.d(TAG, strMessage);
if (strMessage != null) {
getBaseContext().getSharedPreferences(WearConstants.PREFS, MODE_PRIVATE).edit().putString(WearConstants.PREFS_KEY_MESSAGE_TEXT,
strMessage).commit();
getBaseContext().sendBroadcast(new Intent(WearConstants.DATA_CHANGED_ACTION));
}
}
@Override
public void onPeerConnected(Node peer) {}
@Override
public void onPeerDisconnected(Node peer){}
}
TestWatch.java
package net.skyarch.testapp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.wearable.watchface.CanvasWatchFaceService;
import android.support.wearable.watchface.WatchFaceStyle;
import android.text.format.Time;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.WindowInsets;
import net.skyarch.common.WearConstants;
import java.lang.ref.WeakReference;
import java.text.DateFormat;
import java.util.Date;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
/**
* Digital watch face with seconds. In ambient mode, the seconds aren't displayed. On devices with
* low-bit ambient mode, the text is drawn without anti-aliasing in ambient mode.
*/
public class TestWatch extends CanvasWatchFaceService {
private static final String TAG = "TestWatch";
private static final Typeface NORMAL_TYPEFACE =
Typeface.create(Typeface.SANS_SERIF, Typeface.NORMAL);
/**
* Update rate in milliseconds for interactive mode. We update once a second since seconds are
* displayed in interactive mode.
*/
private static final long INTERACTIVE_UPDATE_RATE_MS = TimeUnit.SECONDS.toMillis(1);
/**
* Handler message id for updating the time periodically in interactive mode.
*/
private static final int MSG_UPDATE_TIME = 0;
@Override
public Engine onCreateEngine() {
return new Engine();
}
private class Engine extends CanvasWatchFaceService.Engine {
final Handler mUpdateTimeHandler = new EngineHandler(this);
final BroadcastReceiver mTimeZoneReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
mTime.clear(intent.getStringExtra("time-zone"));
mTime.setToNow();
}
};
boolean mRegisteredTimeZoneReceiver = false;
Paint mBackgroundPaint;
//背景に表示するpng
Bitmap mBackgroundBitmap;
boolean mAmbient;
Time mTime;
//日付
Paint mTextPaintDate;
float mXOffsetDate;
float mYOffsetDate;
//時刻
Paint mTextPaintTime;
float mXOffsetTime;
float mXOffsetTimeAmbient;
float mYOffsetTime;
//mobileアプリからのMessage
Paint mTextPaintMessage;
float mXOffsetMessage;
float mYOffsetMessage;
/**
* Whether the display supports fewer bits for each color in ambient mode. When true, we
* disable anti-aliasing in ambient mode.
*/
boolean mLowBitAmbient;
@Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
setWatchFaceStyle(new WatchFaceStyle.Builder(TestWatch.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_VARIABLE)
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setShowSystemUiTime(false)
.build());
Resources resources = TestWatch.this.getResources();
mYOffsetTime = resources.getDimension(R.dimen.digital_y_offset_time);
mBackgroundPaint = new Paint();
mBackgroundPaint.setColor(resources.getColor(R.color.digital_background));
Drawable backgroundDrawable = resources.getDrawable(R.drawable.test, null);
mBackgroundBitmap = ((BitmapDrawable) backgroundDrawable).getBitmap();
//Date
mTextPaintDate = new Paint();
mTextPaintDate = createTextPaint(resources.getColor(R.color.digital_text_date));
//Time
mTextPaintTime = new Paint();
mTextPaintTime = createTextPaint(resources.getColor(R.color.digital_text_time));
//Message
mTextPaintMessage = new Paint();
mTextPaintMessage = createTextPaint(resources.getColor(R.color.digital_text_message));
mTime = new Time();
}
@Override
public void onDestroy() {
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
super.onDestroy();
}
private Paint createTextPaint(int textColor) {
Paint paint = new Paint();
paint.setColor(textColor);
paint.setTypeface(NORMAL_TYPEFACE);
paint.setAntiAlias(true);
return paint;
}
@Override
public void onVisibilityChanged(boolean visible) {
super.onVisibilityChanged(visible);
if (visible) {
registerReceiver();
// Update time zone in case it changed while we weren't visible.
mTime.clear(TimeZone.getDefault().getID());
mTime.setToNow();
} else {
unregisterReceiver();
}
// Whether the timer should be running depends on whether we're visible (as well as
// whether we're in ambient mode), so we may need to start or stop the timer.
updateTimer();
}
private void registerReceiver() {
if (mRegisteredTimeZoneReceiver) {
return;
}
mRegisteredTimeZoneReceiver = true;
IntentFilter filter = new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED);
TestWatch.this.registerReceiver(mTimeZoneReceiver, filter);
}
private void unregisterReceiver() {
if (!mRegisteredTimeZoneReceiver) {
return;
}
mRegisteredTimeZoneReceiver = false;
TestWatch.this.unregisterReceiver(mTimeZoneReceiver);
}
@Override
public void onApplyWindowInsets(WindowInsets insets) {
super.onApplyWindowInsets(insets);
// Load resources that have alternate values for round watches.
Resources resources = TestWatch.this.getResources();
boolean isRound = insets.isRound();
// for Date 時刻の上に表示
mXOffsetDate = resources.getDimension(isRound
? R.dimen.digital_x_offset_date_round : R.dimen.digital_x_offset_date);
mYOffsetDate = resources.getDimension(R.dimen.digital_y_offset_date);
float textSizeDate = resources.getDimension(isRound
? R.dimen.digital_text_size_date_round : R.dimen.digital_text_size_date);
mTextPaintDate.setTextSize(textSizeDate);
// for Time
mXOffsetTime = resources.getDimension(isRound
? R.dimen.digital_x_offset_time_round : R.dimen.digital_x_offset_time);
mXOffsetTimeAmbient = resources.getDimension(isRound
? R.dimen.digital_x_offset_time_round : R.dimen.digital_x_offset_time) +
resources.getDimension(R.dimen.digital_x_offset_time_ambient_shift);
mYOffsetTime = resources.getDimension(R.dimen.digital_y_offset_time);
float textSizeTime = resources.getDimension(isRound
? R.dimen.digital_text_size_time_round : R.dimen.digital_text_size_time);
mTextPaintTime.setTextSize(textSizeTime);
// for Message
mXOffsetMessage = resources.getDimension(isRound
? R.dimen.digital_x_offset_message_round : R.dimen.digital_x_offset_message);
mYOffsetMessage = resources.getDimension(R.dimen.digital_y_offset_message);
float textSizeMessage = resources.getDimension(isRound
? R.dimen.digital_text_size_message_round : R.dimen.digital_text_size_message);
mTextPaintMessage.setTextSize(textSizeMessage);
}
@Override
public void onPropertiesChanged(Bundle properties) {
super.onPropertiesChanged(properties);
mLowBitAmbient = properties.getBoolean(PROPERTY_LOW_BIT_AMBIENT, false);
}
@Override
public void onTimeTick() {
super.onTimeTick();
invalidate();
}
@Override
public void onAmbientModeChanged(boolean inAmbientMode) {
super.onAmbientModeChanged(inAmbientMode);
if (mAmbient != inAmbientMode) {
mAmbient = inAmbientMode;
if (mLowBitAmbient) {
mTextPaintTime.setAntiAlias(!inAmbientMode);
mTextPaintDate.setAntiAlias(!inAmbientMode);
}
invalidate();
}
// Whether the timer should be running depends on whether we're visible (as well as
// whether we're in ambient mode), so we may need to start or stop the timer.
updateTimer();
}
@Override
public void onDraw(Canvas canvas, Rect bounds) {
//draw the background.
canvas.drawRect(0, 0, bounds.width(), bounds.height(), mBackgroundPaint);
canvas.drawBitmap(mBackgroundBitmap, bounds.width() / 2 - mBackgroundBitmap.getWidth() / 2, 20, null);
//draw date
DateFormat format = android.text.format.DateFormat.getDateFormat(getApplicationContext());
canvas.drawText(format.format(new Date()), mXOffsetDate, mYOffsetDate, mTextPaintDate);
//draw H:MM in ambient mode or H:MM:SS in interactive mode.
mTime.setToNow();
//draw time
String strTime = mAmbient
? String.format("%d:%02d", mTime.hour, mTime.minute)
: String.format("%d:%02d:%02d", mTime.hour, mTime.minute, mTime.second);
if (mAmbient) {
canvas.drawText(strTime, mXOffsetTimeAmbient, mYOffsetTime, mTextPaintTime);
} else {
canvas.drawText(strTime, mXOffsetTime, mYOffsetTime, mTextPaintTime);
}
//draw message
String strMessage = TestWatch.this.getSharedPreferences(WearConstants.PREFS, MODE_PRIVATE)
.getString(WearConstants.PREFS_KEY_MESSAGE_TEXT, "REFRESH");
canvas.drawText(strMessage, mXOffsetMessage, mYOffsetMessage, mTextPaintMessage);
}
/**
* Starts the {@link #mUpdateTimeHandler} timer if it should be running and isn't currently
* or stops it if it shouldn't be running but currently is.
*/
private void updateTimer() {
mUpdateTimeHandler.removeMessages(MSG_UPDATE_TIME);
if (shouldTimerBeRunning()) {
mUpdateTimeHandler.sendEmptyMessage(MSG_UPDATE_TIME);
}
}
/**
* Returns whether the {@link #mUpdateTimeHandler} timer should be running. The timer should
* only run when we're visible and in interactive mode.
*/
private boolean shouldTimerBeRunning() {
return isVisible() && !isInAmbientMode();
}
/**
* Handle updating the time periodically in interactive mode.
*/
private void handleUpdateTimeMessage() {
invalidate();
if (shouldTimerBeRunning()) {
long timeMs = System.currentTimeMillis();
long delayMs = INTERACTIVE_UPDATE_RATE_MS
- (timeMs % INTERACTIVE_UPDATE_RATE_MS);
mUpdateTimeHandler.sendEmptyMessageDelayed(MSG_UPDATE_TIME, delayMs);
}
}
}
private static class EngineHandler extends Handler {
private final WeakReference<TestWatch.Engine> mWeakReference;
public EngineHandler(TestWatch.Engine reference) {
mWeakReference = new WeakReference<>(reference);
}
@Override
public void handleMessage(Message msg) {
TestWatch.Engine engine = mWeakReference.get();
if (engine != null) {
switch (msg.what) {
case MSG_UPDATE_TIME:
engine.handleUpdateTimeMessage();
break;
}
}
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.skyarch.testapp" >
<uses-feature android:name="android.hardware.type.watch" />
<!-- Required to act as a custom watch face. -->
<uses-permission android:name="com.google.android.permission.PROVIDE_BACKGROUND" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@android:style/Theme.DeviceDefault" >
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
<service android:name=".DataLayerListenerService" >
<intent-filter>
<action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
</intent-filter>
</service>
<service
android:name=".TestWatch"
android:label="@string/my_digital_name"
android:permission="android.permission.BIND_WALLPAPER" >
<meta-data
android:name="android.service.wallpaper"
android:resource="@xml/watch_face" />
<meta-data
android:name="com.google.android.wearable.watchface.preview"
android:resource="@drawable/preview_digital" />
<meta-data
android:name="com.google.android.wearable.watchface.preview_circular"
android:resource="@drawable/preview_digital_circular" />
<intent-filter>
<action android:name="android.service.wallpaper.WallpaperService" />
<category android:name="com.google.android.wearable.watchface.category.WATCH_FACE" />
</intent-filter>
</service>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />
</application>
</manifest>
res/values/strings.xml
<resources>
<string name="app_name">TestApp</string>
<string name="my_digital_name">SkyarchTest</string>
</resources>
res/values/dimens.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="digital_text_size_date">10dp</dimen>
<dimen name="digital_text_size_date_round">10dp</dimen>
<dimen name="digital_text_size_time">20dp</dimen>
<dimen name="digital_text_size_time_round">20dp</dimen>
<dimen name="digital_text_size_message">40dp</dimen>
<dimen name="digital_text_size_message_round">40dp</dimen>
<dimen name="digital_x_offset_date">85dp</dimen>
<dimen name="digital_x_offset_date_round">85dp</dimen>
<dimen name="digital_y_offset_date">180dp</dimen>
<dimen name="digital_x_offset_time">70dp</dimen>
<dimen name="digital_x_offset_time_round">70dp</dimen>
<dimen name="digital_x_offset_time_ambient_shift">13dp</dimen>
<dimen name="digital_y_offset_time">200dp</dimen>
<dimen name="digital_x_offset_message">75dp</dimen>
<dimen name="digital_x_offset_message_round">75dp</dimen>
<dimen name="digital_y_offset_message">130dp</dimen>
</resources>
res/values/colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="digital_background">#000000</color>
<color name="digital_text_date">#ffffff</color>
<color name="digital_text_time">#ffffff</color>
<color name="digital_text_message">#cc0000</color>
</resources>
参考記事
Amazon API Gatewayの利用方法
http://dev.classmethod.jp/cloud/aws/api-gateway-with-android/
公式ページデータ転送方法について
https://developer.android.com/intl/ja/training/wearables/data-layer/messages.html
天気予報をAndroidWearのWatchFaceに表示
http://swarmnyc.com/whiteboard/how-to-design-and-develop-an-android-watch-face-app-wearables-overview/
Data APIを使ったデータ転送
https://sites.google.com/a/gclue.jp/android-docs/ni-yinkiwear/data-apiwo-shittadeta-zhuan-song
Android Wearのアプリの作り方
http://www.buildinsider.net/mobile/androidwear/02
ありがとうございました!
投稿者プロフィール
-
Japan AWS Ambassadors 2023, 2024
開発会社での ASP型WEBサービス企画 / 開発 / サーバ運用 を経て
2010年よりスカイアーチネットワークスに在籍しております
機械化/効率化/システム構築を軸に人に喜んで頂ける物作りが大好きです。








