Новый Android размещает SDK в Android Work Manager - PullRequest
1 голос
/ 12 марта 2019

В предисловии я работаю в реагирующем языке, и наше приложение использует много фоновых процессов. Мы опробовали ряд реагирующих нативных решений, таких как background-fetch, background-geolocation и т. Д. В ходе тестирования мы обнаружили, что наилучшим из возможных подходов для сбора данных в фоновых потоках является собственный Work Manager для Android. Мне удалось реализовать несколько основных вещей в Диспетчере работ, таких как отметки времени и использование приложений. Но сейчас я пытаюсь заставить работать новый Android Places SDK (Google Places устарел) для работы в Work Manager. Ниже приведена картина ошибки, которую я получаю.

Невозможно создать обработчик внутри потока, который не вызвал Looper.prepare ()

Поэтому, естественно, после получения любой ошибки, я пришел сюда, и многие из вас предложили асинхронные задачи и создали обработчик. Будучи в основном незнакомым с обработкой потоков Android, я не уверен, как и где реализовывать такие вещи. До сих пор я пытался вызвать Looper.prepare () в onCreate (), а также в моем Worker.

Это мой код места

package com.bettertime.betterLocation;

import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.RequiresPermission;
import android.util.Log;

import com.google.android.gms.common.api.Api;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.tasks.Task;
import com.google.android.libraries.places.api.Places;
import com.google.android.libraries.places.api.model.Place;
import com.google.android.libraries.places.api.model.PlaceLikelihood;
import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest;
import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse;
import com.google.android.libraries.places.api.net.PlacesClient;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.support.v7.app.AppCompatActivity;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;

import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission.ACCESS_WIFI_STATE;
import static com.bettertime.MainActivity.placesClient;


public class BetterLocation extends AppCompatActivity {

    private List<Place.Field> placeList = new ArrayList<>();
    private static String TAG = "Location: ";
    public static Map<String, Object> places = new HashMap<>();

    int PERMISSION_ALL = 1;
    String[] PERMISSIONS = {
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_WIFI_STATE
    };



    public void findCurrentPlace() {
        places.clear();
        if(ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
                ContextCompat.checkSelfPermission(this, ACCESS_WIFI_STATE) == PackageManager.PERMISSION_GRANTED) {
        findCurrentPlaceWithPermissions();
        }
    }


    @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE})
    private void findCurrentPlaceWithPermissions() {


        placeList.add(Place.Field.NAME);
        placeList.add(Place.Field.ADDRESS);
        placeList.add(Place.Field.LAT_LNG);
        placeList.add(Place.Field.TYPES);

        FindCurrentPlaceRequest currentPlaceRequest =
                FindCurrentPlaceRequest.builder(placeList).build();


            if(ContextCompat.checkSelfPermission(this, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
                    ContextCompat.checkSelfPermission(this, ACCESS_WIFI_STATE) == PackageManager.PERMISSION_GRANTED) {
                Task<FindCurrentPlaceResponse> currentPlaceTask = placesClient.findCurrentPlace(currentPlaceRequest);

                currentPlaceTask.addOnCompleteListener(task -> {
                    if (task.isSuccessful()) {
                        FindCurrentPlaceResponse response = task.getResult();
                        assert response != null;
                        for (PlaceLikelihood placeLikelihood : response.getPlaceLikelihoods()) {
                            Log.d(TAG, "findCurrentPlace: "
                                    + placeLikelihood.getPlace().getName() + "\n"
                                    + placeLikelihood.getPlace().getAddress() + "\n"
                                    + placeLikelihood.getPlace().getLatLng() + "\n"
                                    + placeLikelihood.getPlace().getTypes() + "\n"
                                    + placeLikelihood.getLikelihood());

                            PlaceObj placeObj = new PlaceObj(
                                    placeLikelihood.getPlace().getName(),
                                    placeLikelihood.getPlace().getAddress(),
                                    placeLikelihood.getPlace().getLatLng(),
                                    placeLikelihood.getPlace().getTypes(),
                                    placeLikelihood.getLikelihood());

                            places.put("place", placeObj);
                        }

                    } else {
                        Exception exception = task.getException();
                        if (exception instanceof ApiException) {
                            ApiException apiException = (ApiException) exception;
                            Log.e(TAG, "findCurrentPlaceWithPermissions: " + apiException.getStatusCode());
                        }
                    }
                });
            }
    }



    //////////////////////////
    // Helper methods below //
    //////////////////////////
    private boolean checkPermission(String permission) {
        boolean hasPermission =
                ContextCompat.checkSelfPermission(this, permission) == PackageManager.PERMISSION_GRANTED;
        if (!hasPermission) {
            ActivityCompat.requestPermissions(this, new String[]{permission}, 0);
        }
        return hasPermission;
    }

    public static boolean hasPermissions(Context context, String... permissions) {
        if (context != null && permissions != null) {
            for (String permission : permissions) {
                if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
                    return false;
                }
            }
        }
        return true;
    }

}

Вот мой рабочий менеджер

package com.bettertime.betterWorkManager;

import android.content.Context;
import android.content.SharedPreferences;

import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.NonNull;
import android.util.Log;

import com.bettertime.betterLocation.BetterLocation;
import com.bettertime.packages.NativeUsageEvents;
import com.bettertime.timePackage.NativeTime;

import com.google.firebase.firestore.FirebaseFirestore;

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import androidx.work.Worker;
import androidx.work.WorkerParameters;

import static com.bettertime.betterLocation.BetterLocation.places;


public class BetterWorkManager extends Worker {

    private static final String TAG = "Work Manager Firing";

    private FirebaseFirestore db = FirebaseFirestore.getInstance();
    private NativeTime nativeTime = new NativeTime();
    public static Handler mHandler;
    private BetterLocation betterLocation = new BetterLocation();

    public BetterWorkManager(@NonNull Context context, @NonNull WorkerParameters workerParams) {
        super(context, workerParams);
    }




    @NonNull
    @Override
    public Worker.Result doWork() {
        Log.d(TAG, "doWork: fired");


        userStamp();
        return Result.success();
    }


    private void userStamp(){
        Looper.prepare();
         mHandler = new Handler() {
            public void handleMessage(Message msg) {
                // process incoming messages here
                betterLocation.findCurrentPlace();
            }
        };
        Looper.loop();

        Log.d(TAG, "places test: " + places.toString());

}

Я вынул некоторые ненужные биты, но в этом суть. И для хорошей меры вот моя основная деятельность.

package com.bettertime;

import android.os.Bundle;


import com.bettertime.betterWorkManager.BetterWorkManager;
import com.facebook.react.ReactActivity;
import com.google.android.libraries.places.api.Places;
import com.google.android.libraries.places.api.net.PlacesClient;

import java.util.concurrent.TimeUnit;
import androidx.work.ExistingPeriodicWorkPolicy;
import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkManager;



public class MainActivity extends ReactActivity {

    private static final String W_TAG = "Periodic Worker";
    public static PlacesClient placesClient;

    /**
     * Returns the name of the main component registered from JavaScript.
     * This is used to schedule rendering of the component.
     */
    @Override
    protected String getMainComponentName() {
        return "BetterYou";
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        Places.initialize(getApplicationContext(), "THIS_IS_MY_API_KEY");
        placesClient = Places.createClient(this);
        PeriodicWorkRequest fireUploadBuilder =
                new PeriodicWorkRequest.Builder(BetterWorkManager.class, 15, TimeUnit.MINUTES).build();
         WorkManager.getInstance().enqueueUniquePeriodicWork(W_TAG, ExistingPeriodicWorkPolicy.KEEP, fireUploadBuilder);

    }



}

Кто-нибудь добился успеха при внедрении Places SDK в Work Manager? Почему Places SDK должен взаимодействовать с основным потоком пользовательского интерфейса, если разрешения уже приняты? Любые советы высоко ценится. Я уже посмотрел документацию для Looper и AsyncTask, но оба не имеют особого смысла, так как у меня мало контекста, чтобы вставить их. Если вы предлагаете любой из них, пожалуйста, предоставьте контекст относительно того, где его использовать.

Ответы [ 2 ]

0 голосов
/ 26 марта 2019

Итак, после долгой борьбы я разобрался с некоторыми своими проблемами.Я поделюсь своим кодом и объясню (насколько мне известно), что я делал неправильно и как я решил это для тех из вас, кто может пытаться сделать что-то подобное.

Во-первых, мой код местоположения!

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.support.annotation.RequiresPermission;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.tasks.Task;
import com.google.android.libraries.places.api.model.Place;
import com.google.android.libraries.places.api.model.PlaceLikelihood;
import com.google.android.libraries.places.api.net.FindCurrentPlaceRequest;
import com.google.android.libraries.places.api.net.FindCurrentPlaceResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static android.Manifest.permission.ACCESS_FINE_LOCATION;
import static android.Manifest.permission.ACCESS_WIFI_STATE;
import static com.bettertime.MainActivity.placesClient;


public class BetterLocation  {

    private List<Place.Field> placeList = new ArrayList<>();
    private static String TAG = "Location: ";
    public static Map<String, Object> places = new HashMap<>();
    int PERMISSION_ALL = 1;
    String[] PERMISSIONS = {
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_WIFI_STATE
    };

     public void findCurrentPlace(Context context, Activity activity) {

        if(ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
                ContextCompat.checkSelfPermission(context, ACCESS_WIFI_STATE) == PackageManager.PERMISSION_GRANTED) {
            findCurrentPlaceWithPermissions(context);
         }
      else {
        ActivityCompat.requestPermissions(activity, PERMISSIONS, PERMISSION_ALL);
    }
    }


    @RequiresPermission(allOf = {ACCESS_FINE_LOCATION, ACCESS_WIFI_STATE})
    public void findCurrentPlaceWithPermissions(Context context) {

        placeList.add(Place.Field.NAME);
        placeList.add(Place.Field.ADDRESS);
        placeList.add(Place.Field.LAT_LNG);
        placeList.add(Place.Field.TYPES);

        FindCurrentPlaceRequest currentPlaceRequest =
                FindCurrentPlaceRequest.builder(placeList).build();

        if(ContextCompat.checkSelfPermission(context, ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
                ContextCompat.checkSelfPermission(context, ACCESS_WIFI_STATE) == PackageManager.PERMISSION_GRANTED) {

            Task<FindCurrentPlaceResponse> currentPlaceTask =
                    placesClient.findCurrentPlace(currentPlaceRequest);

            currentPlaceTask.addOnCompleteListener(task -> {
                if (task.isSuccessful()) {
                    places.clear();
                    FindCurrentPlaceResponse response = task.getResult();
                    assert response != null;
                    for (PlaceLikelihood placeLikelihood : response.getPlaceLikelihoods()) {
                        Log.d(TAG, "findCurrentPlace: "
                                + placeLikelihood.getPlace().getName() + "\n"
                                + placeLikelihood.getPlace().getAddress() + "\n"
                                + placeLikelihood.getPlace().getLatLng() + "\n"
                                + placeLikelihood.getPlace().getTypes() + "\n"
                                + placeLikelihood.getLikelihood());

                        PlaceObj placeObj = new PlaceObj(
                                placeLikelihood.getPlace().getName(),
                                placeLikelihood.getPlace().getAddress(),
                                placeLikelihood.getPlace().getLatLng(),
                                placeLikelihood.getPlace().getTypes(),
                                placeLikelihood.getLikelihood());

                        places.put(placeObj.Name, placeObj);
                    }


                } else {
                    Exception exception = task.getException();
                    if (exception instanceof ApiException) {
                        ApiException apiException = (ApiException) exception;
                        Log.e(TAG, "findCurrentPlaceWithPermissions: " + apiException.getStatusCode() + apiException.getLocalizedMessage());
                    }
                }
            });
        }
    }

}

До: Я использовал этот класс в качестве AppCompatActivity, который испортил объекты контекста и действия, которые я передавал методам.

После: Я изменил метод, чтобы включить действие и контекст.Я проверил это в своей функции MainActivity, передав ей контекст и действие MainActivity, и это сработало!

Далее мои прослушиваемые рабочие / классы потоков!

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.util.Log;
import com.bettertime.MainActivity;
import com.bettertime.betterLocation.BetterLocation;
import com.bettertime.packages.NativeUsageEvents;
import com.bettertime.timePackage.NativeTime;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.google.firebase.firestore.FirebaseFirestore;
import java.util.HashMap;
import java.util.Map;
import androidx.work.ListenableWorker;
import androidx.work.WorkerParameters;
import static com.bettertime.betterLocation.BetterLocation.places;


class LocThread implements Runnable {
    public static LocThread sInstance;
    public static LocThread getInstance(Context context) {
        if (sInstance == null) {
            //Always pass in the Application Context
            sInstance = new LocThread(context.getApplicationContext());
        }

        return sInstance;
    }

    private Context mContext;

    public LocThread(Context context) {
        mContext = context;
    }

    private BetterLocation betterLocation = new BetterLocation();
    private Activity activity = new MainActivity().mActivity;
    private String TAG = "LocThread: ";

    @Override
    public void run() {
        try {
            Looper.prepare();
            betterLocation.findCurrentPlace(mContext, activity);
            Looper.loop();
        } catch (NullPointerException e) {
            Log.d(TAG, "run: " + e.getLocalizedMessage());
        }
    }
}

public class FitPlaceWorker extends ListenableWorker {

    private static final String TAG = "FitPlace Worker: ";
    private Thread locThread;


    public FitPlaceWorker(Context context, WorkerParameters workerParams) {
        super(context, workerParams);
    }


    @NonNull
    @Override
    public ListenableFuture<Result> startWork(){
        SettableFuture<Result> result = SettableFuture.create();
        Log.d(TAG, "doWork - FitPlace fired");

        fitPlaceStamp();
        
        result.set(Result.success());
        return result;
    }


    private void fitPlaceStamp(){

        locThread = new Thread(new LocThread(getApplicationContext()));
        locThread.start();
        //log methods
        Log.d(TAG, "placeData: " + places.toString());
       
    }



}

До этого: Честно говоря, очень мало знал о многопоточности Android, поэтому я не уверен, что я делал в примере с вопросом.

После: Однажды, потратив некоторое время на изучение потоков, я понял, как создать свой собственный класс Thread.Я использую контекстную оболочку для захвата контекста и использую действие MainActivity для перехода в мой метод.Следующим шагом было внедрение ListenableWorker вместо базового Worker, который позволяет вам обрабатывать собственные потоки и иметь возможность запускать асинхронные задачи.Я вытащил некоторый код, чтобы сделать его немного более универсальным, чтобы он мог быть немного странным, но после тестирования я смог записать ответ Google Места на logcat всякий раз, когда рабочий увольнялся!У меня есть некоторые небольшие изгибы, чтобы работать, но, кажется, работает довольно хорошо!

Пожалуйста, дайте мне знать, если это может быть улучшено, или если у вас есть более глубокое понимание и, возможно, может объяснить лучше.

0 голосов
/ 13 марта 2019

High-level: вы вызываете асинхронный API внутри Worker.Рабочий для синхронного фонового выполнения.Вы можете вместо этого заглянуть в ListenableWorker, RxWorker или CoroutineWorker: https://developer.android.com/topic/libraries/architecture/workmanager/advanced/threading

Кроме того, я бы настоятельно рекомендовал не вызывать методы Looper для потоков, предоставленных вам WorkManager.Это почти всегда приводит к неожиданным и странным проблемам.

...