What is background task app
Similar to music, recorder, users need a long time in the background of the use of products
Background:
The author has been working on a running app in his previous project. The user’s scenario is as follows: After the user starts running mode, we need to monitor Gps signals to collect the user’s exercise data, including distance, pace and time. In fact, it seems to be a very “simple” user scenario, the author also thought so at first, after a period of iterative improvement, now I would like to share some of the “not simple”. I will share the architectural evolution of such a running app from the perspective of a running app developer.
Initial architecture
In order to meet the needs of the product manager as soon as possible, the author completed the initial version of the APP without stopping. At this time, the architecture is like this
Activity + Forground Service + Sqlite+Eventbus Activity represents the UI layer, Service represents the forground Service that starts when running mode is turned on to record exercise data, Sqlite represents the data storage layer, and eventbus is an eventbus library for module decoupling.
Questions raised
After the initial version was released, we received some feedback from users that sports data mileage was lost and records were not accurate. Such problems are fatal for a sports app with data statistics. Then why are there such problems? It’s easy to guess because our app’s process was recycled
How to solve
The UI process is separated from the Service process and some services are kept alive, mainly based on the following two reasons
- Android process management mechanism
Here we have to mention the Android process management mechanism, Android system is through the Low Memory Killer mechanism (reference), which has several priorities for processes:
- native
- persistent
- forground
- visible
- The priority of each cache process depends on the value of oOM_adj calculated by the system. What factors affect oOM_adj? This is mainly about the amount of memory occupied by the process
- For apps like running, the user scene is run in the background for a long time. The foreground UI is only responsible for interaction, while the background service is responsible for business processing. Moreover, the MEMORY occupied by UI process is much larger than that of Sevice. So if you can free up all the UI resources when the app switches to the background, you can save a lot of memory when the app runs.
Revision of the second edition
For these two reasons, with the second refactoring, the architecture looks like this:
UI + Remote process (Service process)
So the question is, what are the pitfalls of switching from a single process to a multi-process app? The author encountered three main problems
- 1. How do processes communicate with each other
- 2. How can two processes access data to ensure process security
- 3. How to ensure the process security operation sharepreference
To solve the first problem, multi-process communication is as follows: 1.Broadcast: All communication protocols in this mode need to be sent and received in the intent, which is an asynchronous communication mode. That is, the result cannot be returned immediately after being called. In addition, the receiver needs to be registered in both UI and Service segments to communicate with each other.
2. The use of Messager Messenger is relatively simple. Define a Messenger and specify a handler as the communication interface, and return the getBinder method of Messenger when onBind. Create a Messenger in the UI using the returned IBinder, and they can communicate with each other. This invocation method is also an asynchronous invocation.
3. Asynchronous communication across components, commonly used in request-callback mode.
4. Rewrite Binder to communicate through AIDL we chose the last option: The main process invoks the Remote process through bindService, and registers a callback of the Remote process when onServiceConnection is used to listen and receive messages from the Remote process.
- It is first declared in androidmanifest.xml
Copy the code
- Declare the AIDL interface
IRemoteService {void registerCallback(IRemoteCallback cb); void unregisterCallback(IRemoteCallback cb); }Copy the code
IRemoteCallback {void onDataUpdate(double Distance,double duration, double pace, double calorie, double velocity); }Copy the code
- Rewrite the RemoteService Binder
LocalBinder mBinder = new LocalBinder(); IRemoteCallback mCallback; class LocalBinder extends IRemoteService.Stub { @Override public void registerCallback(IRemoteCallback cb) throws RemoteException { mCallback = cb; } @Override public void unregisterCallback(IRemoteCallback cb) throws RemoteException { mCallback = null; } public IBinder asBinder() { return null; }}Copy the code
- Rewrite the Binder of UI processes
public class RemoteCallback extends IRemoteCallback.Stub { @Override public void onActivityUpdate(final double distance, final double duration, final double pace, final double calorie, final double velocity) throws RemoteException { //do something } }Copy the code
- OnServiceConnection registers user interface binder with remote process
@Override public void onServiceConnected(ComponentName name, IBinder service) { try { mService = IRemoteService.Stub.asInterface(service); mService.registerCallback(mCallback); } catch (RemoteException e) { e.printStackTrace(); } } @Override public void onServiceDisconnected(ComponentName name) { try { if (mService ! = null) { mService.unregisterCallback(mCallback); } } catch (RemoteException e) { e.printStackTrace(); } mService = null; }Copy the code
Second, how do two processes access data to ensure consistency :ContentProvider encapsulates a layer of ContentProvider on top of Sqlite, so the existing architecture becomes:
UI process: Activity + eventbus
Remote process : Service + ContentProvider + Sqlite + Eventbus
There is also a third issue: user requirements: Multiple processes need to retrieve information about the state of a run, such as during a run, whether a run is paused or finished. A process uses SharePreference to store a persistent state. After the process is divided, MODE_MULTI_PROCESS is used. Later, it is found that the document comment is discarded. Data synchronization is inconsistent as follows:
SharedPreference loading flag: when set, the file on disk will be checked for modification even if the shared preferences instance is already loaded in this process. This behavior is sometimes desired in cases where the application has multiple processes, all writing to the same SharedPreferences file. Generally there are better forms of communication between processes, though.
So how to solve it? Two kinds of schemes
- 1. The ContentProvider + Sqlite Tray (github.com/grandcentri…).
- 2. The ContentProvider + SharePreference (MODE_PRIVATE) DPreference (github.com/DozenWang/D…).
Performance Comparison DPreference setString called 1000 times cost: 375 ms getString called 1000 times cost: 186 ms Tray setString called 1000 times cost : 13699 ms getString called 1000 times cost : 3496 ms
Another disadvantage of Scheme 1 is that if you migrate the old SharePreference data to SQLite, you need to copy all of it. Scheme 2 naturally avoids this problem and provides better read/write performance, so scheme 2 is adopted. So the architecture looks like this:
UI process: Activity + eventbus
Remote process : Service + (ContentProvider + Sqlite)+ (ContentProvider + SharePreference) + Eventbus
The above is the author in the multi process development encountered some problems and solutions, I hope to help you