かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Android Wear → Androidの通信

こちらのページを参考に、メッセージ送信の部分だけを抜き出して作ってみました。

Android, Android Wear共通

両方のAndroidManifest.xmlのapplicationタグの下に以下の一行を追加します。こいつは通信につかうGoogleApiClientを使うのにいるっぽいです。

<meta-data
    android:name="com.google.android.gms.version"
    android:value="@integer/google_play_services_version" />

Android Wear メッセージ送信側

まず、画面でGoogleApiClientのインスタンスを作っておきます。Builderがあるのでさくっと作れます。接続の成否を見るために、addConnectionCallbacksとaddOnConnectionFailedのリスナーを追加しておくといいと思われます。そして、addApiでWearable.APIを追加しておきます。

// フィールドに定義しておく
private GoogleApiClient client;

// OnCreateとかそれに準ずるメソッドあたりで初期化
this.client = new GoogleApiClient.Builder(this.getActivity())
        .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
            @Override
            public void onConnected(Bundle bundle) {
                Log.d("MyFragment", "onConnected");
            }
            @Override
            public void onConnectionSuspended(int i) {
                Log.d("MyFragment", "onConnectionSuspended");
            }
        })
        .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
            @Override
            public void onConnectionFailed(ConnectionResult connectionResult) {
                Log.d("MyFragment", "onConnectionFailed");
            }
        })
        .addApi(Wearable.API)
        .build();
this.client.connect();

次にメッセージ送信処理です。これはボタンのクリックなど、送信したいタイミングのところに書くといいと思います。ポイントはUIスレッドでawait呼ぶと死ぬので別スレッドを立ててるところです。ただ、行儀がいいのかはちょっと個人的に疑問に残ったりするところです…。

Wearable.NodeApi.GetConnectionNodesメソッドで接続のノードを取得して、全ノードに対してsendMessageメソッドでメッセージを送っています。sendMessageは、GoogleClientApiと、ノードのIDと、メッセージ識別用のパスの文字列と、バイト列です。ここではHello worldをバイト配列にして渡してます。

結果に対してisSuccessで成否の判断ができます。到達確認までできてるのかは、未確認…。

new Thread(new Runnable() {
    @Override
    public void run() {
        Log.d("MyFragment", "onClick");
        final String message = "Hello world";
        NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(client).await();
        for (Node node : nodes.getNodes()) {
            MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(
                    client,
                    node.getId(),
                    "/hello",
                    message.getBytes())
                    .await();
            if (result.getStatus().isSuccess()) {
                Log.d("onClick", "isSuccess is true");
            } else {
                Log.d("onClick", "isSuccess is false");
            }
        }
    }
}).start();

Android wear側のコードはこんな感じになってます。

package com.example.kazuki.myapplication;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.support.wearable.view.CardFragment;
import android.support.wearable.view.FragmentGridPagerAdapter;
import android.support.wearable.view.GridViewPager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.MessageApi;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.Wearable;

public class MyActivity extends Activity {

    private TextView mTextView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        final GridViewPager gvp = (GridViewPager) findViewById(R.id.gridPageView);
        gvp.setAdapter(new GridViewPagerAdapter(this.getFragmentManager()));
    }

    static class GridViewPagerAdapter extends FragmentGridPagerAdapter {

        public GridViewPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public Fragment getFragment(int row, int col) {
            return MyFragment.newInstance();
        }

        @Override
        public int getRowCount() {
            return 1;
        }

        @Override
        public int getColumnCount(int i) {
            return 1;
        }
    }

    static class MyFragment extends CardFragment {
        private GoogleApiClient client;

        @Override
        public View onCreateContentView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            this.client = new GoogleApiClient.Builder(this.getActivity())
                    .addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
                        @Override
                        public void onConnected(Bundle bundle) {
                            Log.d("MyFragment", "onConnected");
                        }

                        @Override
                        public void onConnectionSuspended(int i) {
                            Log.d("MyFragment", "onConnectionSuspended");
                        }
                    })
                    .addOnConnectionFailedListener(new GoogleApiClient.OnConnectionFailedListener() {
                        @Override
                        public void onConnectionFailed(ConnectionResult connectionResult) {
                            Log.d("MyFragment", "onConnectionFailed");
                        }
                    })
                    .addApi(Wearable.API)
                    .build();
            this.client.connect();

            Button button = new Button(this.getActivity());
            button.setText("OK");
            button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            Log.d("MyFragment", "onClick");
                            final String message = "Hello world";
                            NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(client).await();
                            for (Node node : nodes.getNodes()) {
                                MessageApi.SendMessageResult result = Wearable.MessageApi.sendMessage(
                                        client,
                                        node.getId(),
                                        "/hello",
                                        message.getBytes())
                                        .await();
                                if (result.getStatus().isSuccess()) {
                                    Log.d("onClick", "isSuccess is true");
                                } else {
                                    Log.d("onClick", "isSuccess is false");
                                }
                            }
                        }
                    }).start();
                }
            });
            return button;
        }

        public static MyFragment newInstance() {
            return new MyFragment();
        }
    }
}

ついでにレイアウトファイル

<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.GridViewPager
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/gridPageView"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MyActivity"
    tools:deviceIds="wear">
</android.support.wearable.view.GridViewPager>

受信側のAndroidの処理

受信側も、何処かでGoogleApiClientのインスタンスを作っておきます。この例では、ActivityのOnCreateで作ってます。手抜きして各種リスナーは登録しないでやりました。すっきり!この処理いりませんでした。

package com.example.kazuki.myapplication;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;

import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.Wearable;


public class MyActivity extends Activity  {
    private GoogleApiClient client;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
        this.client = new GoogleApiClient.Builder(this)
                .addApi(Wearable.API)
                .build();
        this.client.connect();
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.my, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

データの受信にはWearableListenerServiceを継承したサービスを作ります。こいつは、onMessageReceivedをオーバーライドすればOKです。メッセージがきたら呼ばれます。getPathとgetDataでsendMessageしたときのパスとデータがとれます。ここでは、受信結果をログに出しています。

package com.example.kazuki.myapplication;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

import com.google.android.gms.wearable.MessageEvent;
import com.google.android.gms.wearable.WearableListenerService;

public class MyService extends WearableListenerService {
    @Override
    public void onMessageReceived(MessageEvent messageEvent) {
        Log.d("MyService", "onMessageReceived");
        Log.d("MyService", messageEvent.getPath());
        Log.d("MyService", new String(messageEvent.getData()));
    }
}

AndroidManifest.xmlには、以下のようなサービスの定義をしておきます。intent-filterのactionにcom.google.android.gms.wearable.BIND_LISTENERを追加しておくのがポイントです。

<service
    android:name=".MyService" >
    <intent-filter>
        <action android:name="com.google.android.gms.wearable.BIND_LISTENER" />
    </intent-filter>
</service>

これで、接続されているAndroidとAndroid Wearの両方でアプリを起動して、Android Wearでボタンを押すとAndroid側のログにメッセージが出ます。

おまけ

Android実機とAndroid Wearのエミュレータの接続方法は以下のページが参考になります。

Android Wear Previewの取得とかは今はいらないので後半の接続方法のところを参考にしてやればOKです。