Android ANR,服务正在锁定主要的GUI活动

时间:2018-07-11 19:50:11

标签: android multithreading android-anr-dialog futuretask

我正在启动一些后台服务,由于Web服务调用等原因,这些服务需要一些时间来配置...

但是我通过AsyncTask启动这些服务,以避免锁定主线程和GUI,但是GUI仍然被锁定。

我正在使用AsyncTask调用我的Activity onCreate()中的启动BluetoothService:

我只包含了相关的代码行:

 //asynchronously - start the bluetooth service
            new BluetoothServiceStart().execute();

然后在BluetoothServiceStart服务类中,Im使用Callable&Future任务从Web服务获取字节:

 @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // stop the service when the notification bar is pressed
        if (intent != null && ACTION_STOP_SERVICE.equals(intent.getAction())) {
            Log.d(TAG, "Stopping bluetooth service...");
            broadcastServiceState(false);
            stopSelf();
            return START_NOT_STICKY;
        }

        // in case of attempting to restart while already running
        clearSubscriptions();

        Util.logToast(this, TAG, "Bluetooth service starting", Toast.LENGTH_SHORT, Util.DEBUG);
        setupNotification();


        // find and load JSON config file
        loadDevices();
}

   /**
     * Gets the UUIDs of devices to connect to from the bluetooth JSON file.
     */
    private void loadDevices() {
        devicesLoaded = false;


        Byte [] bytesFromWebService = null;
        InputStream is = null;
        URL url = null;
        try {

            if (ConnectivityMonitoring.hasNetwork()) {
                //lets get the path of rest service that has the config file
                String address = NgfrApp.getContext().getResources().getString(R.string.address);
                String configuration_restful_port = NgfrApp.getContext().getResources().getString(R.string.rest_port);
                String client_name = NgfrApp.getContext().getResources().getString(R.string.client_name);
                String protocol = NgfrApp.getContext().getResources().getString(R.string.protocol);
                //construct bluetooth config path
                String bluetooth_config_path = NgfrApp.getContext().getResources().getString(R.string.bluetooth_path);
                url = new URL(protocol + "://" + address + ":" + configuration_restful_port + bluetooth_config_path + client_name);
                //lets execute an FutureTask (async task with a result, that blocks until result is returned).
                ExecutorService exService = Executors.newSingleThreadExecutor();
                Log.i(TAG, "making call to URL:" + url.toString());
                Future<byte []> future = exService.submit(new CallWebServiceAndGetBytes(url));
                bytesFromWebService = Util.toObjects(future.get());
            }
            if (bytesFromWebService != null) {
                devices = readDeviceConfigFromWebService(bytesFromWebService);
                Log.i(TAG, "Loaded configuration from URL:" + url.toString());

            } else {
                // read in the device UUIDs from the file
                is = Util.scanForJson(getString(R.string.file_path), getString(R.string.bt_config_file));
                devices = Util.readJsonStream(is, localConfigReadFunc);
                Log.i(TAG, "Read config file from PATH:" + getString(R.string.file_path)+getString(R.string.bt_config_file));
            }
            if (devices != null) {
                if (devices.size() < 1)
                    Log.w(TAG, "No devices to load!");
                devicesLoaded = true;
            }

            // devices successfully loaded
            if (devices != null && devicesLoaded) {
                Log.d(TAG, "" + devices.size() + " BLE device IDs retrieved");
                Log.d(TAG, "Devices: " + devices.toString());
            }

            // failed to load devices or find the JSON file
            else {
                Log.e(TAG, "Unable to load devices! Creating empty list...");
                devices = new ArrayList<>();
            }
        }
        catch (MalformedURLException e1) {
            e1.printStackTrace();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        catch (FileNotFoundException e) {
            Log.d(TAG, "Unable to locate bluetooth config file: " + getString(R.string.bt_config_file));
        } catch (IOException e) {
            Log.d(TAG, "Error reading json file: " + e.getMessage());
        }

    }//end loadDevices

我遇到了ANR,后来又崩溃了。

Android线程转储:

“ main @ 4817” prio = 5 tid = 0x2 nid = NA等待   java.lang.Thread.State:正在等待      阻止main @ 4817       在java.lang.Object.wait(Object.java:-1)       在java.lang.Thread.parkFor $(Thread.java:2135)       -锁定<0x1a72>(一个java.lang.Object)       在sun.misc.Unsafe.park(Unsafe.java:358)       在java.util.concurrent.locks.LockSupport.park(LockSupport.java:190)       在java.util.concurrent.FutureTask.awaitDone(FutureTask.java:450)       在java.util.concurrent.FutureTask.get(FutureTask.java:192)       在ngfr.wams.controller.core.services.RuleEngineService.loadRules(RuleEngineService.java:358)       在ngfr.wams.controller.core.services.RuleEngineService.updateRules(RuleEngineService.java:462)       在ngfr.wams.controller.core.services.RuleEngineService.onCreate(RuleEngineService.java:131)       在android.app.ActivityThread.handleCreateService(ActivityThread.java:3542)       在android.app.ActivityThread.-wrap4(ActivityThread.java:-1)       在android.app.ActivityThread $ H.handleMessage(ActivityThread.java:1786)       在android.os.Handler.dispatchMessage(Handler.java:105)       在android.os.Looper.loop(Looper.java:164)       在android.app.ActivityThread.main(ActivityThread.java:6938)       在java.lang.reflect.Method.invoke(Method.java:-1)       在com.android.internal.os.Zygote $ MethodAndArgsCaller.run(Zygote.java:327)       在com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1374)

错误行指向future.get()

我了解到future.get()块是一种预期的行为,目的是为了等待Web服务返回字节,否则在网络连接/带宽较低的情况下,代码将继续执行并错过网络响应和数据。

future.get()阻止了该服务,但是由于BluetoothService是使用BluetoothServiceStart AsyncTask启动的,所以为什么UI被阻止了??

谢谢

2 个答案:

答案 0 :(得分:0)

一个普遍的错误假设是,服务正在启动活动之外的另一个线程上运行。它将在主线程上运行,如下所示:Services

  

警告:服务在其托管过程的主线程中运行;除非另行指定,否则该服务不会创建自己的线程,也不会在单独的进程中运行。如果您的服务要执行任何占用大量CPU的工作或阻止操作(例如MP3播放或联网),则应在服务内创建一个新线程以完成该工作。通过使用单独的线程,可以降低应用程序无响应(ANR)错误的风险,并且应用程序的主线程可以保持专用于用户与活动的交互。

startService调用不会更改此行为,即使您在AsyncTask中调用它也是如此。因此,如果要重新激活应用程序,则应在服务内部创建一个线程,该线程不会阻止该服务,因此也不会阻止主线程。

请注意,IntentServices将任务卸载到工作线程中,但是在没有其他工作要做时会自动停止。

  

客户端通过Context.startService(Intent)调用发送请求;该服务将根据需要启动,使用工作线程依次处理每个Intent,并在工作耗尽时自行停止。

也许这不是您想要的蓝牙服务。

答案 1 :(得分:0)

生命周期方法始终在man应用程序循环上运行,因为系统会为您创建Service对象。因此,它的onStartCommand不会在您想要的后台线程上运行。如果您希望服务在后台线程上运行,请使用IntentService。