ALTBeacon 库线程影响应用程序中的其他线程函数

ALTBeacon library threads impacting other thread functions in application

我们正在编写一个客户端服务器应用程序,它与服务器通信以通知其 BLE 位置并从服务器获取更新以执行任何请求的操作。此应用程序使用 ALT 信标库来检测 BLE 接近度并在 UI 上向设备用户显示接近度范围。该应用程序有服务和 activity,当 BLE 为 运行 时,这两个任务将非常频繁地(~每 30 秒)与服务器通信。我们已经意识到,当 BLE 接近启用(开始测距和监控)时,异步任务线程执行得非常晚,这会导致与服务器通信的延迟。我想知道我使用 ALT 信标库的方式是否会产生更多线程或泄漏?

伪代码如下:

public class BeaconScanner extends AppCompatActivity implements View.OnClickListener, ServerCallBack, BeaconConsumer {
    protected static final String TAG = "BeaconScanner";
    private static BeaconManager beaconManager;

    private static Handler timerHandler = new Handler();
    private Runnable timerRunnable = new Runnable() {
        @Override
        public void run() {
            refreshInfo();
            timerHandler.removeCallbacks(timerRunnable);
            timerHandler.postDelayed(this, 30 * 1000);
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ranging);
        mContext = this;

        //....

        enableBluetooth();
        myctx = getApplicationContext();
        if (!createNewRegion()){
            finish();
        }
        beaconManager = BeaconManager.getInstanceForApplication(myctx);
        //BeaconManager.setRssiFilterImplClass(ArmaRssiFilter.class);
        beaconManager.getBeaconParsers().add(new BeaconParser()
                .setBeaconLayout("m:2-3=beac,i:4-19,i:20-21,i:22-23,p:24-24,d:25-25"));
        beaconManager.bind(this);
        //...

        //timerHandler.postDelayed(timerRunnable, 0);
        logToDisplay();
    }


    private boolean createNewRegion() {
        if ((strBeacon == null) || strBeacon.isEmpty()) {
            return false;
        }
        try {
            Identifier id1 = Identifier.parse(strBeacon.substring(0, 8) + "-" +
                    strBeacon.substring(8, 12) + "-" +
                    strBeacon.substring(12, 16) + "-" +
                    strBeacon.substring(16, 20) + "-" +
                    strBeacon.substring(20, 32));
            Identifier id2 = Identifier.parse(strBeacon.substring(32, 36));
            Identifier id3 = Identifier.parse(strBeacon.substring(36, 40));
            regionZebra = new Region(getRandomString(10), id1, id2, id3);
            return true;
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
            return false;
        }

    }

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_BACK:
                showExitDialog(getString(R.string.exit_msg));
                return true;
        }
        return super.onKeyDown(keyCode, event);
    }

    private static final String ALLOWED_CHARACTERS = "0123456789qwertyuiopasdfghjklzxcvbnm";

    private static String getRandomString(final int sizeOfRandomString) {
        final Random random = new Random();
        final StringBuilder sb = new StringBuilder(sizeOfRandomString);
        for (int i = 0; i < sizeOfRandomString; ++i)
            sb.append(ALLOWED_CHARACTERS.charAt(random.nextInt(ALLOWED_CHARACTERS.length())));
        return sb.toString();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        restoreFindingDeviceState(0);
        if(null != beaconManager) {
            try {
                beaconManager.stopRangingBeaconsInRegion(regionZebra);
            } catch (Exception e) {
                Log.e(TAG, e.getMessage());
            }
            beaconManager.unbind(this);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        switch (id) {
            case android.R.id.home:
                showExitDialog(getString(R.string.exit_msg));
                break;
        }
        return super.onOptionsItemSelected(item);
    }


    @Override
    protected void onPause() {
        super.onPause();
        timerHandler.removeCallbacks(timerRunnable);
        if (beaconManager.isBound(this))
            beaconManager.setBackgroundMode(true);
        //restoreFindingDeviceState(0);
    }

    @Override
    protected void onResume() {
        super.onResume();
        timerHandler.postDelayed(timerRunnable, 0);
        restoreFindingDeviceState(1);
        if (beaconManager.isBound(this))
            beaconManager.setBackgroundMode(false);
    }

    @Override
    public void onClick(View v) {
        v.startAnimation(buttonClick);
        String appendData = "";
        String uri = "";
        ServerConnect serverConnect = null;
        switch (v.getId()) {
            case R.id.playsound:
                logMessage(TAG, "### Play sound button clicked" + uri);
                //Creates async task to communicate with server
                break;
            case R.id.found:
                //Show error to user
                //Creates async task to communicate with server
                break;
            case R.id.notfound:
               //Show error to user
               //Creates async task to communicate with server
                break;
        }
    }

    @Override
    public void onServerResponse(String response, int requestCode, String data) {
        //Process server response
    }

    public void refreshInfo() {


        if ((System.currentTimeMillis() - findBeaconTime) > 40 * 1000) {
            //displayState = Constants.BEACON_FINDING;
            logToDisplay();
        }

       //Creates async task to communicate with server to pull the latest info
    }

    private String changeFormatofBeaconID(String id) {
        String retString = "";
        if (id != null) {
            for (int i = 0; i < 4 - id.length(); i++) {
                retString = "0" + retString;
            }
        }
        return retString + id;
    }

    @Override
    public void onBeaconServiceConnect() {

        beaconManager.removeAllRangeNotifiers();
        beaconManager.addRangeNotifier(new RangeNotifier() {
            @Override
            public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
                if (beacons.size() > 0) {
                    for (Iterator<Beacon> iterator = beacons.iterator(); iterator.hasNext(); ) {
                        String id2 = "";
                        String id3 = "";
                        Beacon findBeacon = iterator.next();
                        if ((strBeacon == null) || (findBeacon == null)) {
                            continue;
                        }
                        try {
                            //....
                            if (true == uuidReceived.startsWith(uuidToSearch)) {
                                displayBeacon = findBeacon;
                                displayState = Constants.BEACON_FOUND;
                                logToDisplay();
                            }
                        } catch (Exception e) {
                            Log.e(TAG, "### Exception when compare Beacon, UUID is null");
                        }
                    }
                }
            }

        });

        beaconManager.removeAllMonitorNotifiers();
        beaconManager.addMonitorNotifier(new MonitorNotifier() {
            // If the phone enters a Beacon region
            @Override
            public void didEnterRegion(org.altbeacon.beacon.Region region) {
                try {
                    findBeaconTime = System.currentTimeMillis();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            // If the phone leaves a Beacon region
            @Override
            public void didExitRegion(org.altbeacon.beacon.Region region) {
                try {
                    displayState = Constants.BEACON_FINDING;
                    if ((System.currentTimeMillis() - findBeaconTime) > 5 * 1000) {
                        logToDisplay();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void didDetermineStateForRegion(int i, org.altbeacon.beacon.Region region) {

            }
        });

        try {
            beaconManager.startMonitoringBeaconsInRegion(regionZebra);
       } catch (RemoteException e) {
            Log.e(TAG, e.getMessage());
        }
        try {
            beaconManager.startRangingBeaconsInRegion(regionZebra);
        } catch (RemoteException e) {
            Log.e(TAG, e.getMessage());
        }
    }

    private void showRanging(int mode) {

        webViewRanging = (WebView) findViewById(R.id.webViewRanging);
        webViewRanging.setBackgroundColor(Color.TRANSPARENT); //for gif without background
        String gifName;
        if (mode == IMMEDIATE_RANGE)
            gifName = "immediate.gif";
        else if (mode == NEAR_RANGE)
            gifName = "near.gif";
        else if (mode == FAR_RANGE)
            gifName = "far.gif";
        else
            gifName = "none.gif";

        //Update the UI
    }


    private void logToDisplay() {
        runOnUiThread(new Runnable() {
            public void run() {
                TextView txtShowDistance = (TextView) findViewById(R.id.txtRanging);
                switch (displayState) {
                    case Constants.BEACON_FOUND:
                        txtShowDistance.setText("");
                        if (displayBeacon.getDistance() <= 3) {
                            showRanging(IMMEDIATE_RANGE);
                        } else  if (3 < displayBeacon.getDistance() &&  displayBeacon.getDistance() <= 8) {
                            showRanging(NEAR_RANGE);
                        } else  if (displayBeacon.getDistance() > 8) {
                            showRanging(FAR_RANGE);
                        }
                        break;

                    case Constants.BEACON_FINDING:
                        if (bleStatus)
                            txtShowDistance.setText(R.string.out_of_bt_range);
                        else
                            txtShowDistance.setText(R.string.ble_off_msg);
                        txtShowDistance.setTextColor(Color.BLACK);
                        showRanging(NOT_IN_RANGE);
                        break;
                }
            }
        });
    }

    private static boolean enableBluetooth() {
        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        boolean isEnabled = bluetoothAdapter.isEnabled();
        if (!isEnabled) {
            return bluetoothAdapter.enable();
        }
        return true;
    }

    public void showFoundDialog(String msg) {
        //Show alert to the user clicks on found button when the device is found
       //Creates async task to communicate with the server
    }

    public void showNotFoundDialog(String msg) {
        //Show alert to the user clicks on not found button when the device is found
         //Creates async task to communicate with the server
    }

    private void restoreFindingDeviceState(int mode) {
        //Creates async task to communicate with server
    }

    public void showExitDialog(String msg) {
        //Show application exit dialog
         //Creates async task to communicate with the server
    }

    public void showStatusChangeDialog() {
       //Show server changed the status changed dialog
    }

    public void showSnackBarInfo(String info, int mode) {
        final Snackbar snackbar = Snackbar.make(scrollLayout, info, Snackbar.LENGTH_LONG);
        snackbar.setActionTextColor(Color.WHITE);
        if (mode == 1) {
            View sbView = snackbar.getView();
            sbView.setBackgroundColor(Color.RED);
            snackbar.setDuration(15*1000);
        }
        snackbar.setAction("DISMISS", new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                snackbar.dismiss();
            }
        });

        snackbar.show();
    }

    private void handleSearchStatusError(int errorCode) {
        Log.e(TAG, "handleSearchStatusError called");
        switch (errorCode) {
            case Constants.WIFI_NOT_AVAILABLE:
                Log.e(TAG, "wifi not available");
                showErrorDialog(getString(R.string.details_wifi_not_connected), getString(R.string.wifi_error_desc));
                break;
            case Constants.ZDS_NOT_ENABLED:
                showErrorDialog(getString(R.string.zds_disabled), getString(R.string.zds_error_desc));
                break;
            default:
                break;
        }
    }

    private void showErrorDialog(String title, String message) {
        AlertDialog.Builder builder = new AlertDialog.Builder(mContext);
        builder.setTitle(title).
                setMessage(message).setCancelable(false)
                .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        mDialog.dismiss();
                        mDialog = null;
                    }
                });
        mDialog = builder.create();
        mDialog.show();
    }
}

}

以下日志消息:

01-30 10:43:21.582 30185-30492/com.company.myapp D/ServerConnect: ServerTask started:
01-30 10:43:21.583 30185-30492/com.company.myapp D/ServerConnect: ServerTask doInBackground ():/locationInfo/setDeviceCommand?<<deleted>>
    ServerTask doInBackground ():
01-30 10:43:21.587 30185-30492/com.company.myapp D/ServerConnect: url = <<deleted>>
01-30 10:43:23.436 30185-30578/com.company.myapp D/BeaconScanner: Find the Beaconid1: bd8a9fb8-613b-437f-f8a0-0dc8c7aedf06 id2: 0 id3: 71 type altbeacon
01-30 10:43:24.553 30185-30579/com.company.myapp D/BeaconScanner: Find the Beaconid1: bd8a9fb8-613b-437f-f8a0-0dc8c7aedf06 id2: 0 id3: 71 type altbeacon
01-30 10:43:25.674 30185-30580/com.company.myapp D/BeaconScanner: Find the Beaconid1: bd8a9fb8-613b-437f-f8a0-0dc8c7aedf06 id2: 0 id3: 71 type altbeacon
01-30 10:43:26.790 30185-30581/com.company.myapp D/BeaconScanner: Find the Beaconid1: bd8a9fb8-613b-437f-f8a0-0dc8c7aedf06 id2: 0 id3: 71 type altbeacon
01-30 10:43:27.902 30185-30583/com.company.myapp D/BeaconScanner: Find the Beaconid1: bd8a9fb8-613b-437f-f8a0-0dc8c7aedf06 id2: 0 id3: 71 type altbeacon
01-30 10:43:29.023 30185-30584/com.company.myapp D/BeaconScanner: Find the Beaconid1: bd8a9fb8-613b-437f-f8a0-0dc8c7aedf06 id2: 0 id3: 71 type altbeacon
01-30 10:43:30.135 30185-30585/com.company.myapp D/BeaconScanner: Find the Beaconid1: bd8a9fb8-613b-437f-f8a0-0dc8c7aedf06 id2: 0 id3: 71 type altbeacon
01-30 10:43:31.263 30185-30586/com.company.myapp D/BeaconScanner: Find the Beaconid1: bd8a9fb8-613b-437f-f8a0-0dc8c7aedf06 id2: 0 id3: 71 type altbeacon
01-30 10:43:31.817 30185-30492/com.company.myapp I/ServerConnect: Server Response code : 200
    Server Response : {<<deleted>>}
01-30 10:43:31.818 30185-30492/com.company.myapp D/ServerConnect: ServerTask started:
01-30 10:43:31.818 30185-30185/com.company.myapp D/ServerConnect: ServerTask: onPostExecute started

担心库耗尽线程的主要原因是,如果您在库的回调方法中执行长 运行 操作,didEnterRegion 或更具体地说 didRangeBeaconsInRegion。由于显示的代码并未指示任何长 运行 操作,因此这应该不是问题。

该库使用内部线程池来解析来自蓝牙堆栈的信标检测。这个线程池是这样初始化的:

mExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);

在四核处理器上,这将创建五个线程来解析信标数据包。其中大部分通常处于空闲状态,除非存在大量的蓝牙 LE 流量。对于您描述的用例,我怀疑这些线程是否会减慢您的进程。

您可能会发现您描述的问题与您设置的 AsyncTask 有关,与信标扫描无关。检查这一点的一种方法是不启动信标测距,而是使用某种计时器来模拟信标事件。如果问题仍然存在,则表示它不是特定于扫描的。

这里是sample app I wrote that does something similar, sending detections to a server. It sends one request to a server for each beacon detected, and even with a large number of beacons in the vicinity, I have not seen such slowdowns. You can see the AsyncTask implementation here.