WearListenerService onDataChanged 奇怪的行为

WearListenerService onDataChanged strange behavior

我想在 Android Wear 和 Handheld 之间进行双向数据传输。 除了在手持设备上触发 onDataChanged 之外,一切似乎都很好。只有当我插入 in\out USB 电缆并连接到 PC 时,它才会触发。 所以我不明白为什么会这样。

这是我的代码:

手持设备上的 WearListenerService:

import android.content.Intent;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.WearableListenerService;

import java.util.List;

import ru.orangesoftware.financisto.db.DatabaseAdapter;
import ru.orangesoftware.financisto.model.Category;
import ru.orangesoftware.financisto.model.CategoryTree;
import ru.orangesoftware.financisto.utils.Log;

public class WearService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener
{
    private static final String WEARABLE_DATA_PATH = "/wearable_data";
    private static final String HANDHELD_DATA_PATH = "/handheld_data";

    private SendToDataLayerThread s;
    GoogleApiClient googleClient;

    private DatabaseAdapter db;

    @Override
    public void onCreate()
    {
        super.onCreate();

        Log.d("WearService Created");
        db = new DatabaseAdapter(this);
        db.open();

        initGoogleApiClient();
    }

    @Override
    public void onDataChanged(DataEventBuffer dataEvents)
    {
        Log.d("In dataChanged");

        DataMap dataMap;
        for (DataEvent event : dataEvents)
        {

            // Check the data type
            if (event.getType() == DataEvent.TYPE_CHANGED)
            {
                // Check the data path
                String path = event.getDataItem().getUri().getPath();
                if (path.equals(HANDHELD_DATA_PATH))
                {
                    dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
                    Log.v("Path phone: " + path);
                    Log.v("DataMap received from watch: " + dataMap);

                    Intent messageIntent = new Intent();
                    messageIntent.setAction(Intent.ACTION_SEND);
                    messageIntent.putExtra("time", System.currentTimeMillis());
                    messageIntent.putExtra("DataMap", dataMap.toBundle());
                    LocalBroadcastManager.getInstance(this).sendBroadcast(messageIntent);

                    // Create a DataMap object and send it to the data layer
                    dataMap = new DataMap();
                    dataMap.putString("Pong", "Pong" + String.valueOf(System.currentTimeMillis()));
                    dataMap.putLong("time", System.currentTimeMillis());

                    //Requires a new thread to avoid blocking the UI
                    s = new SendToDataLayerThread(WEARABLE_DATA_PATH, dataMap);
                    s.start();
                }
            }
        }
    }

    private void initGoogleApiClient()
    {
        // Build a new GoogleApiClient for the the Wearable API

        Log.d("Initialaizing GoogleClient");

        if (googleClient == null)
        {
            googleClient = new GoogleApiClient.Builder(this)
                    .addApi(Wearable.API)
                    .addConnectionCallbacks(this)
                    .addOnConnectionFailedListener(this)
                    .build();
        }

        if (!googleClient.isConnected())
        {
            Log.d("Tring to connect to GoogleApi...");

            googleClient.connect();

        }

        Log.d("Google Client ID = " + googleClient.toString());
    }

    // Disconnect from the data layer when the Activity stops
    @Override
    public void onDestroy()
    {
        super.onDestroy();

        Log.d("WearService: onDestroy");

        if (null != googleClient && googleClient.isConnected())
        {
            googleClient.disconnect();
        }

        if (db != null)
        {
            db.close();
        }

    }



  @Override
public void onConnected(Bundle bundle)
{
    Log.d("onConnected entered");
    Log.d("GoogleAPI now status:" + googleClient.isConnected());
}

    @Override
    public void onConnectionSuspended(int i)
    {

    }

    @Override
    public void onConnectionFailed(ConnectionResult result) {
        Log.e("Connection to google api has failed. " + result.getErrorMessage());
    }

    class SendToDataLayerThread extends Thread
    {
        String path;
        DataMap dataMap;

        // Constructor for sending data objects to the data layer
        SendToDataLayerThread(String p, DataMap data)
        {
            path = p;
            dataMap = data;
        }

        public void run()
        {
            NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleClient).await();
            for (Node node : nodes.getNodes())
            {

                // Construct a DataRequest and send over the data layer
                PutDataMapRequest putDMR = PutDataMapRequest.create(path);
                putDMR.getDataMap().putAll(dataMap);
                PutDataRequest request = putDMR.asPutDataRequest();
                DataApi.DataItemResult result = Wearable.DataApi.putDataItem(googleClient, request).await();
                if (result.getStatus().isSuccess())
                {
                    Log.v("DataMap: " + dataMap + " sent to: " + node.getDisplayName() + "; path=" + path);
                } else
                {
                    // Log an error
                    Log.v("ERROR: failed to send DataMap");
                }
            }
        }
    }
}

WearListenerService on Wear:

import android.content.Intent;
import android.os.Bundle;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.wearable.DataApi;
import com.google.android.gms.wearable.DataEvent;
import com.google.android.gms.wearable.DataEventBuffer;
import com.google.android.gms.wearable.DataMap;
import com.google.android.gms.wearable.DataMapItem;
import com.google.android.gms.wearable.Node;
import com.google.android.gms.wearable.NodeApi;
import com.google.android.gms.wearable.PutDataMapRequest;
import com.google.android.gms.wearable.PutDataRequest;
import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.wearable.WearableListenerService;

public class ListenerService extends WearableListenerService implements GoogleApiClient.ConnectionCallbacks,
        GoogleApiClient.OnConnectionFailedListener
{
    private static final String WEARABLE_DATA_PATH = "/wearable_data";
    private static final String HANDHELD_DATA_PATH = "/handheld_data";

    GoogleApiClient googleClient;

    private SendToDataLayerThread s;

    @Override
    public void onDataChanged(DataEventBuffer dataEvents)
    {
        Log.d("In dataChanged");

        DataMap dataMap;
        for (DataEvent event : dataEvents)
        {
            // Check the data type
            if (event.getType() == DataEvent.TYPE_CHANGED)
            {
                // Check the data path
                String path = event.getDataItem().getUri().getPath();
                Log.d("DataChanged: " + "path = " + path);
                if (path.equals(WEARABLE_DATA_PATH))
                {
                    dataMap = DataMapItem.fromDataItem(event.getDataItem()).getDataMap();
                    Log.d("DataChanged: " + "dataMap received on watch: " + dataMap);
                }

            }
        }
    }

    private void initGoogleApiClient()
    {
        if (googleClient == null)
        {
            Log.d("Building google client id...");
            googleClient = new GoogleApiClient.Builder(this)
                    .addApi(Wearable.API)
                            .addConnectionCallbacks(this)
                            .addOnConnectionFailedListener(this)
                    .build();

            Log.d("Google client id = " + googleClient.toString());
        }

        if (!googleClient.isConnected())
        {
            googleClient.connect();
        }

        Log.d("Google Client ID = " + googleClient.toString());
    }

    // Placeholders for required connection callbacks
    @Override
    public void onConnectionSuspended(int cause)
    {
    }

    @Override
    public void onConnected(Bundle connectionHint)
    {
        Log.v("OnConnected entered");
    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult)
    {
    }

    // Connect to the data layer when the Activity starts
    @Override
    public void onCreate()
    {
        super.onCreate();
        initGoogleApiClient();
    }

    // Disconnect from the data layer when the Activity stops
    @Override
    public void onDestroy()
    {
        if (null != googleClient && googleClient.isConnected())
        {
            Log.d("onDestroy: Disconnecting googleClient");
            googleClient.disconnect();
        }

        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startid)
    {
        Log.d("onStartCommand: Service was started.");

        // Create a DataMap object and send it to the data layer
        DataMap dataMap = new DataMap();
        dataMap.putString("ping", "ping" + String.valueOf(System.currentTimeMillis()));
        dataMap.putLong("time", System.currentTimeMillis());
        //Requires a new thread to avoid blocking the UI

        s = new SendToDataLayerThread(HANDHELD_DATA_PATH, dataMap);
        s.start();

        return super.onStartCommand(intent, flags, startid);
    }

    class SendToDataLayerThread extends Thread
    {
        String path;
        DataMap dataMap;

        // Constructor for sending data objects to the data layer
        SendToDataLayerThread(String p, DataMap data)
        {
            path = p;
            dataMap = data;
        }

        public void run()
        {
            NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleClient).await();
            for (Node node : nodes.getNodes())
            {

                final Node node2 = node;

                // Construct a DataRequest and send over the data layer
                PutDataMapRequest putDMR = PutDataMapRequest.create(path);
                putDMR.getDataMap().putAll(dataMap);
                PutDataRequest request = putDMR.asPutDataRequest();

                PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi.putDataItem(googleClient, request);
                pendingResult.setResultCallback(new ResultCallback<DataApi.DataItemResult>()
                {
                    @Override
                    public void onResult(DataApi.DataItemResult dataItemResult)
                    {
                        if (dataItemResult.getStatus().isSuccess())
                        {
                            Log.v("DataMap: " + dataMap + " sent to: " + node2.getDisplayName());
                        } else
                        {
                            // Log an error
                            Log.v("ERROR: failed to send DataMap");
                        }
                    }
                });


            }
        }
    }
}

Handheld 和 Wear 上的主要活动刚刚开始服务。 数据路径为: 1) Wear服务发送数据。 onDataChanged 在磨损时触发,因为它应该 2) 手持设备仅在拔出或插入USB 数据线后触发onDataChanged。手持发送数据到佩戴。 3) onDataChanged 触发磨损并接收数据。

附加信息。 清单手持设备:

<?xml version="1.0" encoding="utf-8"?>
<manifest package="ru.orangesoftware.financisto"
          xmlns:android="http://schemas.android.com/apk/res/android"
          android:installLocation="internalOnly">

    <supports-screens
        android:anyDensity="true"
        android:largeScreens="true"
        android:normalScreens="true"
        android:resizeable="true"
        android:smallScreens="true"/>

    <uses-feature
        android:name="android.hardware.touchscreen"
        android:required="false"/>
    <uses-feature
        android:name="android.hardware.camera"
        android:required="false"/>
    <uses-feature
        android:name="android.hardware.location"
        android:required="false"/>
    <uses-feature
        android:name="android.hardware.location.network"
        android:required="false"/>
    <uses-feature
        android:name="android.hardware.location.gps"
        android:required="false"/>

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.VIBRATE"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
    <uses-permission android:name="android.permission.USE_CREDENTIALS"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

    <application
        android:allowBackup="true"
        android:description="@string/app_description"
        android:icon="@drawable/icon"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.DeviceDefault">


        <uses-library
            android:name="com.google.android.maps"
            android:required="false"/>
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version"/>
        <activity
        android:name=".activity.MainActivity"
        android:configChanges="orientation|keyboardHidden"
        android:label="@string/app_name"
        android:taskAffinity=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN"/>
            <category android:name="android.intent.category.LAUNCHER"/>
        </intent-filter>
    </activity>

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

    </application>

</manifest> 

明显磨损:

<?xml version="1.0" encoding="utf-8"?>
<manifest package="ru.orangesoftware.financisto"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-feature android:name="android.hardware.type.watch"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@android:style/Theme.DeviceDefault">

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

        <activity
            android:name=".MainWearActivity"
            android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <service
            android:name="ru.orangesoftware.financisto.ListenerService"
            android:enabled="true">
            <intent-filter>
                <action android:name="com.google.android.gms.wearable.BIND_LISTENER"/>
            </intent-filter>
        </service>


    </application>

</manifest>

Gradle 掌机:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:1.3.0'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.4'
    }
}

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

repositories {
    maven { url "http://repo.commonsware.com.s3.amazonaws.com" }
    maven { url "https://repository-achartengine.forge.cloudbees.com/snapshot/" }
    mavenCentral()
}

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "ru.orangesoftware.financisto"
        minSdkVersion 19
        targetSdkVersion 22
        versionCode 92
        versionName "1.6.8"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }

    packagingOptions {
        exclude 'META-INF/LICENSE'
        exclude 'META-INF/LICENSE.txt'
        exclude 'META-INF/NOTICE'
        exclude 'META-INF/NOTICE.txt'
    }
}

def googlePlayVersion = '8.3.0'

dependencies {
    compile "com.google.android.gms:play-services-base:$googlePlayVersion"
    compile "com.google.android.gms:play-services-drive:$googlePlayVersion"
    compile "com.google.android.gms:play-services-wearable:$googlePlayVersion"

    compile files('libs/dropbox-android-sdk-1.6.1/dropbox-android-sdk-1.6.1.jar')
    compile files('libs/google-rfc-2445/rfc2445-no-joda.jar')
    compile 'com.google.code.gson:gson:2.3'
    compile 'com.commonsware.cwac:wakeful:1.0.1'
    compile 'org.achartengine:achartengine:1.2.0'
    compile 'net.sf.trove4j:trove4j:3.0.3'
    compile 'com.wdullaer:materialdatetimepicker:2.0.0'
}

Gradle穿:

apply plugin: 'com.android.application'


android {
    compileSdkVersion 22
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "ru.orangesoftware.financisto"
        minSdkVersion 20
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

def googlePlayVersion = '8.3.0'

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.google.android.support:wearable:1.3.0'
    compile "com.google.android.gms:play-services-wearable:$googlePlayVersion"
}

应用程序启动后登录手持设备

11-15 12:20:20.616 29043-29043/? D/Financisto: WearService Created [WearService.onCreate:44]
11-15 12:20:20.616 29043-29043/? D/Financisto: Initialaizing GoogleClient [WearService.initGoogleApiClient:94]
11-15 12:20:20.636 29043-29043/? D/Financisto: Tring to connect to GoogleApi... [WearService.initGoogleApiClient:107]
11-15 12:20:20.636 29043-29043/? D/Financisto: Google Client ID = com.google.android.gms.internal.zzmg@4344d5c0 [WearService.initGoogleApiClient:113]
11-15 12:20:21.016 29043-29043/ru.orangesoftware.financisto D/Financisto: onConnected entered [WearService.onConnected:139]
11-15 12:20:21.016 29043-29043/ru.orangesoftware.financisto D/Financisto: GoogleAPI now status:true [WearService.onConnected:140]

-------now I send data from watch and unplug usb cable after 30 seconds

11-15 12:24:31.986 29043-29091/? D/Financisto: In dataChanged [WearService.onDataChanged:54]
11-15 12:24:31.986 29043-29091/? V/Financisto: Path phone: /handheld_data [WearService.onDataChanged:68]
11-15 12:24:31.986 29043-29091/? V/Financisto: DataMap received from watch: {time=1447565065308, ping=ping1447565065306} [WearService.onDataChanged:69]
11-15 12:24:32.036 29043-29091/? D/Financisto: In dataChanged [WearService.onDataChanged:54]
11-15 12:24:32.046 29043-1493/? V/Financisto: DataMap: {Pong=Pong1447565071992, time=1447565071992} sent to: Gear 2 76A1; path=/wearable_data [SendToDataLayerThread.run:179]

应用程序启动并发送数据后登录 Wear:

11-15 12:24:25.301 2460-2460/ru.orangesoftware.financisto D/FinancistoWear: onStartCommand: Service was started.
11-15 12:24:25.377 2460-2460/ru.orangesoftware.financisto V/FinancistoWear: DataMap: {time=1447565065308, ping=ping1447565065306} sent to: Tolive GN3
11-15 12:24:25.379 2460-3309/ru.orangesoftware.financisto D/FinancistoWear: In dataChanged
11-15 12:24:25.379 2460-3309/ru.orangesoftware.financisto D/FinancistoWear: DataChanged: path = /handheld_data

请指出我做错了什么?

解决方案

我重写了SendToDataLayerThread。现在它是一个常规的 class 没有扩展 Thread 并且 DataRequest 变得紧急:

class SendToDataLayerThread
    {
        String path;
        DataMap dataMap;

        // Constructor for sending data objects to the data layer
        SendToDataLayerThread(String p, DataMap data)
        {
            path = p;
            dataMap = data;
        }

        public void run()
        {
            //NodeApi.GetConnectedNodesResult nodes = Wearable.NodeApi.getConnectedNodes(googleClient);
            PendingResult<NodeApi.GetConnectedNodesResult> nodes = Wearable.NodeApi.getConnectedNodes(googleClient);
            nodes.setResultCallback(new ResultCallback<NodeApi.GetConnectedNodesResult>()
            {
                @Override
                public void onResult(NodeApi.GetConnectedNodesResult getConnectedNodesResult)
                {
                    for (Node node : getConnectedNodesResult.getNodes())
                    {

                        final Node node2 = node;

                        // Construct a DataRequest and send over the data layer
                        PutDataMapRequest putDMR = PutDataMapRequest.create(path);
                        putDMR.getDataMap().putAll(dataMap);
                        PutDataRequest request = putDMR.asPutDataRequest();
                        request.setUrgent();

                        PendingResult<DataApi.DataItemResult> pendingResult = Wearable.DataApi.putDataItem(googleClient, request);
                        pendingResult.setResultCallback(new ResultCallback<DataApi.DataItemResult>()
                        {
                            @Override
                            public void onResult(DataApi.DataItemResult dataItemResult)
                            {
                                if (dataItemResult.getStatus().isSuccess())
                                {
                                    Log.v("DataMap: " + dataMap + " sent to: " + node2.getDisplayName());
                                } else
                                {
                                    // Log an error
                                    Log.v("ERROR: failed to send DataMap");
                                }
                            }
                        });
                    }
                }
            });
        }
    }

感谢帮助!

根据 Google Play services 8.3 blog post:

With Google Play services 8.3, we’ve updated the DataApi to allow for urgency in how data items are synced. Now, a priority can be added to the data item to determine when it should be synced. For example, if you are building an app that requires immediate syncing, such as a remote control app, it can still be done immediately by calling setUrgent(), but for something such as updating your contacts, you could tolerate some delay. Non-urgent DataItems may be delayed for up to 30 minutes, but you can expect that in most cases they will be delivered within a few minutes. Low priority is now the default, so setUrgent() is needed to obtain the previous timing.

因此,如果您希望数据请求立即同步到其他连接的设备,请务必调用 setUrgent()