Я хочу сделать двунаправленную передачу данных между Android Wear и Handheld. Все кажется хорошим, за исключением запуска onDataChanged на Handheld. Он запускается только тогда, когда я подключаю USB-кабель, подключенный к ПК. Так что я не понимаю, почему это происходит.WearListenerService onDataChanged странное поведение
Вот мой код:
WearListenerService на Handheld:
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 на 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 и носить только запускать службы. Путь данных: 1) Услуги по нанесению ношения. onDataChanged на износ, вызванный, как он должен 2) Ручные триггеры onDataChanged только после отсоединения или подключения USB-кабеля. Handheld посылает данные на износ. 3) триггеры onDataChanged при износе, как и должны, и получать данные.
Дополнительная информация. Manifest Handheld:
<?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>
Manifest Wear:
<?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 Wear:
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 = [email protected] [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. Теперь это обычный класс без продолжения 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");
}
}
});
}
}
});
}
}
Спасибо за помощь!
Ничего себе, спасибо, что это проблема на КПК! (Я добавляю это как к карманным, так и к изнашиванию). Теперь он немедленно запускает onDataChange. По моему износу молчит после отправки данных в карманный компьютер. Я пытаюсь переписать процедуру отправки на карманный компьютер, как на изнашивание, но ничего. –
Теперь у меня есть молчание onDataChanged на износ для ручного запроса. Из журнала (это похоже на вопрос) я вижу, что запрос был успешно отправлен на нужное устройство, но onDataChanged на износ не получает его (не имеет значения с или без setUrgent()). Если я отправил из портативного сообщения через MessageApi, он получил ношение в добавленном onMessageReceived. Я хочу знать секрет, почему MessageApi работает хорошо, а DataAPI где-то потерян. –
Я обнаружил, что данные могут достичь износа в одной из этих модификаций: после отправки данных сделать googleapi disconnect() и connect() на карманном компьютере или заменить Thread.start() на Thread.run() –