DirectionsApiRequest 和 apiRequest.setCallback 的问题

Problem with DirectionsApiRequest and apiRequest.setCallback

编写了一种在两点之间创建和叠加路线的方法。问题是,当它在正常模式下触发时(没有断点),它会导致 NullPointerExceptionIllegalStateException,但是当我 运行 我的应用程序处于调试模式时,它什么都不抛出并正常工作。 我很抱歉我的英语不好(我来自俄罗斯),对于问题的提出我深表歉意,我仍然不太熟悉 Whosebug。

这是我的方法:

    List<com.google.maps.model.LatLng> path;
    DirectionsRoute[] routes;
    DisplayMetrics metricsB = new DisplayMetrics();
    int width = metricsB.widthPixels;
    int heith = metricsB.heightPixels;
    PolylineOptions line = new PolylineOptions();
    LatLngBounds.Builder latLngBuilder = new LatLngBounds.Builder();
    String dist;

    public void TravelCost(LatLng startGeoPoint, LatLng stopGeoPoint) throws InterruptedException,
ApiException, IOException {

        GeoApiContext geoApiContext = new GeoApiContext.Builder()
                .apiKey(maps_api_key)
                .build();

        DirectionsApiRequest apiRequest = DirectionsApi.newRequest(geoApiContext);

        apiRequest.origin(new com.google.maps.model.LatLng(startGeoPoint.latitude, startGeoPoint.longitude));
        apiRequest.destination(new com.google.maps.model.LatLng(stopGeoPoint.latitude, stopGeoPoint.longitude));
        apiRequest.mode(TravelMode.DRIVING); //set travelling mode

        apiRequest.setCallback(new com.google.maps.PendingResult.Callback<DirectionsResult>() {
            @Override
            public void onResult(DirectionsResult result) {
                routes = result.routes;
                path = routes[0].overviewPolyline.decodePath();
                dist = routes[0].legs[0].distance.humanReadable;
                for (int i = 0; i < path.size(); i++) {
                    line.add(new com.google.android.gms.maps.model.LatLng(path.get(i).lat, path.get(i).lng));
                    latLngBuilder.include(new com.google.android.gms.maps.model.LatLng(path.get(i).lat, path.get(i).lng));
                }
            }

            @Override
            public void onFailure(Throwable e) {
                Toast.makeText(MapsActivity.this, "Error!", Toast.LENGTH_SHORT).show();
            }
        });

        line.width(16f).color(R.color.purple_500);
        map.addPolyline(line);
        LatLngBounds latLngBounds = latLngBuilder.build();
        CameraUpdate track = CameraUpdateFactory.newLatLngBounds(latLngBounds, 1080, 1920, 25);
        map.moveCamera(track);
    }

以下是日志中的错误:

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.plukash.travelcost, PID: 19561
    java.lang.IllegalStateException: Could not execute method for android:onClick
        at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:414)
        at android.view.View.performClick(View.java:7448)
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119)
        at android.view.View.performClickInternal(View.java:7425)
        at android.view.View.access00(View.java:810)
        at android.view.View$PerformClick.run(View.java:28305)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: java.lang.reflect.InvocationTargetException
        at java.lang.reflect.Method.invoke(Native Method)
        at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:409)
        at android.view.View.performClick(View.java:7448) 
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119) 
        at android.view.View.performClickInternal(View.java:7425) 
        at android.view.View.access00(View.java:810) 
        at android.view.View$PerformClick.run(View.java:28305) 
        at android.os.Handler.handleCallback(Handler.java:938) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7656) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
     Caused by: java.lang.IllegalStateException: no included points
        at com.google.android.gms.common.internal.Preconditions.checkState(Unknown Source:29)
        at com.google.android.gms.maps.model.LatLngBounds$Builder.build(Unknown Source:21)
        at com.plukash.travelcost.MapsActivity.TravelCost(MapsActivity.java:305)
        at com.plukash.travelcost.MapsActivity.onMapSearch(MapsActivity.java:142)
        at java.lang.reflect.Method.invoke(Native Method) 
        at androidx.appcompat.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:409) 
        at android.view.View.performClick(View.java:7448) 
        at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:1119) 
        at android.view.View.performClickInternal(View.java:7425) 
        at android.view.View.access00(View.java:810) 
        at android.view.View$PerformClick.run(View.java:28305) 
        at android.os.Handler.handleCallback(Handler.java:938) 
        at android.os.Handler.dispatchMessage(Handler.java:99) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7656) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

这是 XML-布局文件。

<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:layout_behavior="@string/bottom_sheet_behavior">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="5dp"
            android:orientation="horizontal">

            <EditText
                android:id="@+id/LocSearch"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="4"
                android:hint="Куда поедем?"
                android:inputType="text"
                tools:ignore="Autofill,HardcodedText" />


            <Button
                android:id="@+id/search_button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_weight="0.5"
                android:onClick="onMapSearch"  <------- OnClick Method
                android:text="Search"
                tools:ignore="HardcodedText" />

        </LinearLayout>

        <fragment
            android:id="@+id/map"
            android:name="com.google.android.gms.maps.SupportMapFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            tools:context=".MapsActivity"
            tools:ignore="FragmentTagUsage" />
    </LinearLayout>


    <include layout="@layout/bottom_sheet" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="15dp"
        android:src="@drawable/go_px"
        app:layout_anchor="@+id/bottom_sheet"
        app:layout_anchorGravity="top|end"
        tools:ignore="ContentDescription" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

这里是搜索按钮的点击方法,它调用“TravelCost”方法。

public void onMapSearch(View view) throws InterruptedException, ApiException, IOException {
        boolean bool = TrafficJam();
        try {
            Button search = findViewById(R.id.search_button);

            String location = locationSearch.getText().toString();
            List<Address> addressList;

            Geocoder geocoder = new Geocoder(this);
            try {
                addressList = geocoder.getFromLocationName(location, 1);

            } catch (IOException e) {
                e.printStackTrace();
                Toast toast = new Toast(this);
                toast.setText("Введите корректный адрес!");
                toast.show();
                return;
            }
            if (addressList != null) {
                Address address = addressList.get(0);
                latLng = new LatLng(address.getLatitude(), address.getLongitude());
                map.addMarker(new MarkerOptions().position(latLng).title("Marker"));
                map.animateCamera(CameraUpdateFactory.newLatLng(latLng));
                //Убирать ли текст в поисковом поле?
                //locationSearch.setText("");
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(search.getWindowToken(),
                        InputMethodManager.HIDE_NOT_ALWAYS);
            } else {
                Toast toast = new Toast(this);
                toast.setText("Введите адрес!");
                toast.show();
            }
        } catch (Exception e) {
            Toast toast = new Toast(this);
            toast.setText("Введите корректный адрес!");
            toast.show();
            return;
        }
        if (checker == 1) {
            startpoint = latLng;
            checker += 1;
            locationSearch.setText("");
            locationSearch.setHint("Куда поедем?");
        } else if (checker == 2) {
            LatLng endpoint = latLng;
            locationSearch.setText("Откуда поедем?");
            checker = 1;
            TravelCost(startpoint, endpoint); <-------------- Method call
        }
    }

UPD

需要 return 来自其他线程的值。

DirectionsRoute[] routes;
    public void TravelCost(LatLng startGeoPoint, LatLng stopGeoPoint) {
        GeoApiContext geoApiContext = new GeoApiContext.Builder()
                .apiKey("AIzaSyA9qY28oZ-4TTdDt1jgxdCKLYh9T8Kh0Ss")
                .build();

        DirectionsApiRequest apiRequest = DirectionsApi.newRequest(geoApiContext);
        apiRequest.origin(new com.google.maps.model.LatLng(startGeoPoint.latitude, startGeoPoint.longitude));
        apiRequest.destination(new com.google.maps.model.LatLng(stopGeoPoint.latitude, stopGeoPoint.longitude));
        apiRequest.mode(TravelMode.DRIVING);
        apiRequest.language("Russian");//set travelling mode


        AppExecutors.getInstance().networkIO().execute(() -> {
            apiRequest.setCallback(new com.google.maps.PendingResult.Callback<DirectionsResult>() {
                @Override
                public void onResult(DirectionsResult result) {
                    routes = result.routes;
<---- **routes here contains values**   
                }

                @Override
                public void onFailure(Throwable e) {
                    Toast.makeText(MapsActivity.this, "Error!", Toast.LENGTH_SHORT).show();
                }
            });
        });
<---- **Here routes is nullarray.**
        List<com.google.maps.model.LatLng> path = routes[0].overviewPolyline.decodePath();   
        dist = routes[0].legs[0].distance.humanReadable;
        for (int i = 0; i < path.size(); i++) {
            line.add(new com.google.android.gms.maps.model.LatLng(path.get(i).lat, path.get(i).lng));
            latLngBuilder.include(new com.google.android.gms.maps.model.LatLng(path.get(i).lat, path.get(i).lng));
        }

        line.width(16f).color(R.color.purple_500);
        map.addPolyline(line);
        LatLngBounds latLngBounds = latLngBuilder.build();
        CameraUpdate track = CameraUpdateFactory.newLatLngBounds(latLngBounds, 1080, 1920, 25);
        map.moveCamera(track);
    }

您的问题似乎与 android:onClick 系统没有找到该方法并因此抛出错误有关。

我的假设是发生这种情况是因为您 运行 在启用混淆器的情况下安装应用程序,混淆器可能正在更改方法名称,这将导致此异常。

我建议使用点击侦听器,因为使用 android:onClick

被认为是不好的做法
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Button button = findViewById(R.id.search_button);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onMapSearch(v);
            }
        });

        // implementation of an onclick listener using a lambda instead
        button.setOnClickListener(v -> onMapSearch(v));
    }

此外,您可以考虑删除 onMapSearch 中的视图参数,因为它不再被使用了

编辑

要修复 android.os.NetworkOnMainThreadException 您需要 运行 主线程之外的网络调用,请注意,不建议使用 AsyncTask,因为它已经被弃用很长时间了。

工人示例

public class AppExecutors {

    private static final Object LOCK = new Object();
    private static AppExecutors sInstance;
    private static Executor processor;
    private final Executor diskIO;
    private final Executor networkIO;
    private final Executor mainThread;

    private AppExecutors(Executor diskIO, Executor networkIO, Executor mainThread){
        this.diskIO = diskIO;
        this.networkIO = networkIO;
        this.mainThread = mainThread;
    }

    public static AppExecutors getInstance(){
        if(sInstance == null){
            synchronized (LOCK){
                sInstance = new AppExecutors(Executors.newSingleThreadExecutor(),
                        Executors.newFixedThreadPool(3),
                        new MainThreadExecutor());
            }
        }
        return sInstance;
    }

    public Executor diskIO() {
        return diskIO;
    }

    public Executor networkIO() {
        return networkIO;
    }

    public Executor getMainThread() {
        return mainThread;
    }

    private static class MainThreadExecutor implements Executor {
        private final Handler mainThreadHandler = new Handler(Looper.getMainLooper());

        @Override
        public void execute(@NotNull Runnable command) {
            mainThreadHandler.post(command);
        }
    }
}

用法

AppExecutors.getInstance().networkIO().execute(() -> {
    // Network call goes here       
});