作为前言,我正在使用react-native,我们的应用程序使用了大量的后台进程。我们已经尝试了许多基于本机的反应解决方案,例如后台获取,后台地理定位等。我们在测试中发现,在后台线程中收集数据的最佳方法是Android的本机工作管理器。我已经设法在工作管理器中实现了一些基本的东西,如时间戳和应用程序使用。但现在我正在努力获取新的Android Places SDK(Google商家信息现已弃用)以便在Work Manager中使用。下面是我得到的错误的图片。
Can't create handler inside thread that has not called Looper.prepare()
所以在遇到任何错误之后,我自然而然地提到了Async任务,并创建了一个处理程序。由于对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());
}
我拿出了一些不必要的东西,但这就是它的要点。这里有一个很好的衡量标准是我的主要活动。
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。工作者用于同步后台执行。您可能希望查看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());
}
}
});
}
}
}
之前:我使用这个类作为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!我确实有一些小问题可以解决,但似乎工作得很好!
如果可以进一步改进,或者您有更深入的见解并且可以更好地解释,请告诉我。