美文网首页Android系统与设计模式
Android源码之单例设计模式

Android源码之单例设计模式

作者: wendy__xu | 来源:发表于2020-05-21 10:26 被阅读0次

    概念:单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。(也就是说在整个应用的生命周期内, 只有一个对象实例的存在)

    一、如何使用及注意点
    二、Android源码中所使用的单例;


    DCL模式(双重检查锁定模式)的正确使用方式

    一般我们使用DCL方法来实现单例模式时都是这样的模版代码:

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

    实际上,上述方法在多线程的环境下,还是会有可能创建多个实例。为什么呢?

    mSingleton = new Singleton()这行代码虚拟机在执行的时候会有多个操作,大致包括:

    • 为新的对象分配内存
    • 调用Singleton的构造方法,初始化成员变量
    • 将mSingleton这个引用指向新创建的Singleton对象的地址

    在多线程环境下,每个线程的私有内存空间中都有mSingleton的副本。这导致可能存在下面的情况:

    • 当在一个线程中初始化mSingleton后,主内存中的mSingleton变量的值可能并没有及时更新;
    • 主内存的mSingleton变量已经更新了,但在另一个线程中的mSingleton变量没有及时从主内存中读取最新的值

    这样的话就有可能创建多个实例,虽然这种几率比较小。

    那怎么解决这个问题呢?答案是使用 volatile关键字

    volatile关键字能够保证可见性,被volatile修饰的变量,在一个线程中被改变时会立刻同步到主内存中,而另一个线程在操作这个变量时都会先从主内存更新这个变量的值。

    更保险的单例模式实现

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

    单例可能会引起的问题

    1)、Context的泄漏

    //SingleInstance.class
    private volatile static SingleInstance mSingleInstance = null;
    private SingleInstance (Context context) {}
    
    public static SingleInstance getInstance(Context context) {
        if (mSingleInstance == null) {
            synchronized (SingleInstance.class) {
                if (mSingleInstance == null) {
                    mSingleInstance = new SingleInstance(context);
                }
            }
        }
        return mSingleInstance;
    
    }
    
    //TestActivity 
    public class TestActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            
            //这样就容易出问题了
            SingleInstance singleInstance = SingleInstance.getInstance(this);
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
        }
    }
    

    如上面那样直接传入TestActivity 的引用,如果当前TestActivity 退出了,但应用还没有退出,singleInstance一直持有TestActivity 的引用,TestActivity 就不能被回收了。

    解决方法也很简单,传入ApplicationContext就可以了。

    SingleInstance singleInstance = SingleInstance.getInstance(getApplicationContext());
    

    2)、View的泄漏

    如果单例模式的类中有跟View相关的属性,就需要注意了。搞不好也会导致内存泄漏,原因如上:

    //SingleInstance.class
    private volatile static SingleInstance mSingleInstance = null;
    private SingleInstance (Context context) {}
    
    public static SingleInstance getInstance(Context context) {
        if (mSingleInstance == null) {
            synchronized (SingleInstance.class) {
                if (mSingleInstance == null) {
                    mSingleInstance = new SingleInstance(context);
                }
            }
        }
        return mSingleInstance;
    
    }
    
    //单例模式中这样持有View的引用会导致内存泄漏
    private View myView = null;
    public void setMyView(View myView) {
        this.myView = myView;
    }
    

    解决方案是采用弱引用

    private volatile static SingleInstance mSingleInstance = null;
    private SingleInstance (Context context) {}
    
    public static SingleInstance getInstance(Context context) {
        if (mSingleInstance == null) {
            synchronized (SingleInstance.class) {
                if (mSingleInstance == null) {
                    mSingleInstance = new SingleInstance(context);
                }
            }
        }
        return mSingleInstance;
    
    }
    
    //    private View myView = null;
    //    public void setMyView(View myView) {
    //        this.myView = myView;
    //    }
    
    //用弱引用
    private WeakReference<View> myView = null;
    public void setMyView(View myView) {
        this.myView = new WeakReference<View>(myView);
    }
    

    注意:弱引用可能有为空的情况。

    被弱引用关联的对象只能生存到下一次垃圾收集发生之前,当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象


    二、Android源码中使用的单例模式;

    上面讲了如何使用及注意点,下面一起来看下android源码中,哪里用到了单例模式;

    LayoutInflater 单例分析

    在Android系统中,我们经常会通过Context获取系统级别的服务,比如WindowsManagerService, ActivityManagerService等,更常用的是一个叫LayoutInflater的类。这些服务会在合适的时候以单例的形式注册在系统中,在我们需要的时候就通过Context的getSystemService(String name)获取。我们以LayoutInflater为例来说明, 平时我们使用LayoutInflater较为常见的地方是在ListView的getView方法中。

    @Override
    public View getView(int position, View convertView, ViewGroup parent)
        View itemView = null;
        if (convertView == null) {
            itemView = LayoutInflater.from(mContext).inflate(mLayoutId, null);
            // 其他代码
        } else {
            itemView = convertView;
        }
        // 获取Holder
        // 初始化每项的数据
        return itemView;
    }
    

    通常我们使用LayoutInflater.from(Context)来获取LayoutInflater服务, 下面我们看看LayoutInflater.from(Context)的实现。

        /**
         * Obtains the LayoutInflater from the given context.
         */
        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;
        }
    

    可以看到from(Context)函数内部调用的是Context类的getSystemService(String key)方法,我们跟踪到Context类看到, 该类是抽象类。

    public abstract class Context {
        // 省略
    }
    

    使用的getView中使用的Context对象的具体实现类是什么呢 ?其实在Application,Activity, Service,中都会存在一个Context对象,即Context的总个数为Activity个数 + Service个数 + 1。而ListView通常都是显示在Activity中,那么我们就以Activity中的Context来分析。

    我们知道,一个Activity的入口是ActivityThread的main函数。在该main函数中创建一个新的ActivityThread对象,并且启动消息循环(UI线程),创建新的Activity、新的Context对象,然后将该Context对象传递给Activity。下面我们看看ActivityThread源码。

    public static void main(String[] args) {
            SamplingProfilerIntegration.start();
    
            // CloseGuard defaults to true and can be quite spammy.  We
            // disable it here, but selectively enable it later (via
            // StrictMode) on debug builds, but using DropBox, not logs.
            CloseGuard.setEnabled(false);
    
            Environment.initForCurrentUser();
    
            // Set the reporter for event logging in libcore
            EventLogger.setReporter(new EventLoggingReporter());
            Process.setArgV0("<pre-initialized>");
            // 主线程消息循环
            Looper.prepareMainLooper();
            // 创建ActivityThread对象
            ActivityThread thread = new ActivityThread();
            // false 代表不是系统应用
            thread.attach(false);
    
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
    
            AsyncTask.init();
    
            if (false) {
                Looper.myLooper().setMessageLogging(new
                        LogPrinter(Log.DEBUG, "ActivityThread"));
            }
    
            Looper.loop();
    
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    
        private void attach(boolean system) {
            sThreadLocal.set(this);
            mSystemThread = system;
            if (!system) {
                ViewRootImpl.addFirstDrawHandler(new Runnable() {
                    public void run() {
                        ensureJitEnabled();
                    }
                });
                android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
                                                        UserHandle.myUserId());
                RuntimeInit.setApplicationObject(mAppThread.asBinder());
                IActivityManager mgr = ActivityManagerNative.getDefault();
                try {
                    mgr.attachApplication(mAppThread);
                } catch (RemoteException ex) {
                    // Ignore
                }
            } else {
                   // 省略
            }
    }
    

    在main方法中,我们创建一个ActivityThread对象后,调用了其attach函数,并且参数为false. 在attach函数中, 参数为false的情况下, 会通过Binder机制与ActivityManagerService通信,并且最终调用handleLaunchActivity函数 ( 具体分析请参考老罗的博客 : Activity的启动流程),我们看看该函数的实现 。

     private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            // 代码省略
            Activity a = performLaunchActivity(r, customIntent);
            // 代码省略
        }
    
         private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
            // System.out.println("##### [" + System.currentTimeMillis() + "] ActivityThread.performLaunchActivity(" + r + ")");
            // 代码省略
            Activity activity = null;
            try {
                java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
                activity = mInstrumentation.newActivity(         // 1 : 创建Activity
                        cl, component.getClassName(), r.intent);
             // 代码省略
            } catch (Exception e) {
             // 省略
            }
    
            try {
                Application app = r.packageInfo.makeApplication(false, mInstrumentation);
    
                if (activity != null) {
                    Context appContext = createBaseContextForActivity(r, activity); // 2 : 获取Context对象
                    CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
                    Configuration config = new Configuration(mCompatConfiguration);
                    // 3: 将appContext等对象attach到activity中
                    activity.attach(appContext, this, getInstrumentation(), r.token,
                            r.ident, app, r.intent, r.activityInfo, title, r.parent,
                            r.embeddedID, r.lastNonConfigurationInstances, config);
    
                    // 代码省略
                    // 4 : 调用Activity的onCreate方法
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                    // 代码省略
            } catch (SuperNotCalledException e) {
                throw e;
            } catch (Exception e) {
                // 代码省略
            }
    
            return activity;
        }
    
    
        private Context createBaseContextForActivity(ActivityClientRecord r,
                final Activity activity) {
            // 5 : 创建Context对象, 可以看到实现类是ContextImpl
            ContextImpl appContext = new ContextImpl();           appContext.init(r.packageInfo, r.token, this);
            appContext.setOuterContext(activity);
    
            // 代码省略
            return baseContext;
        }
    

    通过上面1~5的代码分析可以知道, Context的实现类为ComtextImpl类。我们继续跟踪到ContextImpl类。

    class ContextImpl extends Context {
    
        // 代码省略
        /**
         * Override this class when the system service constructor needs a
         * ContextImpl.  Else, use StaticServiceFetcher below.
         */
         static class ServiceFetcher {
            int mContextCacheIndex = -1;
    
            /**
             * Main entrypoint; only override if you don't need caching.
             */
            public Object getService(ContextImpl ctx) {
                ArrayList<Object> cache = ctx.mServiceCache;
                Object service;
                synchronized (cache) {
                    if (cache.size() == 0) {
                        for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
                            cache.add(null);
                        }
                    } else {
                        service = cache.get(mContextCacheIndex);
                        if (service != null) {
                            return service;
                        }
                    }
                    service = createService(ctx);
                    cache.set(mContextCacheIndex, service);
                    return service;
                }
            }
    
            /**
             * Override this to create a new per-Context instance of the
             * service.  getService() will handle locking and caching.
             */
            public Object createService(ContextImpl ctx) {
                throw new RuntimeException("Not implemented");
            }
        }
    
        // 1 : service容器
        private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =
                new HashMap<String, ServiceFetcher>();
    
        private static int sNextPerContextServiceCacheIndex = 0;
        // 2: 注册服务器
        private static void registerService(String serviceName, ServiceFetcher fetcher) {
            if (!(fetcher instanceof StaticServiceFetcher)) {
                fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
            }
            SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
        }
    
    
        // 3: 静态语句块, 第一次加载该类时执行 ( 只执行一次, 保证实例的唯一性. )
        static {
            //  代码省略
            // 注册Activity Servicer
            registerService(ACTIVITY_SERVICE, new ServiceFetcher() {
                    public Object createService(ContextImpl ctx) {
                        return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());
                    }});
    
            // 注册LayoutInflater service
            registerService(LAYOUT_INFLATER_SERVICE, new ServiceFetcher() {
                    public Object createService(ContextImpl ctx) {
                        return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
                    }});
            // 代码省略
        }
    
        // 4: 根据key获取对应的服务,
        @Override
        public Object getSystemService(String name) {
            // 根据name来获取服务
            ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
            return fetcher == null ? null : fetcher.getService(this);
        }
    
        // 代码省略
    }
    

    从ContextImpl类的部分代码中可以看到,在虚拟机第一次加载该类时会注册各种服务,其中就包含了LayoutInflater Service, 将这些服务以键值对的形式存储在一个HashMap中,用户使用时只需要根据key来获取到对应的服务,从而达到单例的效果。这种模式就是上文中提到的“单例模式的实现方式5”。系统核心服务以单例形式存在,减少了资源消耗。

    优缺点:

    优点

    • 减少内存开支
    • 减少系统开销
    • 可以避免对资源的多重占用
    • 可以设置全局的访问点,优化和共享资源访问

    缺点
    单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。

    相关文章

      网友评论

        本文标题:Android源码之单例设计模式

        本文链接:https://www.haomeiwen.com/subject/sqenohtx.html