Android ANR,服务正在锁定主 GUI Activity

Android ANR , Service is locking main GUI Activity

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

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

我正在使用 AsyncTask 在我的 Activity onCreate():

中调用启动蓝牙服务

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

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

然后在 BluetoothServiceStart 服务 class 中,我使用 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:等待中 块主要@ 4817 在 java.lang.Object.wait(Object.java:-1) 在 java.lang.Thread.parkFor$(Thread.java:2135) - 锁定 <0x1a72> (a 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() 块,这是为了等待网络服务 return 字节的预期行为,否则在低网络 connectivity/bandwidth代码将继续执行并错过网络响应和数据的情况。

future.get() 阻塞了服务,但是既然 BluetoothService 是使用 BluetoothServiceStart AsyncTask 启动的,那么为什么 UI 被阻塞了???

谢谢

假设服务 运行 在另一个线程而不是起始 activity 是一个常见的错误。它将在主线程上 运行 并在此处声明:Services

Caution: A service runs in the main thread of its hosting process; the service does not create its own thread and does not run in a separate process unless you specify otherwise. If your service is going to perform any CPU-intensive work or blocking operations, such as MP3 playback or networking, you should create a new thread within the service to complete that work. By using a separate thread, you can reduce the risk of Application Not Responding (ANR) errors, and the application's main thread can remain dedicated to user interaction with your activities.

startService 调用不会改变此行为,即使您在 AsyncTask 中调用它也是如此。所以如果你想重新激活你的应用程序,你应该在你的服务中创建一个线程,它不会阻塞服务,因此不会阻塞主线程。

请注意,IntentServices 将任务卸载到工作线程,但当没有进一步的工作要做时会自动停止。

Clients send requests through Context.startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.

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

生命周期方法总是在 man 应用程序循环中 运行,因为系统会为您创建服务对象。因此,它的 onStartCommand 不会像您预期的那样在后台线程上 运行 。如果您希望服务在后台线程上 运行,请使用 IntentService。