问题
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):
Why fields of
latlang
arestatic
? It doesn't looks like they should.At
SunFragment.onCreate()
you are readinglocation
ifsavedInstanceState != null
.savedInstanceState
is not null only if activity that holds this fragment was restored from saved state. It may not happen at all.You should use fragment's
arguments
(Bundle) to pass initial data to fragmentYou should implement
Parcelable
interface forlatlang
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.
- Request last known location in MainActivity
- Keep a reference of last location in cache for easy loading and better user experience
- If last location is null, request location updates until you get a good fix
- 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..."),
"",
sun_rise,
"",
sun_set,
"",
moon_rise,
"",
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