Android:当我使用 AsyncTask 时出现“调用线程必须是准备好的 Looper 线程”错误
Posted
技术标签:
【中文标题】Android:当我使用 AsyncTask 时出现“调用线程必须是准备好的 Looper 线程”错误【英文标题】:Android: "Calling thread must be a prepared Looper thread" error when I use AsyncTask 【发布时间】:2017-07-17 21:51:24 【问题描述】:我创建了一个 AsyncTask 来检索具有 20 米精度的 GPS 位置。 我想执行一段时间循环,直到精度可以接受。
问题是当我请求更新位置时出现异常。
java.lang.NullPointerException: Calling thread must be a prepared Looper thread.
这是出错的一段代码
@Override
protected String doInBackground(Void... params)
if (ActivityCompat.checkSelfPermission(activity, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(activity, android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED)
return null;
mLocationRequest.setInterval(100);
mLocationRequest.setPriority(100);
mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
while (mLastLocation == null || (mLastLocation.getAccuracy()>Float.parseFloat("20.0") && mLastLocation.hasAccuracy()))
try
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,mLocationRequest,activity);
mLastLocation = LocationServices.FusedLocationApi.getLastLocation(mGoogleApiClient);
catch (SecurityException secex)
return null;
【问题讨论】:
你在 doInBackgroud() 中做一些与 UI 相关的事情 首先在doInBackgroud()
中添加Looper.perpare()
。我不确定,但试试看。
发布完整的日志输出
【参考方案1】:
一整天的工作我都遇到了麻烦。我在 Service 的后台运行了 FusedLocationApi。
我所做的——我在其他地方读到过——添加 Looper.getMainLooper() 作为requestLocationUpdates()
的最后一个参数。
所以,而不是这个
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient,mLocationRequest,activity);
你会有
LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this, Looper.getMainLooper());
如果您阅读整个FusedLocationProviderApi 页面,您会发现该方法会根据参数执行不同的操作。
【讨论】:
小心这个!回调中的所有代码都将在 mainUI 线程上运行。如果它是密集的代码,你可以让你的应用程序卡顿。 我尝试了不同的方法来解决这个问题。我什至设置了自己的循环器和处理程序,但没有用。我停止使用该方法来获取位置更新。我是用 FusedLocationClient 来做的,不需要这个参数。 查看我的新答案。它应该避免 MainThread 问题。 ***.com/a/45068298/42994 你正在使用 Guava.. 特别是我坚持框架,即使 Guava 是由谷歌开发的。 是的,您的解决方案不太适合我的用例。我在后台获取方法中需要一个位置,我不想让 gps 保持开启状态。尽管您所拥有的可能是更常见的位置用例。【参考方案2】:在doInBackground中运行时使用Looper.prepare() or runOnUiThread()
函数改变UI或改变doInBackground以外的方法
例子:
runOnUiThread(new Runnable()
@Override
public void run()
progressBar = new ProgressDialog(MainActivity.this);
progressBar.setCancelable(false);
progressBar.setTitle("Downloading Files...");
progressBar.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressBar.setProgress(0);
progressBar.setMax(100);
progressBar.show();
);
【讨论】:
Looper.prepare() 似乎有效!我不明白为什么需要它,因为我没有在 UI 中进行更改。 (googleapiclient 是 UI 的一部分?) runOnUiThread() 适用于 Runnable,不接受 AsyncTask 作为参数。有什么想法吗? 如果 Looper.prepare() 有效,那么您正在同时执行多个可运行文件。请检查一下【参考方案3】:我使用了来自 guava 的 SettableFuture,这样当调用返回时,我就不会在主循环器上运行不必要的代码。 (如果您的回调启动了一些繁重的工作负载并可能导致 UI 卡顿,这可能是一个问题。)
protected String doInBackground(Void... params)
SettableFuture<Location> future = SettableFuture.create();
Log.w(TAG, "before fused call isMainLooper= %b", Looper.getMainLooper().isCurrentThread());
// future.set runs on the main thread.
//future.get stays on the background thread.
mFusedLocationApi.requestLocationUpdates(
client,
LOCATION_REQUEST,
location1 ->
future.set(location1);
Log.w(TAG, "callback: isMainLooper= %b", Looper.getMainLooper().isCurrentThread());
Looper.getMainLooper());
location = future.get(10, SECONDS);
Log.w(TAG, "after fused call isMainLooper= %b", Looper.getMainLooper().isCurrentThread());
日志输出
W TAG: before fused call isMainLooper = false
W TAG: callback: isMainLooper = true
W TAG: after fused call isMainLooper = false
【讨论】:
【参考方案4】:我在后台获取位置时遇到了问题。当我发现异常时,我添加了 Looper.getMainLooper.. 然后我决定再次阅读文档,我完全改变了在后台获取位置的方式;不同之处在于使用FusedLocationProviderClient
和这样的回调。
首先定义您的提供者客户端。
private FusedLocationProviderClient mFusedLocationClient;
定义位置回调,确保使用com.google.android.gms.location.LocationCallback
/**
* Fuse location api callback
*/
private LocationCallback mLocationCallback = new LocationCallback()
@Override
public void onLocationResult(LocationResult locationResult)
super.onLocationResult(locationResult);
for (Location location : locationResult.getLocations())
mLastLocation = location;
//Send to your server or update your UI
@Override
public void onLocationAvailability(LocationAvailability locationAvailability)
super.onLocationAvailability(locationAvailability);
//Send to your server or update your UI
;
定义请求位置更新的方法
public void startLocationUpdates()
boolean hasLocationPermission = ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED &&
ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
if (!hasLocationPermission)
gettingLocationUpdates = false;
return;
mFusedLocationClient.requestLocationUpdates(mLocationRequest, mLocationCallback, /*Looper*/ null);
com.google.android.gms.tasks.Task<Location> task = mFusedLocationClient.getLastLocation();
task.addOnCompleteListener(task1 ->
mLastLocation = task1.getResult();
gettingLocationUpdates = true;
);
在你的服务的 onCreate 中初始化它并开始请求更新
@Override
public void onCreate()
mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this);
startLocationUpdates();
不要忘记删除位置更新,否则它永远不会停止
@Override
public void onDestroy()
if (mFusedLocationClient != null)
mFusedLocationClient.flushLocations();
mFusedLocationClient.removeLocationUpdates(mLocationCallback);
试一试,然后告诉我。
【讨论】:
以上是关于Android:当我使用 AsyncTask 时出现“调用线程必须是准备好的 Looper 线程”错误的主要内容,如果未能解决你的问题,请参考以下文章
Android:尝试通过 AsyncTask 加载 videoview 时出现 NullPointerException [重复]
OnCreate 中的 AsyncTask 在运行时失败,如何在 Android 中避免这种情况?
当我将 Android Location 对象传递给 asyncTask 时,它返回 null
Android:使用 Asynctask 时 Recyclerview 出现问题