新 Android 将 SDK 放入 Android Work Manager

New Android Places SDK in Android Work Manager

前言,我在 react-native 工作,我们的应用程序使用了很多后台进程。我们已经尝试了很多基于 React Native 的解决方案,例如 background-fetch、background-geolocation 等。我们在测试中发现,在后台线程中收集数据的最佳方法是 Android 的原生工作管理器.我已经设法在工作管理器中实现了一些基本的东西,比如时间戳和应用程序使用。但现在我正在努力让新的 Android Places SDK(Google Places 现已弃用)在 Work Manager 中工作。下面是我遇到的错误的图片。

Can't create handler inside thread that has not called Looper.prepare()

很自然地,在遇到任何错误后,我来到这里,你们中的许多人建议异步任务,并创建一个处理程序。由于不熟悉 Android 线程处理,我不确定如何或在哪里实现这些东西。到目前为止,我已经尝试在 onCreate() 和我的 Worker 中调用 Looper.prepare() 。

这是我的地方代码

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());

}

我删除了一些不必要的部分,但这就是它的要点。为了更好的衡量,这里是我的主要 activity.

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);

    }



}

有没有人成功尝试在 Work Manager 中实施 Places SDK?如果权限已被接受,为什么 Places SDK 必须与主 UI 线程通信?任何意见是极大的赞赏。我已经查看了 Looper 和 AsyncTask 的文档,但两者都没有多大意义,因为我没有什么上下文可以放入它们。如果您建议,请提供关于在哪里使用它的上下文。

高级:您正在调用 Worker 中的异步 API。 Worker 用于同步后台执行。您可能需要查看 ListenableWorker、RxWorker 或 CoroutineWorker:https://developer.android.com/topic/libraries/architecture/workmanager/advanced/threading

此外,我强烈建议不要在 WorkManager 提供给您的线程上调用 Looper 方法。这几乎总是会导致奇怪的意外问题。

经过一番努力,我解决了一些问题。我将分享我的代码并(据我所知)解释我做错了什么以及我是如何为那些可能正在尝试做类似事情的人解决它的。

首先,我的位置代码!

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());
                    }
                }
            });
        }
    }

}

之前: 我将此 class 用作 AppCompatActivity,这弄乱了我传递给方法的上下文和 activity 对象。

After: 我改变了接受 activity 和上下文的方法。我在我的 MainActivity 中测试了这个,将 MainActivity 的上下文和 activity 传递给它,它成功了!

接下来是我的ListenableWorker/Thread类!

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 线程知之甚少,所以我不确定我在问题示例中做了什么。

之后: 在我花了一些时间学习更多关于线程的知识后,我想出了如何创建自己的线程 class。我使用上下文包装器来获取上下文,并使用 MainActivity 的 activity 传递到我的方法中。下一步是实现一个 ListenableWorker 而不是基本的 Worker,它允许您处理自己的线程并能够 运行 异步任务。我拿出一些代码使它更通用一点,所以它可能有点奇怪,但在测试它之后,我能够记录 google 地方对 logcat 每当工人开火时的响应!我确实有一些小问题需要解决,但它似乎工作得很好!

请让我知道它是否可以进一步改进,或者如果您有更深入的见解并且可以用更好的方式解释。