Android开发:如何在App启动时获取位置?解决线程Handler报错
App启动时先获取位置再加载Fragment的问题解决
嘿,我看你遇到了启动时获取位置再加载Fragment的问题,还碰到了Can't create handler inside thread that has not called Looper.prepare()的报错,我来帮你梳理下问题原因,然后给出可行的解决方案:
问题场景与现有代码
你需要在MainActivity启动时先拿到经纬度,再加载依赖该信息的HomeFragment,尝试用子线程实现时触发了报错,以下是你的代码:
MainActivity代码
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { MyLocation myLocation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); myLocation = new MyLocation(getApplicationContext()); Thread t = new Thread(new Runnable() { @Override public void run() { myLocation.getLocation(getApplicationContext(), locationResult); } }); t.start(); // spawn thread try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } FragmentTransaction tx = getSupportFragmentManager().beginTransaction(); tx.replace(R.id.flContent, new HomeFragment()); tx.commit(); } public MyLocation.LocationResult locationResult = new MyLocation.LocationResult() { @Override public void gotLocation(Location location) { // TODO Auto-generated method stub double Longitude = location.getLongitude(); double Latitude = location.getLatitude(); try { System.out.println("lat"+Latitude); System.out.println("long"+Longitude); SharedPreferences locationpref = getApplication() .getSharedPreferences("location", MODE_PRIVATE); SharedPreferences.Editor prefsEditor = locationpref.edit(); prefsEditor.putString("Longitude", Longitude + ""); prefsEditor.putString("Latitude", Latitude + ""); prefsEditor.commit(); System.out.println("Lat:"+Latitude); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } }; }
MyLocation类代码
public class MyLocation { Timer timer1; LocationManager lm; LocationResult locationResult; boolean gps_enabled = false; boolean network_enabled = false; Context context; public MyLocation(Context context){ this.context = context; } public boolean getLocation(Context context, LocationResult result) { //I use LocationResult callback class to pass location value from MyLocation to user code. locationResult = result; if (lm == null) lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); //exceptions will be thrown if provider is not permitted. try { gps_enabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER); } catch (Exception ex) { } try { network_enabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER); } catch (Exception ex) { } //Toast.makeText(context, gps_enabled+" "+network_enabled, Toast.LENGTH_LONG).show(); //don't start listeners if no provider is enabled if (!gps_enabled && !network_enabled) return false; if (gps_enabled) if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // TODO: Consider calling // ActivityCompat#requestPermissions // here to request the missing permissions, and then overriding // public void onRequestPermissionsResult(int requestCode, String[] permissions, // int[] grantResults) // to handle the case where the user grants the permission. See the documentation // for ActivityCompat#requestPermissions for more details. return true; } lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListenerGps); if (network_enabled) lm.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListenerNetwork); timer1 = new Timer(); timer1.schedule(new GetLastLocation(context), 10000); // Toast.makeText(context, " Yaha Tak AAya", Toast.LENGTH_LONG).show(); return true; } LocationListener locationListenerGps = new LocationListener() { public void onLocationChanged(Location location) { timer1.cancel(); locationResult.gotLocation(location); lm.removeUpdates(this); lm.removeUpdates(locationListenerNetwork); } public void onProviderDisabled(String provider) {} public void onProviderEnabled(String provider) {} public void onStatusChanged(String provider, int status, Bundle extras) {} }; LocationListener locationListenerNetwork = new LocationListener() { public void onLocationChanged(Location location) { timer1.cancel(); locationResult.gotLocation(location); lm.removeUpdates(this); lm.removeUpdates(locationListenerGps); } public void onProviderDisabled(String provider) {} public void onProviderEnabled(String provider) {} public void onStatusChanged(String provider, int status, Bundle extras) {} }; class GetLastLocation extends TimerTask { Context context; public GetLastLocation(Context context){ this.context = context; } @Override public void run() { //Context context = getClass().getgetApplicationContext(); Location net_loc = null, gps_loc = null; if (gps_enabled) if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { // TODO: Consider calling // ActivityCompat#requestPermissions // here to request the missing permissions, and then overriding // public void onRequestPermissionsResult(int requestCode, String[] permissions, // int[] grantResults) // to handle the case where the user grants the permission. See the documentation // for ActivityCompat#requestPermissions for more details. return; } gps_loc = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER); if(network_enabled) net_loc=lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); //if there are both values use the latest one if(gps_loc!=null && net_loc!=null){ if(gps_loc.getTime()>net_loc.getTime()) locationResult.gotLocation(gps_loc); else locationResult.gotLocation(net_loc); return; } if(gps_loc!=null){ locationResult.gotLocation(gps_loc); return; } if(net_loc!=null){ locationResult.gotLocation(net_loc); return; } locationResult.gotLocation(null); } } public static abstract class LocationResult{ public abstract void gotLocation(Location location); } }
问题原因分析
- Looper报错根源:你在子线程中调用了
getLocation,而LocationManager的回调(如onLocationChanged)会尝试在当前线程创建Handler,但子线程默认没有初始化Looper,因此抛出异常。 - 主线程阻塞问题:
t.join()会阻塞主线程,导致App启动时出现ANR(应用无响应),严重影响用户体验,这是绝对不能用的写法。
正确解决方案
我们需要在主线程发起位置请求,通过回调拿到结果后再加载Fragment,同时避免阻塞主线程,具体实现如下:
修改后的MainActivity代码
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener { MyLocation myLocation; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 可选:显示加载动画,提升用户体验 // findViewById(R.id.loading_progress).setVisibility(View.VISIBLE); myLocation = new MyLocation(getApplicationContext()); // 直接在主线程发起位置请求,无需子线程 myLocation.getLocation(getApplicationContext(), locationResult); } public MyLocation.LocationResult locationResult = new MyLocation.LocationResult() { @Override public void gotLocation(Location location) { if (location == null) { // 处理获取位置失败的情况 Toast.makeText(MainActivity.this, "获取位置失败,请检查定位权限和设置", Toast.LENGTH_SHORT).show(); loadHomeFragment(); return; } double longitude = location.getLongitude(); double latitude = location.getLatitude(); try { SharedPreferences locationpref = getApplication() .getSharedPreferences("location", MODE_PRIVATE); SharedPreferences.Editor prefsEditor = locationpref.edit(); prefsEditor.putString("Longitude", String.valueOf(longitude)); prefsEditor.putString("Latitude", String.valueOf(latitude)); prefsEditor.apply(); // 用apply()异步保存,比commit()更高效 // 位置获取成功后加载Fragment loadHomeFragment(); } catch (Exception e) { e.printStackTrace(); // 异常情况下也加载Fragment,避免App卡住 loadHomeFragment(); } } }; // 封装Fragment加载逻辑,复用代码 private void loadHomeFragment() { // 隐藏加载动画 // findViewById(R.id.loading_progress).setVisibility(View.GONE); FragmentTransaction tx = getSupportFragmentManager().beginTransaction(); tx.replace(R.id.flContent, new HomeFragment()); tx.commit(); } }
MyLocation类的线程安全优化
GetLastLocation是TimerTask,运行在子线程中,直接调用locationResult.gotLocation()可能引发线程安全问题,需要切换到主线程执行回调:
class GetLastLocation extends TimerTask { Context context; public GetLastLocation(Context context){ this.context = context; } @Override public void run() { Location net_loc = null, gps_loc = null; if (gps_enabled) if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { return; } gps_loc = lm.getLastKnownLocation(LocationManager.GPS_PROVIDER); if(network_enabled) net_loc=lm.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); // 切换到主线程执行回调 new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { if(gps_loc!=null && net_loc!=null){ if(gps_loc.getTime()>net_loc.getTime()) locationResult.gotLocation(gps_loc); else locationResult.gotLocation(net_loc); return; } if(gps_loc!=null){ locationResult.gotLocation(gps_loc); return; } if(net_loc!=null){ locationResult.gotLocation(net_loc); return; } locationResult.gotLocation(null); } }); } }
额外注意事项
- 权限处理:务必在
AndroidManifest.xml中添加定位权限,并在运行时请求ACCESS_FINE_LOCATION/ACCESS_COARSE_LOCATION,否则无法获取位置。 - 异常兜底:一定要处理
location == null的情况,避免App崩溃。 - 用户体验:加载位置时显示加载动画,让用户感知到App在正常工作。
内容的提问来源于stack exchange,提问作者user8654574
相关产品推荐
相关产品推荐

