首页 > 解决方案 > 在Android中获取位置后创建一个返回地址的类

问题描述

我正在尝试在 Android 中创建一个多合一位置获取器类,它将获取位置并返回Address实例。

此类结合了向用户询问位置许可、显示许可理由、在无法获取位置时显示错误消息以及带有 ProgressBar 的 AlertDialog,它将在获取位置时显示并在找到位置或发生错误时隐藏自身。

这是我目前的工作:

public class LocationFetcher {

    static Handler handler = new Handler(Looper.getMainLooper());
    AlertDialog alertDialog;
    Context context;
    View retry;

    LocationFetcher(Context context, View retry) {
        this.context = context;
        this.retry = retry;
        handler.post(() -> {
            alertDialog = new AlertDialog.Builder(context)
                    .setTitle("Trying to fetch location...")
                    .setCancelable(false)
                    .create();
            alertDialog.setView(LayoutInflater.from(context).inflate(R.layout.progress_dialog, null));
        });
    }

    void showErrorMessage() {
        handler.post(() -> {
            alertDialog.dismiss();
            new Builder(context)
                    .setTitle("Error getting location data")
                    .setMessage("Please check the location settings in the Settings app and try again")
                    .setPositiveButton("Retry", (dialog, which) -> {
                        retry.performClick();
                        alertDialog.dismiss();
                    })
                    .setNegativeButton("Cancel", (dialog, which) -> {
                        // do nothing
                    }).show();
        });
    }

    Address getLocation() {
        final int LOCATION_PERMISSION_REQUEST_CODE = 1;
        if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
            Task<Location> locationTask = LocationServices.getFusedLocationProviderClient(context).getLastLocation();
            handler.post(alertDialog::show);
            try {
                return Executors.newSingleThreadExecutor().submit(() -> {
                    boolean flag = true;
                    while (flag) {
                        if (locationTask.isComplete()) {
                            if (locationTask.isSuccessful()) {
                                Location location = locationTask.getResult();
                                if (location != null) {
                                    List<Address> addresses = null;
                                    try {
                                        addresses = new Geocoder(context, Locale.getDefault()).getFromLocation(location.getLatitude(), location.getLongitude(), 1);
                                    } catch (IOException e) {
                                        e.printStackTrace();
                                    }
                                    if (addresses != null) {
                                        alertDialog.dismiss();
                                        return addresses.get(0);
                                    }
                                    else showErrorMessage();
                                } else showErrorMessage();
                            } else if (locationTask.getException() != null)
                                showErrorMessage();
                            flag = false;
                        }
                    }
                    return null;
                }).get(5, TimeUnit.SECONDS);
            } catch (ExecutionException | InterruptedException | TimeoutException e) {
                e.printStackTrace();
                showErrorMessage();
            }
        } else // if permission is not there, show permission rationale
            if (ActivityCompat.shouldShowRequestPermissionRationale((Activity) context, Manifest.permission.ACCESS_FINE_LOCATION))
                handler.post(() -> {
                    new AlertDialog.Builder(context)
                            .setTitle("Permission rationale")
                            .setMessage("This permission is required to access your location. Without it, the app cannot get your location to serve you better.")
                            .setPositiveButton("Ok", (dialog, which) -> {
                                ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE);
                            }).setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss())
                            .show();
                });
            else // if rationale not needed, ask for permission again
                ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_PERMISSION_REQUEST_CODE);
        return null;
    }
}

我面临的问题在于代码的这些部分:

构造函数:

handler.post(() -> {
            alertDialog = new AlertDialog.Builder(context)
                    .setTitle("Trying to fetch location...")
                    .setCancelable(false)
                    .create();
            alertDialog.setView(LayoutInflater.from(context).inflate(R.layout.progress_dialog, null));
        });

并在实际的 GetLocation 方法中显示对话框:

handler.post(alertDialog::show);

基本上这里发生的事情是因为handler.post构造函数中的部分作为异步操作工作,有时该handler.post(alertDialog::show);行在创建之前执行,alertDialog这导致GetLocation方法抛出NullPointerException.

如何在alertDialog使用之前等待构造函数和异步任务(或将异步转换为同步)完成?

编辑:

编辑 1:
注意:
我想异步调用 GetLocation 方法,即我调用这样的函数:

Executors.newSingleThreadExecutor().execute(() -> {
                    LocationFetcher locationFetcher = new LocationFetcher(this, tvCurrentLocation);
                    Address address = locationFetcher.getLocation();
                    });  

标签: javaandroidasynchronous

解决方案


同步创建您的 alertDialog 并getLocation在新线程中调用您的方法:

构造函数

LocationFetcher(Context context, View retry) {
    this.context = context;
    this.retry = retry;
    alertDialog = new AlertDialog.Builder(context)
            .setTitle("Trying to fetch location...")
            .setCancelable(false)
            .create();
    alertDialog.setView(LayoutInflater.from(context).inflate(R.layout.progress_dialog, null));
}

和方法调用

LocationFetcher locationFetcher = new LocationFetcher(this, tvCurrentLocation);
Executors.newSingleThreadExecutor().execute(() -> {    
    Address address = locationFetcher.getLocation();
}); 

推荐阅读