Android Design Patterns (I) - Singleton Patterns

Posted by Liodel on Tue, 16 Jul 2019 00:34:20 +0200

Recently, I read the book "Android Source Design Pattern Analysis and Practice" and found that there are some analysis of the source code. I haven't had a good look at the design pattern before. Let's take a note and follow it.

Definition

Make sure that there is only one instance of a class, and instantiate it and provide it to the whole system.

Use scenarios

You need to ensure that a class has only one instance of the scenario, to avoid generating too many resources with multiple object sizes, or that the class should have only one instance. For example, creating an object consumes too much resources, or accessing resources such as IO and database.
Configuration files, tool classes, thread pools, caches, log objects, etc.

UML class diagram



Role introduction:
Client - High Level Client
Singleton - Singleton class

Key points for implementing the singleton pattern are:

  • The constructor is not open to the outside world, usually Private. It does not allow external access to objects through new Singleton().
  • Returns a singleton class object through a static method or enumeration, such as the getInstance() method.
  • Ensure that there is only one object for a singleton class, especially in the case of multithreading. Ensure that singletons can be implemented even in multithreading.
  • Ensure that singleton class objects are not rebuilt when deserialized.

Ways of realization

Hungry Chinese Style

public class Singleton {
    private static final Singleton mSingleton = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        return mSingleton;
    }
}

The object inside is a static object, which is initialized the first time it is declared. This object can only be accessed externally through getInstance(), which implements the singleton.

Lazy-man Style

public class Singleton {
    private static  Singleton mSingleton ;
    private Singleton(){}
    public static synchronized Singleton getInstance(){
        if (mSingleton==null){
            mSingleton=new Singleton();
        }
        return mSingleton;
    }
}

The getInstance() method here adds the synchronized keyword to ensure that multiple threads can also implement singletons.

Advantage:
Singles can only be instantiated when they are used, which saves resources to a certain extent.
Disadvantages:
(1) A single case should be instantiated in time at the first loading, with a slightly slow response.
(2) Every call to getInstance() must be synchronized, resulting in unnecessary synchronization overhead.

So it is generally not recommended.

Double Check Lock (DCL) Double Check Lock

public class Singleton {
    private static  Singleton mSingleton ;
    private Singleton(){}
    public static  Singleton getInstance(){
        if (mSingleton==null){
            synchronized (Singleton.class){
                if (mSingleton==null){
                    mSingleton=new Singleton();
                }
            }
        }
        return mSingleton;
    }
}

In this getInstance method, mSingleton is blanked twice: the first is to avoid unnecessary synchronization locks; the second is to create instances at null.
DCL failure:
In multithreading, assuming that thread A executes to mSingleton=new Singleton(), the CPU does not execute this statement at once, because it is not an atomic operation (i.e., an operation that will not be interrupted by the thread scheduling mechanism).
For example, execute Timer timer = new Timer(); you can see from the bytecode file that this line of code is compiled as follows:

         0: new           #2                  // class java/util/Timer
         3: dup
         4: invokespecial #3                  // Method java/util/Timer."<init>":()V
         7: astore_1
         8: return

So mSingleton=new Singleton() does roughly three things:
(1) Allocate memory to instances of Singleton
(2) Calling the construction method of Singleton
(3) Point mSingleton to allocated memory space (mSingleton is not empty at this time)
Because the Java compiler allows the processor to execute in disorder, the execution order of step two and step three above cannot be guaranteed. The order of execution may be 1-2-3 or 1-3-2.
When thread A executes in the order of 1-3-2, if it reaches 1-3 and step 2 is not executed, if thread B judges mSingleton==null, it will send FALSE results, thus returning an incorrect singleton.

Advantage:
Resource utilization is high, and the first time getInstance is executed, it will be instantiated and efficient.
Disadvantages:
The first volume has a slightly slow loading response and the possibility of failure, but the probability is very small.

This is the most commonly used way to implement singletons, which can be guaranteed in most cases.

Static inner class

public class Singleton {
    private Singleton(){}
    public static  Singleton getInstance(){
        return SingletonHolder.mSingleton;
    }
    private static class SingletonHolder{
        private static final Singleton mSingleton = new Singleton();
    }
}

The mSingleton is not initialized when the first Singleton is loaded, and the SIngletonHolder class is loaded only when the getInstance is called for the first time.
Advantage:
It not only ensures thread safety, but also guarantees the uniqueness of singletons. It also delays the instantiation and recommendation of singletons.

Enumeration of singletons

public enum Singleton{
    INSTANCE;
    public void doThing(){
        System.out.println(this.hashCode());
    }
}

Singleton singleton = Singleton.INSTANCE; can be used to obtain the singleton.
Advantage:
Writing is simple and thread-safe by default, which is a singleton in any case.

Characteristic:
In one case, the above will fail singly and create objects repeatedly, that is, deserialization.
When deserializing, a readResolve() method is called to regenerate an instance, so the following methods are needed to solve this problem:

public class Singleton {
    private Singleton(){}
    public static  Singleton getInstance(){
        return SingletonHolder.mSingleton;
    }
    private static class SingletonHolder{
        private static final Singleton mSingleton = new Singleton();
    }

    private Object readRedolve() throws ObjectStreamException{
        return SingletonHolder.mSingleton;
    }
}

Use containers to implement singletons

public class SingletonManager {
    private static Map<String,Objects> objMap = new HashMap<>();
    private SingletonManager(){}
    public static void registerService(String key,Object obj){
        if (!objMap.containsKey(key)){
            objMap.put(key,obj);
        }
    }
    public static Object getService(String key){
        return objMap.get(key);
    }
}

At the beginning of the program, many singleton objects are placed in a container, and the corresponding singleton objects are obtained according to the key.

Singleton pattern in Android source code

There are so many singleton patterns in the source code that there is even a special abstract class of singletons:

package android.util;
public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

We often get some system services through context. getSystem Service (String name), such as Activity Manager in Activity:

ActivityManager  mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);

Take LayoutInflater as an example in the book. Usually the way to get it is LayoutInflater.from(context). Look at this method:

package android.view;
public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

Discovery is also obtained by calling context. getSystem Service (String name).

So how does a singleton work out? Look up the code.
context.getSystemService(String name) will enter if it is entered directly

package android.content;
public abstract class Context {
...
    public abstract Object getSystemService(@ServiceName @NonNull String name);
...
}

By analyzing the start-up process of activity, we can see that the specific implementation of Context is in ContextImpl.java, see the specific implementation code:

package android.app;
class ContextImpl extends Context {
...
    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
}
...

Then continue SystemService Registry. getSystemService (this, name):

package android.app;
final class SystemServiceRegistry {
    ...
//A container for getSystem Service, in which ServiceFetcher<?> is stored.
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
...
//Static code blocks are executed at the first load and only once, ensuring the uniqueness of registered services.
    static {
        registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
                new CachedServiceFetcher<AccessibilityManager>() {
            @Override
            public AccessibilityManager createService(ContextImpl ctx) {
                return AccessibilityManager.getInstance(ctx);
            }});
        registerService(Context.DOWNLOAD_SERVICE, DownloadManager.class,
                new CachedServiceFetcher<DownloadManager>() {
            @Override
            public DownloadManager createService(ContextImpl ctx) {
                return new DownloadManager(ctx);
            }});
        ...
//There are also many service registrations.
    }

  ...
//This method is called in the static code block, and the service name and the created service are placed in the container to realize the singleton.
      private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }
  ...
  public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
  ...
}

Instead of getting the service directly, it calls fetcher.getService(ctx) to get the service. Look at Service Fetcher <?>:

static abstract interface ServiceFetcher<T> {
        T getService(ContextImpl ctx);
    }

This is an interface. Look at the method in the static code block above to find the CachedService Fetcher class used for registering services:

static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
        private final int mCacheIndex;

        public CachedServiceFetcher() {
            mCacheIndex = sServiceCacheSize++;
        }

        @Override
        @SuppressWarnings("unchecked")
        public final T getService(ContextImpl ctx) {
//Ctx.mService Cache is to get an array: new Object [sService CacheSize];
//The length of the array is the variable in the construction method. For each service registered, a new CachedService Fetcher corresponds to it, and then the length of the array is + 1. The first time you get this array, it must be an empty array.
            final Object[] cache = ctx.mServiceCache;
            synchronized (cache) {
                // Fetch or create the service.
                Object service = cache[mCacheIndex];
//The first time you get this service, the array is empty, so service = null is TRUE.
                if (service == null) {
//Call the createService method implemented at registration time and place the generated specific services in the corresponding subscripts of the array.
//It is then retrieved directly from the array. The singleton is realized.
                    service = createService(ctx);
                    cache[mCacheIndex] = service;
                }
                return (T)service;
            }
        }
      //  Implementing in Static Code Block
        public abstract T createService(ContextImpl ctx);
    }

There is an abstract method that needs to be instantiated. The method in the static code block implements the createService(ContextImpl ctx) method and returns the corresponding service.
Attached is a screenshot of some registered services, which can not be truncated:


summary

Advantage:

  • Since singleton mode has only one instance in memory, which reduces memory overhead, especially when an object needs to be created frequently, or when the performance cannot be optimized when it is created or destroyed, the advantages of singleton mode are obvious.
  • Because only one instance is generated, the system performance overhead is reduced.
  • It can avoid multiple occupancy of resources, such as file operations.
  • Singleton mode can be set as a global access point to optimize and share resource access.

shortcoming

  • Singleton mode generally has no interface, and it is difficult to expand. It is necessary to modify the source code.
  • In Android, if the singleton mode holds the Content of Activity, it is prone to memory leaks. So try to use Application Context.

Topics: Android Java Database