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 出现问题

在 targetSdkVersion 8 上运行 AsyncTask 时 Android 崩溃

Android AsyncTask 和登录