IT_Programming/Android_Java

[펌] Making Multi-process Android applications.

JJun ™ 2016. 7. 12. 02:42



 출처: http://engineering.life360.com/engineering/2016/06/10/multi-process-android-apps/




It is possible to run various components of an Android application in different processes.

This is sometimes necessary and improves performance and runtime of the application.

But, there are some gotchas you need to be aware of, which are not well documented and not well understood.

Lets look at when this is useful, what are the challenges and how to address them.


All components of an Android application run in the same Linux process, by default.

But, it is possible to run different components of the application in different processes. This is useful in many circumstances.

Lets look at these.


From time to time, Android needs to kill some processes to reclaim memory for more important processes.

Android uses importance hierarchy to decide which ones to kill and which ones to keep.

A process with high memory usage is more likely to be killed by Android when in background. If you keep your background components (services, content providers) and foreground components (Activities) in the same process, the process is more likely to use more memory.

When this app goes to background, it is more likely to get killed. All components are killed when Android kills a process.

To decouple the background components from the UI lifecycle, it is better to run them in a separate process.


For example, if the app need to synchronize data between the client and server, it can do this in a background service which runs in a different process than the main UI process. This will result in less frequent restarts for the background process and makes it independent of the UI process life cycle. Also, crashes and exceptions in the UI process will not affect the background process and vice versa.


Another reason for having multiple processes could be to run several distinct features independent of each other. For example, if the app supports Email, Calendar, Contacts, Notes and Tasks, each of these could run in a separate process making their lifecycle independent of each other.



How to run in multiple processes?

The manifest entry for each type of component <activity>, <service>, <receiver>, <provider> supports an android:process attribute that can specify a process in which that component should run. You can set this attribute to run a component in a specific process. Multiple components could share a process and you can have as many processes as you need.


Challenges

When an Android app uses multiple processes, it needs to deal with data consistency across processes. Even if your code is well organized, it may not be easy to know which process context a particular piece of code might execute at run time.



Shared Data

Even if you can separate logic to run in multiple independent processes, they have to share some data at some point. If you do this sharing via files, databases and shared preferences, you will encounter issues with data inconsistency across processes.
If you use files, you need to listen for any changes to the files in other processes.
If you access a database from multiple processes, you will most certainly end up with hard-to-debug database corruption and database locking issues. If you use shared preferences, you must open it in multi-process mode and must handle the case where they could be changed in the other process by having an OnSharedPreferenceChangeListener().


Singletons

Singletons are an easy way to share state information and some data across components in an Android application. You can use synchronized to make the singleton methods thread-safe. But, when a singleton is used in multiple processes, there would be as many instances of the singleton objects as the number of processes it is used in. This is because processes don’t share address space and the singleton instance in one process is not visible to the other. If the singleton is initialized with data from shared prefs, databases or a file, it would be hard for all of the instances of the singletons (in different processes) to have consistent data and most likely, they will end up having different data during run time. Also, note that synchronized will not help in this situation. Look for the singletons in common code that is used across your application.



Let us assume you have a singleton class, say SubscriptionManager, which keeps track of features user subscribed to. You update the subscriptions to/from your back end in this class. You also store them in a file for offline access. You initialize the singleton from the file, if it exists. You have two processes: main, background. You need to check if the user subscribed to a feature in a utility class that could be called from either process. Now, let us say, the user subscribed to the feature from UI/main process. You call SubscriptionManager.getInstance(context).updateSubscription(feature, subscription);. This call updates the feature, sends to your backend and also updates the file.


In this situation, the SubscriptionManager instance in the background process is not aware of the subscription, unless you have a FileObserver on the file and update the singleton from the file contents. Or you might work around by broadcasting an intent that subscriptions are updated. Also, you might do unnecessary duplicate network calls to/from your back end from the two instances. As you can see, this gets quickly complicated and not the best solution.




Solution

Android provides inter process communication (IPC) via Binder interface. Content providers and Bound services use the binder interface to communicate across processes. So, the solution lies in utilizing these patterns to maintain your data and state consistent across processes. IntentServices or services with intents is another easy solution that may be suitable to your application.


Content Provider

Content providers manage access to a structured set of data. They encapsulate the data, and provide mechanisms for defining data security. Content providers are the standard interface that connects data in one process with code running in another process.



Though Content providers are meant to share data across applications, you can use them to share data in a multi-process application. Android guarantees the singleton nature of the content provider across processes. ContentResolver provides an easy interface so that the application code does not need to worry about IPC details.


NOTE: Do not export the content provider in the Manifest (android:exported=”false”) if you don’t need to share data outside your application.


Though Content provider and resolver’s "CRUD" (create, retrieve, update, and delete) API is mainly designed to share data in an SQL database, you can leverage the generic call API and extend to suit your app specific needs.

Example: In the above example about the singleton SubscriptionManager, You can have a provider API to check, update the subscription.

ContentResolver resolver = getContentResolver(); 
Bundle result = resolver.call(“SubcriptionCheck”, arg, extras);



Bound Service

A bound service is the server in a client-server interface. A bound service allows components (such as activities) to bind to the service,

send requests, receive responses, and even perform interprocess communication (IPC).



There are two options for implementing bound services: using Messengers and AIDL.

Messenger option is simpler and involves sending messages to the handlers in the service process and receiving replies through another

messenger in the caller.

In both these options, Android does the IPC heavy lifting (marshalling, unmarshalling, RPC) under the hood and makes it transparent to

the callers. The steps to communicate with the service

  • bindService
  • Get callback when the service gets connected.
  • Use the binder interface to send requests or call service APIs.
  • Get responses from services (if asynchronous)


Service with Intents

You can start services that run in a different process with an intent, or send broadcast intents from anywhere in the code and a receiver

running in a different process can receive it and start a service to process it.
For example, if you have a metric system that captures certain events across the application, you could send the metric to an Android service and let that service handle writing to database or sending to your backend metrics system, instead of accessing the DB and making network calls from everywhere in the code.


Caveat with Application class

You can provide your own implementation of Application class by creating a subclass and specifying the fully-qualified name of this subclass as the "android:name" attribute in your AndroidManifest.xml's <application> tag. The Application class, or your subclass of the Application class, is instantiated before any other class when the process for your application/package is created.

If you do the above, note that this class gets instantiated once for every process that is started in your application.

And unfortunately it is not (yet) possible to specify different application classes for different processes.




If you are using Application class, most likely, you are doing some initializations in this class’s onCreate(). Make sure to check if all the initializations are appropriate and necessary in all of your processes. You can check the process context and do only the necessary initializations appropriate to the process. You can use the following code in Application::onCreate() to determine what process context, the Application class is being initialized.


int pid = android.os.Process.myPid(); 
ActivityManager manager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE); 
for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) { 
    if (processInfo.pid == pid) { 
        String currentProcName = processInfo.processName; 
        if (!TextUtils.isEmpty(currentProcName) && currentProcName.equals(":background")) {
           //Rest of the initializations are not needed for the background
           //process
           return; 
        } 
    } 
}

/* Initializations for the UI process */

This way you can reduce memory usage, avoid a few large memory allocations and might even speed up your app launch speed. Example initializations include, loading proprietary fonts, Google Maps initialization, any other third party SDK initializations that are needed only in your main process.



Wrap Up

Hope you found this blog post helpful in understanding Android apps with multiple processes, challenges involved and some useful tips about how to address them.



Notes

  • Do not export your providers and services if you don’t need to make them accessible outside your application.
  • Set package on the intents and limit broadcast intent visibility to your application.