Location is empty at the start

不问归期 提交于 2021-02-12 11:40:44

问题


I am reframing my last question, which is unanswered, and I have rewritten the problem following Google's BasicLocation.

My main activity is defined as:

public class MainActivity extends AppCompatActivity
        implements NavigationView.OnNavigationItemSelectedListener {
//    private LocationCallback locationCallback;
//     private FusedLocationProviderClient mFusedLocationClient;
    private FusedLocationProviderClient mFusedLocationClient;
    protected Location mLastLocation;
    private static final int REQUEST_PERMISSIONS_REQUEST_CODE = 34;
    private static final String TAG = MainActivity.class.getSimpleName();



    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.drawer_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
        SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
        ViewPager viewPager = findViewById(R.id.view_pager);
        viewPager.setAdapter(sectionsPagerAdapter);
        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        NavigationView navigationView = findViewById(R.id.nav_view);
        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.addDrawerListener(toggle);
        toggle.syncState();
        navigationView.setNavigationItemSelectedListener(this);

        ImageButton leftNav = findViewById(R.id.left_nav);
        ImageButton rightNav = findViewById(R.id.right_nav);
        leftNav.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int tab = viewPager.getCurrentItem();
                if (tab > 0) {
                    tab--;
                    viewPager.setCurrentItem(tab);
                } else if (tab == 0) {
                    viewPager.setCurrentItem(tab);
                }
            }
        });

        rightNav.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                int tab = viewPager.getCurrentItem();
                tab++;
                viewPager.setCurrentItem(tab);
            }
        });

    }
    @Override
    public void onStart() {
        super.onStart();
        if (!checkPermissions()) {
            requestPermissions();
        } else {
            getLastLocation();
        }
    }

with latlang.[Lat,Lang] is in a seperate file:

public class latlang {
    public static double Lat;
    public static double Lang;
}

and the location file, which is the first fragment in the viewpager is defined as:

public class SunFragment extends Fragment {

    List<SunSession> sunsList;
    Typeface sunfont;


    //to be called by the MainActivity
    public SunFragment() {
        // Required empty public constructor
    }

    // Keys for storing activity state.
//  private static final String KEY_CAMERA_POSITION = "camera_position";
    private static final String KEY_LOCATION_NAME = "location_name";
    public String location;//="No location name found";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Retrieve location and camera position from saved instance state.
        if (savedInstanceState != null) {
            location = savedInstanceState.getCharSequence(KEY_LOCATION_NAME).toString();
            System.out.println("OnCreate location  "+location);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View rootView = inflater.inflate(R.layout.fragment_sun, container, false);
        onSaveInstanceState(new Bundle());
        //SecondFragment secondFragment = new SecondFragment();
        //secondFragment.getDeviceLocation();

        RecyclerView rv = rootView.findViewById(R.id.rv_recycler_view);
        rv.setNestedScrollingEnabled(false);
        rv.setHasFixedSize(true);
        //MyAdapter adapter = new MyAdapter(new String[]{"Today", "Golden Hour", "Blue Hour", "Civil Twilight", "Nautical Twilight", "Astronomical Twilight", "Hello", "World"});
        //rv.setAdapter(adapter);
        LinearLayoutManager llm = new LinearLayoutManager(getActivity());
        rv.setLayoutManager(llm);
        System.out.println("location  "+location);



   /*
     Reversegeocoding location
     */
        String location="No location name found";
        String errorMessage = "";
        List<Address> addresses = null;

        Geocoder geocoder = new Geocoder(getContext(), Locale.getDefault());
        try {
            addresses = geocoder.getFromLocation(
                    latlang.Lat,
                    latlang.Lang,
                    1);
        } catch (IOException ioException) {
            // Catch network or other I/O problems.
            errorMessage = getString(R.string.service_not_available);
            // Log.e(TAG, errorMessage, ioException);
            if (getView() != null){
                Snackbar.make(getView(), errorMessage, Snackbar.LENGTH_LONG).show();
            }
        } catch (IllegalArgumentException illegalArgumentException) {
            // Catch invalid latitude or longitude values.
            errorMessage = getString(R.string.invalid_lat_long_used);
            if (getView() != null){
                Snackbar.make(getView(),
                        "Illegal Latitude = " + latlang.Lat + ", Longitude = " +
                                latlang.Lang, Snackbar.LENGTH_LONG).show();
            }
        }
        if (addresses == null || addresses.size() == 0) {
            if (errorMessage.isEmpty()) {
                System.out.println("Adress Empty No Address Found");// Snackbar.LENGTH_LONG).show();
                location = "Lat:"+latlang.Lat+" Lang: "+latlang.Lang;
            }
        } else {
            location = addresses.get(0).getAddressLine(0);//+", "+ addresses.get(0).getLocality();
        /* for(int i = 0; i <= addresses.get(0).getMaxAddressLineIndex(); i++) {
          location = addresses.get(0).getAddressLine(i);
        }*/
        }

The problem with this is evident from the logcat:

I/System.out: location  null
 I/Google Maps Android API: Google Play services package version: 17785022
 I/Choreographer: Skipped 31 frames!  The application may be doing too much work on its main thread.
 I/System.out: Position:0
 I/System.out: Position:1
 I/System.out: Position:2
 I/zygote: Do full code cache collection, code=202KB, data=177KB
 I/zygote: After code cache collection, code=129KB, data=91KB
 I/zygote: JIT allocated 56KB for compiled code of void android.view.View.<init>(android.content.Context, android.util.AttributeSet, int, int)
 I/zygote: Background concurrent copying GC freed 44415(2MB) AllocSpace objects, 7(136KB) LOS objects, 49% free, 3MB/6MB, paused 294us total 102.458ms
 I/System.out: Position:3
 I/System.out: Position:4
 I/zygote: Do partial code cache collection, code=193KB, data=126KB
 I/zygote: After code cache collection, code=193KB, data=126KB
 I/zygote: Increasing code cache capacity to 1024KB
 I/zygote: JIT allocated 71KB for compiled code of void android.widget.TextView.<init>(android.content.Context, android.util.AttributeSet, int, int)
 I/zygote: Compiler allocated 4MB to compile void android.widget.TextView.<init>(android.content.Context, android.util.AttributeSet, int, int)
 E/MainActivity: Latit: 37.42342342342342

This shows, at the start, location is null,

I/System.out: location null

then the recyclerview of the sunfragment is created

 I/System.out: Position:0
 I/System.out: Position:1
 I/System.out: Position:2

and after that I am getting the location:

E/MainActivity: Latit: 37.42342342342342

Link of the complete code:https://drive.google.com/file/d/1pMl_3Lf76sy82C0J4b-9ta4jbSHonJ2y/view?usp=sharing

Is it somehow possible to get the location first before creating the sunfragment's oncreateview?


回答1:


I found something wrong about your code (I may be wrong):

  1. Why fields of latlang are static? It doesn't looks like they should.

  2. At SunFragment.onCreate() you are reading location if savedInstanceState != null. savedInstanceState is not null only if activity that holds this fragment was restored from saved state. It may not happen at all.

  3. You should use fragment's arguments (Bundle) to pass initial data to fragment

  4. You should implement Parcelable interface for latlang to be able to pass custom class thru Bundle

I think that's not everything but for me it seems like enough for this code to not work as you expected




回答2:


As stated by the previous answer there are a lot of issues in your code.

Apart from that understand that last known location may not always return a value. Gps basically has two data types: Ephemeris (precise nav data) and Almanac (coarse data). Now when the receiver is cold started ie after gps has been off for more than 8-10 mins, there is basically no last known location (the duration may vary based on the device but the basic idea is this).

So when you do not get last known location, fetch the actual live location using the fused client. Also since you are saving the data in shared preference and fetching it in your fragment, i believe your fragment is going to heavily rely on this data. So i would suggest either of the following two approaches to get correct result

1) do not fetch the location in the nesting activity at all. Just do it in the fragment where it is needed. This will not work if other fragments in your viewpager also need the location.

2) If you must have the location in your activity and it is a dependency in the container fragments, you can use two approaches here as well. My approaches rely on event bus.. Event bus, otto or rxbus anything will do

2a) do not add anything to the viewpager. basically fetch the location first fully and then add stuff to the viewpager once you get the location callback.

2b) Add stuff to the viewpager from the start. In the activity once you get the location, use the event bus to inform the fragments of the same and on getting the event in the fragments, actually start what you need to do.

I have previously used both these approaches and everything works. Now it is entirely up to your use case to use what suits you. Either ways it is too long a code and too complicated to post everything here.




回答3:


Right now you are using like this:

@Override
public void onStart() {
    super.onStart();
    if (!checkPermissions()) {
        requestPermissions();
    } else {
        getLastLocation();
    }
}

In your else statement check if getLastLocation() is not null. if not null, replace the fragment.

@Override
public void onStart() {
    super.onStart();
    if (!checkPermissions()) {
        requestPermissions();
    } else {
        if(getLastLocation() != null){
           //replace your fragment
        }else{//Null Location}
    }
}



回答4:


Use a service class to handle location service precisely. Here, i'm giving you a custom LocationService class which i used in many projects to collect the location data from background continuously

LocationService.kt

import android.Manifest
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Binder
import android.os.Build
import android.os.IBinder
import android.os.Looper
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat

import com.google.android.gms.location.FusedLocationProviderClient
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
import com.google.android.gms.location.LocationServices


class LocationService : Service() {

    //Custom Location Binder class
    inner class LocationBinder : Binder() {
        val locationService: LocationService
            get() = this@LocationService
    }

    companion object {
        val TAG = "LocationService_123"
        private val UPDATE_INTERVAL = (4 * 1000).toLong()  /* 4 secs */
        private val FASTEST_INTERVAL: Long = 2000 /* 2 sec */

        private var INSTANCE: LocationService? = null
        fun isInstanceCreated(): Boolean {
            return (INSTANCE != null)
        }
    }


    private var mFusedLocationClient: FusedLocationProviderClient? = null
    private var mLocationListener: LocationListener? = null
    private var mLocationBinder = LocationBinder()

    fun setLocationListener(locationListener: LocationListener) {
        this.mLocationListener = locationListener
    }

    override fun onBind(intent: Intent): IBinder? {
        return mLocationBinder
    }

    override fun onCreate() {
        INSTANCE = this
        super.onCreate()

        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

        if (Build.VERSION.SDK_INT >= 26) {
            val CHANNEL_ID = "ostad_gps"
            val channel = NotificationChannel(
                    CHANNEL_ID,
                    "Ostad GPS",
                    NotificationManager.IMPORTANCE_DEFAULT)

            (getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager).createNotificationChannel(channel)

            val notification = NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("").build()

            startForeground(1, notification)
        }
    }

    override fun onDestroy() {
        INSTANCE = null
        super.onDestroy()
    }

    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        Log.d(TAG, "onStartCommand: called.")
        getLocation()
        return Service.START_NOT_STICKY
    }

    private fun getLocation() {

        // ---------------------------------- LocationRequest ------------------------------------
        // Create the location request to start receiving updates
        val mLocationRequestHighAccuracy = LocationRequest()
        mLocationRequestHighAccuracy.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
        mLocationRequestHighAccuracy.interval = UPDATE_INTERVAL
        mLocationRequestHighAccuracy.fastestInterval = FASTEST_INTERVAL


        // new Google API SDK v11 uses getFusedLocationProviderClient(this)
        if (ActivityCompat.checkSelfPermission(
                        this,
                        Manifest.permission.ACCESS_FINE_LOCATION
                ) != PackageManager.PERMISSION_GRANTED
        ) {
            Log.d(TAG, "getLocation: stopping the location mitchService.")
            stopSelf()
            return
        }
        Log.d(TAG, "getLocation: getting location information.")
        mFusedLocationClient!!.requestLocationUpdates(
                mLocationRequestHighAccuracy, object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult?) {

                val location = locationResult!!.lastLocation

                Log.d(TAG, "onLocationResult: got location result: $location")

                if (location != null) {
                    if (mLocationListener != null)
                        mLocationListener!!.onLocationChanged(location)
                }

            }
        },
                Looper.myLooper()
        ) // Looper.myLooper tells this to repeat forever until thread is destroyed
    }

}

add this in your AndroidManifest file and start the LocationService from you MainActivity.




回答5:


By looking at the code, it seems you do not need a very accurate location, you will be fine with last known location. This value might be null in some cases, like you have already experienced. Simple answer to your question is no, you cannot get not null location before creating SunFragment. Following steps is to load location in background and update UI once found.

  1. Request last known location in MainActivity
  2. Keep a reference of last location in cache for easy loading and better user experience
  3. If last location is null, request location updates until you get a good fix
  4. Have a listener in SunFragment to track location updates

Here are some code you need (Please do read them)

Use the LocationUtil to handle location related events (I prefer LocationManager over FusedLocationProviderClient);

    public class LocationUtil {

    public static void updateLastKnownLocation(Context context) {

        LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);

        if(hasSelfPermission(context, new String[]{
                Manifest.permission.ACCESS_FINE_LOCATION, Manifest.permission.ACCESS_COARSE_LOCATION})) {

            try {

                Location currentBestLocation;

                Location gpsLocation = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER);
                Location lbsLocation = lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);

                if(isBetterLocation(lbsLocation, gpsLocation)) {
                    currentBestLocation = lbsLocation;
                } else {
                    currentBestLocation = gpsLocation;
                }

                if(currentBestLocation == null) {
                    requestLocationUpdates(lm);
                } else {
                    updateCacheLocation(currentBestLocation);
                }

            } catch (SecurityException se) {
                // unlikely as permission checks
                se.printStackTrace();
            } catch (Exception e) {
                // unexpected
                e.printStackTrace();
            }
        }
    }

    private static void updateCacheLocation(Location location) {

        if(location == null) return;

        LocationLite temp = new LocationLite();
        temp.lat = location.getLatitude();
        temp.lon = location.getLongitude();

        Gson gson = new Gson();
        String locationString = gson.toJson(temp);

        AppCache.setLastLocation(locationString);
    }

    @SuppressLint("MissingPermission")
    private static void requestLocationUpdates(LocationManager lm) {

        try {

            lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000, 0.0F, new LocationListener() {

                @Override
                public void onLocationChanged(Location location) {
                    updateCacheLocation(location);
                    lm.removeUpdates(this);
                }

                @Override
                public void onStatusChanged(String s, int i, Bundle bundle) {
                    // doing nothing
                }

                @Override
                public void onProviderEnabled(String s) {
                    // doing nothing
                }

                @Override
                public void onProviderDisabled(String s) {
                    // doing nothing
                }
            });
        }catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static boolean isBetterLocation(Location location, Location currentBestLocation) {

        int TWO_MINUTES  = 1000 * 60 * 2;

        if (currentBestLocation == null) {
            // A new location is always better than no location
            return true;
        }

        if (location == null) {
            // A new location is always better than no location
            return false;
        }

        // Check whether the new location fix is newer or older
        long timeDelta = location.getTime() - currentBestLocation.getTime();
        boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
        boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
        boolean isNewer = timeDelta > 0;

        // If it's been more than two minutes since the current location, use the new location
        // because the user has likely moved
        if (isSignificantlyNewer) {
            return true;
            // If the new location is more than two minutes older, it must be worse
        } else if (isSignificantlyOlder) {
            return false;
        }

        // Check whether the new location fix is more or less accurate
        int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
        boolean isLessAccurate = accuracyDelta > 0;
        boolean isMoreAccurate = accuracyDelta < 0;
        boolean isSignificantlyLessAccurate = accuracyDelta > 200;

        // Check if the old and new location are from the same provider
        boolean isFromSameProvider = isSameProvider(location.getProvider(), currentBestLocation.getProvider());

        // Determine location quality using a combination of timeliness and accuracy
        if (isMoreAccurate) {
            return true;
        } else if (isNewer && !isLessAccurate) {
            return true;
        } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
            return true;
        }

        return false;
    }

    private static boolean isSameProvider(String provider1, String provider2) {
        if (provider1 == null) {
            return provider2 == null;
        }
        return provider1.equals(provider2);
    }


    public static boolean hasSelfPermission(Context context, String[] permissions) {
        // Below Android M all permissions are granted at install time and are already available.
        if (!(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)) {
            return true;
        }

        // Verify that all required permissions have been granted
        for (String permission : permissions) {
            if (context.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }

        return true;
    }
}

Use AppCache to store last location;

public class AppCache {

    public static final String KEY_LAST_LOCATION = "_key_last_location";

    private static SharedPreferences mPreference;

    static {
        mPreference = PreferenceManager.getDefaultSharedPreferences(App.getApp().getApplicationContext());
    }

    public static String getLastLocation() {
        return mPreference.getString(KEY_LAST_LOCATION, null);
    }

    public static String getLastLocation(String defaultValue) {
        return mPreference.getString(KEY_LAST_LOCATION, defaultValue);
    }

    public static void setLastLocation(String lastLocation) {
        mPreference.edit().putString(KEY_LAST_LOCATION, lastLocation).commit();
    }

    public static void registerPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) {
        mPreference.registerOnSharedPreferenceChangeListener(listener);
    }

    public static void unregisterPreferenceChangeListener(SharedPreferences.OnSharedPreferenceChangeListener listener) {
        mPreference.unregisterOnSharedPreferenceChangeListener(listener);
    }
}

Put the following code into your MainActivity onCreate() This will call locationManager to get last known location and update app cache.

LocationUtil.updateLastKnownLocation(MainActivity.this);

Also replace fetchLocation(); in onRequestPermissionsResult method with above line of code, so it will look like;

@Override
    public void onRequestPermissionsResult(...){
        switch (requestCode) {
            case 101:
            {
                ...
                    // permission was granted
                    //fetchLocation();
                    LocationUtil.updateLastKnownLocation(MainActivity.this);
                } else {
                   // Show some error
                }
                return;
            }
        }
    }

I did not use your latlang class. (Please make sure all class names follow Java coding standards) Instead use LocationLite to store location in cache. Also I used GSON google library to convert and restore pojo to JSON and backward.

public class LocationLite {

    public double lat;
    public double lon;
    public String address;
}

Final changes in SunFragment.

Make SunAdapter as a member variable, and SharedPreferences.OnSharedPreferenceChangeListener to listen to any changes on location value.

SunAdapter mAdapter;

SharedPreferences.OnSharedPreferenceChangeListener mPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
        @Override
        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {

            if(AppCache.KEY_LAST_LOCATION.equalsIgnoreCase(key)) {
                // location value has change, update data-set
                SunSession sunSession = sunsList.get(0);
                sunSession.setId(sharedPreferences.getString(key, "No Location"));
                sunsList.add(0, sunSession);
                mAdapter.notifyDataSetChanged();
            }
        }
    };

Start listening to preference changes in onStart() and unregister in onStop()

@Override
public void onStart() {
    super.onStart();
    AppCache.registerPreferenceChangeListener(mPreferenceChangeListener);
}

@Override
public void onStop() {
    super.onStop();
    AppCache.unregisterPreferenceChangeListener(mPreferenceChangeListener);
}

Finally when populating first SunSession use the following instead location local variable. So It will look like following;

             sunsList.add(
                new SunSession(
                        AppCache.getLastLocation("Searching location..."),
                        "&#xf051;",
                        sun_rise,
                        "&#xf052;",
                        sun_set,
                        "&#xf0c9",
                        moon_rise,
                        "&#xf0ca",
                        moon_set));

That's all. Feel free to ask anything you do not understand.



来源:https://stackoverflow.com/questions/57462562/location-is-empty-at-the-start

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!