Android中AsyncTask获取位置抛出Looper异常的解决方法咨询
这个问题我之前也碰到过,核心原因很明确:LocationManager.requestLocationUpdates()内部会创建Handler来处理位置回调,但AsyncTask的doInBackground()是在没有Looper的后台线程运行的,Handler必须绑定Looper才能工作,所以就抛出了java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()异常。而你之前不用AsyncTask时正常,是因为当时是在主线程调用的——主线程默认已经初始化了Looper。
下面给你两种解决方案,推荐第一种更规范、易维护的:
方案一:主线程注册位置监听,后台线程等待结果(推荐)
思路是把requestLocationUpdates()的调用放在主线程(有Looper),然后用CountDownLatch让后台线程等待位置结果,这样既不会阻塞主线程,又能满足你后台完成位置获取的需求。
步骤1:重构GPSlocater类,添加回调和Latch支持
修改GPSlocater,让它支持异步回调结果,同时兼容后台线程的等待逻辑:
package com.example.samarth.serviceprovider1; import android.app.AlertDialog; import android.app.Service; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; import android.os.IBinder; import android.provider.Settings; import android.util.Log; import java.util.concurrent.CountDownLatch; public class GPSlocater extends Service implements LocationListener { private final Context mContext; boolean isGPSEnabled = false; boolean isNetworkEnabled = false; boolean canGetLocation = false; Location location; double latitude; double longitude; private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10; private static final long MIN_TIME_BW_UPDATES = 1000 * 60 * 1; protected LocationManager locationManager; // 新增回调接口,用于通知位置结果 public interface LocationCallback { void onLocationReceived(Location location); void onLocationFailed(); } private LocationCallback mCallback; private CountDownLatch mLatch; // 用于后台线程等待位置 // 构造方法1:用于主线程回调 public GPSlocater(Context context, LocationCallback callback) { this.mContext = context; this.mCallback = callback; } // 构造方法2:用于后台线程等待 public GPSlocater(Context context, CountDownLatch latch) { this.mContext = context; this.mLatch = latch; } // 修改getLocation为异步逻辑,不再同步返回 public void getLocation() { try { locationManager = (LocationManager) mContext.getSystemService(LOCATION_SERVICE); isGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); isNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); if (!isGPSEnabled && !isNetworkEnabled) { showSettingsAlert(); notifyFailure(); return; } this.canGetLocation = true; // 优先获取网络定位的最近位置 if (isNetworkEnabled) { locationManager.requestLocationUpdates( LocationManager.NETWORK_PROVIDER, MIN_TIME_BW_UPDATES, MIN_DISTANCE_CHANGE_FOR_UPDATES, this); Location lastKnown = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); if (lastKnown != null) { updateLocation(lastKnown); return; } } // 再尝试GPS定位 if (isGPSEnabled) { Location lastKnown = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); if (lastKnown != null) { updateLocation(lastKnown); return; } locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, MIN_TIME_BW_UPDATES, MIN_DISTANCE_CHANGE_FOR_UPDATES, this); } } catch (Exception e) { e.printStackTrace(); notifyFailure(); } } // 统一处理位置更新 private void updateLocation(Location location) { this.location = location; this.latitude = location.getLatitude(); this.longitude = location.getLongitude(); stopUsingGPS(); // 获取到位置后停止监听,节省电量 if (mCallback != null) { mCallback.onLocationReceived(location); } if (mLatch != null) { mLatch.countDown(); } } // 统一处理定位失败 private void notifyFailure() { if (mCallback != null) { mCallback.onLocationFailed(); } if (mLatch != null) { mLatch.countDown(); } } // 原有方法保持不变 public void stopUsingGPS(){ if(locationManager != null){ locationManager.removeUpdates(GPSlocater.this); } } public double getLatitude(){ if(location != null){ latitude = location.getLatitude(); } return latitude; } public double getLongitude(){ if(location != null){ longitude = location.getLongitude(); } return longitude; } public boolean canGetLocation() { return this.canGetLocation; } public void showSettingsAlert(){ AlertDialog.Builder alertDialog = new AlertDialog.Builder(mContext); alertDialog.setTitle("GPS设置"); alertDialog.setMessage("GPS未启用,是否前往设置界面开启?"); alertDialog.setPositiveButton("设置", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); mContext.startActivity(intent); } }); alertDialog.setNegativeButton("取消", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.cancel(); notifyFailure(); } }); alertDialog.show(); } @Override public void onLocationChanged(Location location) { updateLocation(location); } @Override public void onProviderDisabled(String provider) {} @Override public void onProviderEnabled(String provider) {} @Override public void onStatusChanged(String provider, int status, Bundle extras) {} @Override public IBinder onBind(Intent arg0) { return null; } }
步骤2:修改AsyncTask逻辑,主线程注册监听,后台等待结果
修改asyncG类,把位置监听的注册移到主线程,用CountDownLatch等待结果:
class asyncG extends AsyncTask<Void, Void, Location> { Context context; ProgressDialog progressDialog; public asyncG(MainActivity mainActivity) { this.context = mainActivity; } @Override protected void onPreExecute() { super.onPreExecute(); progressDialog = new ProgressDialog(context); progressDialog.setMessage("Finding Location"); progressDialog.show(); } @Override protected Location doInBackground(Void... voids) { CountDownLatch latch = new CountDownLatch(1); // 必须在主线程调用requestLocationUpdates(因为需要Looper) ((MainActivity) context).runOnUiThread(new Runnable() { @Override public void run() { GPSlocater gpSlocater = new GPSlocater(context, latch); gpSlocater.getLocation(); } }); try { // 设置30秒超时,避免后台线程无限等待 latch.await(30, java.util.concurrent.TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } // 获取最终的位置结果 GPSlocater locater = new GPSlocater(context); return locater.getLocation(); } @Override protected void onPostExecute(Location location) { super.onPostExecute(location); progressDialog.dismiss(); if (location == null) { // 处理定位失败的情况 android.widget.Toast.makeText(context, "Failed to get location", android.widget.Toast.LENGTH_SHORT).show(); return; } double lat = location.getLatitude(); double longi = location.getLongitude(); Geocoder geocoder = new Geocoder(context, java.util.Locale.getDefault()); try { java.util.List<android.location.Address> arr = geocoder.getFromLocation(lat, longi, 1); if (arr == null || arr.isEmpty()) { android.widget.Toast.makeText(context, "No address found", android.widget.Toast.LENGTH_SHORT).show(); return; } android.location.Address address = arr.get(0); String pincode = address.getPostalCode(); String stat = address.getAdminArea(); String city1 = address.getLocality(); String city2 = address.getSubAdminArea(); String area1 = address.getSubLocality(); String area2 = address.getLocality(); if(pincode!=null && stat!=null) { state.setText(stat); pin.setText(pincode); } if(city1!=null) { city.setText(city1); } else if(city2!=null) { city.setText(city2); } if(area1!=null) { area.setText(area1); } else if(area2!=null) { area.setText(area2); } } catch (java.io.IOException e) { e.printStackTrace(); android.widget.Toast.makeText(context, "Failed to get address", android.widget.Toast.LENGTH_SHORT).show(); } } }
方案二:后台手动创建Looper(不推荐)
如果你一定要在后台线程处理,可以手动在doInBackground()里创建Looper,但这样会增加线程复杂度,而且位置回调还是在后台线程,后续更新UI还要切主线程,代码会更乱:
@Override protected Object doInBackground(Object[] objects) { Looper.prepare(); // 手动初始化Looper GPSlocater gpSlocater = new GPSlocater(context); gpSlocater.getLocation(); lat = gpSlocater.getLatitude(); longi = gpSlocater.getLongitude(); Looper.loop(); // 启动Looper循环 return null; }
这种方法不推荐,因为Looper.loop()会阻塞线程,你还得手动退出循环,很容易出问题。
额外注意事项
- 权限问题:别忘了在
AndroidManifest.xml添加定位权限,Android 6.0以上还要动态申请:<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> - 超时处理:设置
latch.await()的超时时间,避免后台线程无限等待。 - 停止监听:获取到位置后一定要调用
stopUsingGPS(),避免持续耗电。
内容的提问来源于stack exchange,提问作者Samarth patel

