2015-11-15 6 views
7

Я хочу сделать двунаправленную передачу данных между 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"); 
           } 
          } 
         }); 
        } 
       } 
      }); 
     } 
    } 

Спасибо за помощь!

ответ

13

согласно Google Play services 8.3 blog post:

С Google Play услуги 8.3, мы обновили DataApi, чтобы обеспечить актуальность в том, как элементы данных синхронизируются. Теперь в элемент данных можно добавить приоритет, чтобы определить, когда он должен быть синхронизирован.Например, если вы создаете приложение, для которого требуется немедленная синхронизация, например приложение удаленного управления, его все равно можно сделать сразу, вызвав setUrgent(), но для чего-то вроде обновления ваших контактов вы можете переносить некоторую задержку. Несрочные данные Данные могут быть отложены на срок до 30 минут, но вы можете ожидать, что в большинстве случаев они будут доставлены в течение нескольких минут. Низкий приоритет теперь по умолчанию, поэтому setUrgent() необходим для получения предыдущего времени.

Поэтому, если вы хотите, чтобы они немедленно синхронизировались с другими подключенными устройствами, вы вызываете setUrgent().

+1

Ничего себе, спасибо, что это проблема на КПК! (Я добавляю это как к карманным, так и к изнашиванию). Теперь он немедленно запускает onDataChange. По моему износу молчит после отправки данных в карманный компьютер. Я пытаюсь переписать процедуру отправки на карманный компьютер, как на изнашивание, но ничего. –

+1

Теперь у меня есть молчание onDataChanged на износ для ручного запроса. Из журнала (это похоже на вопрос) я вижу, что запрос был успешно отправлен на нужное устройство, но onDataChanged на износ не получает его (не имеет значения с или без setUrgent()). Если я отправил из портативного сообщения через MessageApi, он получил ношение в добавленном onMessageReceived. Я хочу знать секрет, почему MessageApi работает хорошо, а DataAPI где-то потерян. –

+0

Я обнаружил, что данные могут достичь износа в одной из этих модификаций: после отправки данных сделать googleapi disconnect() и connect() на карманном компьютере или заменить Thread.start() на Thread.run() –

Смежные вопросы