AltBeacon / android-beacon-library

Allows Android apps to interact with BLE beacons
Apache License 2.0
2.83k stars 836 forks source link

App using forground service scanning in the background cannot call CycledLeScannerForLollipop function successfully #1044

Closed maosuli closed 2 years ago

maosuli commented 2 years ago

Expected behavior

The app can get beacon signals every second in the background and at least does not have a long-term zero-beacon-nearby scanning.

Actual behavior

S1: In the background, taking Redmi (M2003J15SC) phone as an example, it can scan for a long period in the background. But around 1 min 5 s, the scanning results turn the actual value to 0 and never come back. Set debug mode to true. I found that the CycledLeScannerForLollipop function never appeared after around 1 min 5 s. That may be the reason why zero results were collected constantly.

Q1: Is there any way to check the function CycledLeScannerForLollipop calling status? If it is not called, at least I can know the scanning results are not convincing. One of the methods is to check the logcat. But I think is computationally intensive.

S2: Extra finding: Even I used the foreground service and set the notification, the app can be frozen when running in the background. The start time is random. Maybe after around 10 seconds or 20 min. But the frozen scanning can come back when you unlock the screen and turn to the app interface.

Q2: Do you have any suggestions to improve it?

Steps to reproduce this behavior

S1: Set debug mode to true and use logcat to check the detailed log data. S2: Record the scanning results and timestamp in the phone and check the data when background running is finished.

Mobile device model and OS version

S1: For some phones (SM, Nokia, and Redmi), a similar problem can happen in the initial six seconds due to Bluetooth registration failure. For Redmi (M2003J15SC, android 10), it always happens after about 1 min when running in the background.

S2: For some phones (Redmi and Nokia), the problem can happen when running the background. SM is better and can run for a long time without frozen issues.

Android Beacon Library version

V 2.17.1

Thank you for any comments and suggestions.

IMPORTANT: This forum is reserved for feature requests or reproducible bugs with the library itself. If you need help with using the library with your project, please open a new question on StackOverflow.com.

davidgyoung commented 2 years ago

OEM-specific background behavior is often restricted by proprietary power saving apps. See here for a description of Xiaomi/Redmi behavior: https://dontkillmyapp.com/xiaomi. this page gives steps for you to try to see if the problem goes away after disabling the power saving features described.

Please see equivalent pages on the same site for other OEMs

maosuli commented 2 years ago

Yes, it works for my Redmi phone. Thank you for your suggestion. However, I think it is not a wise idea to find every phone's battery saver setting. From your reference link, even the same phone model of Redmi may have a different interface design due to different MIUI versions.

One of my observations of the logcat data is that the app often fails to call CycledLeScannerForLollipop function when the battery saver code is triggered. In other words, if we can monitor the function call status, we can judge whether scanning is normal directly. I don't need to keep my app running in the background. But I want to know its scanning status.

One of the methods is to check the logcat within the app. But I think is computationally intensive. Do you have any suggestions?

Thank you.

davidgyoung commented 2 years ago

@LuZaiJiaoXiaL, unfortunately there is very little app developers (and library developers) can do when OEMs build proprietary and undocumented (basically secret) background app killers. Understand that this library is not part of the operating system, it is part of your app -- when you link it into your app it is the same as if you wrote the code yourself. OEM's like Xiaomi are actively trying to stop your app from performing their functions in the background by killing the process. Once the app process is killed, the library is no longer running either. There can be no callbacks to any of your app functions, including CycledLeScannerForLollipop (part of this library's code). When an app is killed, there is nothing left running to determine that a particular function is no longer being called.

This is a widespread problem in the industry, originally driven by Chinese OEMs starting about three years ago. For apps that really do need to do background work, they must find a way to get around these proprietary background app killers. A common approach is for apps to have an onboarding process which explains device-specific instructions to disable the battery optimization so the app can run in the background properly. The Tocsen app provides a good example of how you might do this.

How can your app build something like above? This library is about solving a specific problem: detecting bluetooth beacons. Overcoming proprietary background app killers is certainly critical to background detection use cases. But building functions that help circumvent these app killers across several different OEMs is a big project so it is simply out of scope for what this library can accomplish. I wish I could point you to another library that does a good job of focussing on this specific problem. Unfortunately, I do not know of one at this time. That means that if you want to instruct users to disable these power savers, you have to build your own screens.

There is one new library feature that might help here, at least for some OEMs. Library version 2.19-beta5 includes a new intent scan strategy that will register a beacon scan with the operating system to be delivered by Android Intent if there is a detection. In theory, this will launch your app to deliver the Intent if a bluetooth device matching your scan is detected when the app is not running. This will work on vanilla Android on Pixel phones if the app is killed by the OS, say, because the phone was low on memory while you were playing a game.

To try this, remove any code that enables foreground service scanning with the library and instead use this: beaconManager.setIntentScanningStrategyEnabled(true)

Will this work for Redmi devices in the background? It depends on whether or not the proprietary app killers block re-launching the app by intent after it has been killed. Since there is no documentation on these proprietary app killers, you simply have to try and see.

maosuli commented 2 years ago

Thanks for your detailed explanation. Will try your method later.

From my observation, taking Redmi as an example, when the battery saver code is triggered, initially the scanning will not stop right away. By contrast, it will have an abnormal scanning and collection.size() is 0. Then sometimes it will enter a frozen mode. I mean all the processes within the app stop working. But it is not killed. When you unlock the screen and turn to the app interface, all things come back. The scanning becomes normal and gives you the correct result. I also have a Nokia phone to validate the observation. The difference is that there just exists a frozen status.

In this case, if we can monitor the function call status, the normal scanning period is clear. And the scanning results can be more convincing. If the battery saver kills my app, intent may be designed to re-launch it. But for the time being, I haven't found a killing issue.

maosuli commented 2 years ago

By the way, I found that the Bluetooth will be registered every six seconds when having a constant foreground service scanning (disabling the scheduled job scanning). I am curious about the reasons. One of the explanations is that the phone cache memory has to be released time by time for Bluetooth scanning. Does this six-second restarting aim to solve this problem?

Thank you for your comments.

davidgyoung commented 2 years ago

There is little practical difference between killing an app and freezing it so it gets no cpu time. You still cannot run code to detect that it is in this condition unless that code can run outside the app.

I do not know what “Bluetooth registered” every six seconds means. It may be some system logging that happens at an even multiple of the scan period.

maosuli commented 2 years ago

Yeah, when the app is frozen, the code cannot run anyway. But different from killing, the app will wake up and turn to normal automatically. In this case, my idea is that if we can set a timestamp log and then compare the previous one and the next one after freezing disappears. The freezing status can be monitored.

For the Bluetooth registration, please see the screenshot attached. It seems there exists a six-second scanning mechanism within our library. 6sCircleToDavidYoung

davidgyoung commented 2 years ago

The above behavior is by library design. The library has a configurable scanPeriod, defaulted to 1100 ms. Because early Android BLE models (e.g. Nexus 4) only allowed a single scan to give you only one detection from each transmitter, it was necessary to stop and restart scanning every cycle (every 1100 ms by default) to get repeated detections. But the Android N release added a restriction preventing apps from starting scans more than once every 6 seconds. So I added logic to the library to refuse to stop/start scanning at the end of each cycle if the last scan start was within the last 6 seconds. This makes the logic work both on older Android devices and Android 6+. This logic is why you see these events every ~6 seconds.

See here: https://github.com/AltBeacon/android-beacon-library/blob/master/lib/src/main/java/org/altbeacon/beacon/service/scanner/CycledLeScanner.java#L412

maosuli commented 2 years ago

OK. Thank you for your clear explanation. Got it. From another document, I know that if Bluetooth has long-term scanning, the full cache memory may disable the normal scanning. Have you heard about this phenomenon? And can the 6-second restart here solve it? One of the opinions is that 15-second stopping for every minute is recommended.

davidgyoung commented 2 years ago

I have not heard of this, no. Do you believe this is part of the standard Bluetooth spec? A feature of certain Bluetooth chips? An Android open source project feature? A Xiaomi extension?

if part of the Bluetooth spec or AOSP, then it should be documented and affect most all Android models.

From what I hear you describe, the problem you are seeing is that the main CPU on the phone stops executing your app code. The CycledLEScanner logging is based on timers firing and these timers stop. This does not sound like a Bluetooth scan issue but a CPU scheduling issue — an OS level function.

The suggestion I made above about the Intent scan strategy may help this case because it would provide another OS “kick” that might override the proprietary OS extension stopping your app from running. It all depends on the secret details of how that OS extension works.

maosuli commented 2 years ago

OK. Got it. Thanks for your experience sharing.