隱藏在 SDK 中的單例類别範本

Youlou發表於2019-02-26

原始碼分享,如需轉載,請註明作者:Yuloran (t.cn/EGU6c76)

前言

單例類寫法,網上有諸多介紹,此處不再贅述。不過仍有一點需要注意一下:從程式設計規範角度來講,單例類應當是不可繼承和構造器私有的,即:

public final class ActivityMgrService
{
    private ActivityMgrService()
    {
    }
}
複製程式碼

Kotlin 在這方面做的很好,對程式設計安全做了很多優化。比如,類預設不可繼承,如果需要被繼承,需顯式使用 open 關鍵字。

單例模板

雖然單例寫法很多,但是同一個專案不可能允許那麼多的單例類寫法同時存在。所以,我們需要一個單例模板。其實,Android SDK 早就為我們提供了這樣一個模板,可能很多人不知道,因為它是 @hide 的。

Singleton 原始碼

/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.util;

/**
 * Singleton helper class for lazily initialization.
 *
 * Modeled after frameworks/base/include/utils/Singleton.h
 *
 * @hide
 */
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;
        }
    }
}
複製程式碼

在 Android Studio 中雙擊 Shift,輸入 Singleton,即可檢索到此類。因為是 @hide,所以需要手動複製到自己的專案中。

使用示例

public final class ActivityMgrService
{
    private static final String TAG = "ActivityMgrService";

    private static final Singleton<ActivityMgrService> INSTANCE = new Singleton<ActivityMgrService>()
    {
        @Override
        protected ActivityMgrService create()
        {
            return new ActivityMgrService();
        }
    };

    private ActivityMgrService()
    {
    }

    public static ActivityMgrService getInstance()
    {
        return INSTANCE.get();
    }

    ...
}
複製程式碼

ActivityMgrService 是筆者寫的一個監控專案中所有 Activity 生命週期的工具類,對此類感興趣可以點選連結自行獲取。

Live Template

可以新建一個 Live Template 來一鍵生成以上程式碼:

1. 新建 Template Group

隱藏在 SDK 中的單例類别範本

2. 新建 Template

隱藏在 SDK 中的單例類别範本

3. 手動編寫 Template

隱藏在 SDK 中的單例類别範本

下面是筆者的單例生成模板:

private static final Singleton<$class$> INSTANCE = new Singleton<$class$>() {
    @Override
    protected $class$ create() {
        return new $class$();
    }
};

private $class$() {
}

public static $class$ getInstance() {
    return INSTANCE.get();
}
複製程式碼

模板變數配置:

隱藏在 SDK 中的單例類别範本

4. 匯入 Template

也可以直接匯入筆者的單例模板:

<template name="sigl" value="private static final Singleton&lt;$class$&gt; INSTANCE = new Singleton&lt;$class$&gt;() {&#10;    @Override&#10;    protected $class$ create() {&#10;        return new $class$();&#10;    }&#10;};&#10;&#10;private $class$() {&#10;}&#10;&#10;public static $class$ getInstance() {&#10;    return INSTANCE.get();&#10;}" description="singleton" toReformat="true" toShortenFQNames="true">
  <variable name="class" expression="className()" defaultValue="" alwaysStopAt="false" />
  <context>
    <option name="JAVA_CODE" value="true" />
  </context>
</template>
複製程式碼

複製以上程式碼,在 Template Group 上右鍵貼上即可:

隱藏在 SDK 中的單例類别範本

單例模板擴充套件

SDK 原始碼提供的單例不帶引數,所以我們可以自行擴充套件一個帶引數的單例模板:

/*
 * Copyright (C) 2018 Yuloran(https://github.com/Yuloran)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.yuloran.lib_core.template;

/**
 * [帶一個引數的單例模板類]
 * <p>
 * Author: Yuloran
 * Date Added: 2018/12/16 12:13
 *
 * @since 1.0.0
 */
public abstract class Singleton1<T, P>
{
    private T mInstance;

    protected abstract T create(P arg);

    public final T get(P arg)
    {
        synchronized (this)
        {
            if (mInstance == null)
            {
                mInstance = create(arg);
            }
            return mInstance;
        }
    }
}
複製程式碼

用法與 Singleton 類似,不再介紹。

單例寫法之 CAS

我們可以藉助 AtomicReference 的 compareAndSet() 來實現單例:

package com.yuloran.lib_core.template;

import java.util.concurrent.atomic.AtomicReference;

/**
 * [CAS 實現的單例類]
 * <p>
 * Author: Yuloran
 * Date Added: 2019/1/3 12:13
 *
 * @since 1.0.0
 */
public final class SingletonCAS
{
    private static final AtomicReference<SingletonCAS> INSTANCE = new AtomicReference<>();

    private SingletonCAS()
    {
    }

    public static SingletonCAS getInstance()
    {
        for (; ; )
        {
            SingletonCAS instance = INSTANCE.get();

            if (instance != null)
            {
                return instance;
            }

            // 多執行緒併發訪問時,此處可能建立多個例項
            instance = new SingletonCAS();

            if (INSTANCE.compareAndSet(null, instance))
            {
                return instance;
            }
        }
    }
}
複製程式碼

這種寫法可以保證單例,但是有個致命的問題:for 迴圈中可能建立多個例項!

結語

所以還是使用單例模板類比較好,即安全又省事。如果你是 Kotlin 開發者,你也可以使用 object 或 companion 實現單例。

相關文章