Android中的單例模式-深入理解LayoutInflater

單例模式介紹

許多時(shí)候整個(gè)系統(tǒng)只需要擁有一個(gè)全局對(duì)象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為。

使用場(chǎng)景:

  1. 如在一個(gè)應(yīng)用中,應(yīng)該只有一個(gè)ImageLoader實(shí)例,這個(gè)ImageLoader中又含有線程池、緩存系統(tǒng)、網(wǎng)絡(luò)請(qǐng)求等,很消耗資源,因此,沒(méi)有理由讓它構(gòu)造多個(gè)實(shí)例。這就是單例模式的使用場(chǎng)景。
  2. 某種類型的對(duì)象應(yīng)該有且只有一個(gè)。如果制造出多個(gè)這樣的實(shí)例,可能導(dǎo)致:程序行為異常、資源使用過(guò)量、結(jié)果不一致等問(wèn)題。

定義: 確保某一個(gè)類只有一個(gè)實(shí)例,而且自行實(shí)例化并向整個(gè)系統(tǒng)提供這個(gè)實(shí)例。

單例模式的優(yōu)點(diǎn):可以減少系統(tǒng)內(nèi)存開(kāi)支,減少系統(tǒng)性能開(kāi)銷,避免對(duì)資源的多重占用、同時(shí)操作。

單例模式的缺點(diǎn):擴(kuò)展很困難,容易引發(fā)內(nèi)存泄露,測(cè)試?yán)щy,一定程度上違背了單一職責(zé)原則,進(jìn)程被殺時(shí)可能有狀態(tài)不一致問(wèn)題。

簡(jiǎn)單示例

public class Singleton {
    private static final Singleton instance = new Singleton();
    private Singleton(){}

    public static Singleton getInstance() {
        return instance;
    }
}

Singleton類在外部是不能通過(guò)new的形式構(gòu)造對(duì)象的,只能通過(guò)Singleton.getInstance()方法來(lái)獲取。
而這個(gè)instance對(duì)象是靜態(tài)對(duì)象,并且在聲明的時(shí)候就已經(jīng)初始化了,這就保證了Singleton對(duì)象的唯一性。
這個(gè)實(shí)現(xiàn)的核心在于將CEO類的構(gòu)造方法私有化,使得外部程序不能通過(guò)構(gòu)造函數(shù)來(lái)構(gòu)造Singleton對(duì)象,而Singleton類通過(guò)一個(gè)靜態(tài)方法返回一個(gè)靜態(tài)對(duì)象。
這只是單例模式中的一種寫法,叫做餓漢式。它一開(kāi)始就初始化了單例,同時(shí)它還不是線程安全的。

懶漢式單例

public class Singleton {
    private static Singleton instance;
    private Singleton(){}

    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

在getInstance()方法中添加了synchronized關(guān)鍵字,也就是getInstance()是一個(gè)同步的方法,保證了在多線程情況下單例對(duì)象的唯一性。
但是在instance已經(jīng)被初始化之后,每次調(diào)用getInstance方法都會(huì)進(jìn)行同步,這樣就會(huì)消耗不必要的資源。這也是懶漢式單例存在的問(wèn)題。

Double CheckLock實(shí)現(xiàn)單例

Double CheckLock實(shí)現(xiàn)單例模式的優(yōu)點(diǎn)是既能夠在需要時(shí)才初始化單例,又能夠保證線程的安全。

public class Singleton {
    private volatile static Singleton sInstance = null;
    private Singleton() {}

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

這里對(duì)sInstance進(jìn)行了兩次判空。第一次主要是為了避免不必要的同步,第二次是為了在null的情況下創(chuàng)建實(shí)例。
優(yōu)點(diǎn):資源利用率高,第一次執(zhí)行g(shù)etInstance時(shí)單例對(duì)象才會(huì)被實(shí)例化。
缺點(diǎn):第一次加載時(shí)反應(yīng)稍慢,在高并發(fā)環(huán)境下也有可能失敗;同時(shí)需要在JDK1.5以上版本使用。

靜態(tài)內(nèi)部類單例

public class Singleton {
    private Singleton() {}
    public static Singleton getInstance() {
        return SingletonHolder.sInstance;
    }
    // 靜態(tài)內(nèi)部類
    private static class SingletonHolder {
        private static final Singleton sInstance = new Singleton();
    }
}

這種方式同樣利用了classloder的機(jī)制來(lái)保證初始化instance時(shí)只有一個(gè)線程,只有顯示通過(guò)調(diào)用getInstance方法時(shí),才會(huì)顯示裝載SingletonHolder類,從而實(shí)例化instance。

枚舉

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

這種方式是《Effective Java》提倡的方式,它不僅能避免多線程同步問(wèn)題,而且還能防止反序列化重新創(chuàng)建新的對(duì)象,不過(guò),個(gè)人認(rèn)為由于1.5中才加入enum特性,用這種方式寫不免讓人感覺(jué)生疏,在實(shí)際工作中,我也很少看見(jiàn)有人這么寫過(guò)。

Android中的單例模式

在Android系統(tǒng)中,我們經(jīng)常會(huì)通過(guò)Context獲取系統(tǒng)級(jí)別的服務(wù),如WindowManagerService、ActivityManagerService等,我們比較常用的是一個(gè)LayoutInflater類,這些服務(wù)會(huì)在合適的時(shí)候以單例的形式注冊(cè)在系統(tǒng)中,在我們需要的時(shí)候就通過(guò)Context的getSystemService(String name)方法來(lái)獲取。
這里我們就以LayoutInflater為例來(lái)進(jìn)行說(shuō)明。通常我們使用LayoutInflater比較多的地方是ListView/RecyclerView中:

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(mContext).inflate(R.layout.recycler_view, parent, false);
    return new RecyclerView.ViewHolder(view);
}

思考:
每次都通過(guò)這個(gè)方法來(lái)獲取LayoutInflater對(duì)象,不會(huì)造成資源浪費(fèi)嗎? 這個(gè)系統(tǒng)級(jí)的服務(wù)是什么時(shí)候注冊(cè)的呢? infalte方法是怎么構(gòu)建出View對(duì)象呢?
我們這里就直接點(diǎn)擊from進(jìn)入源碼一看究竟:

// #LayoutInflater.java
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)其實(shí)內(nèi)部是調(diào)用的context.getSystemService(String key)方法。
所以我們就只需要查看Context中的具體實(shí)現(xiàn)方法就可以了,大家都知道Context類其實(shí)是一個(gè)抽象類,它的具體實(shí)現(xiàn)都依托于ContextImpl。
注: Context完全解析

// ContextImpl.java
@Override
public String getSystemServiceName(Class<?> serviceClass) {
    return SystemServiceRegistry.getSystemServiceName(serviceClass);
}

// SystemServiceRegistry.java
final class SystemServiceRegistry {
    // Service registry information.
    // This information is never changed once static initialization has completed.
    private static final HashMap<Class<?>, String> SYSTEM_SERVICE_NAMES = new HashMap<Class<?>, String>();
    // Not instantiable.
    private SystemServiceRegistry() { }
    static {
      registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                    new CachedServiceFetcher<LayoutInflater>() {
                @Override
                public LayoutInflater createService(ContextImpl ctx) {
                    return new PhoneLayoutInflater(ctx.getOuterContext());
                }});
      // 省略其他注冊(cè)代碼          
      ...
    }

    /**
     * Statically registers a system service with the context.
     * This method must be called during static initialization only.
     */
    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);
    }

    /**
    * Gets the name of the system-level service that is represented by the specified class.
    */
    public static String getSystemServiceName(Class<?> serviceClass) {
       return SYSTEM_SERVICE_NAMES.get(serviceClass);
    }
}

我們可以看到在SystemServiceRegistry類中,通過(guò)一個(gè)HashMap保存了系統(tǒng)級(jí)別的一些服務(wù), 這些服務(wù)是在該類中通過(guò)static代碼塊來(lái)進(jìn)行注冊(cè)的。
這樣就避免了每次獲取的時(shí)候重復(fù)創(chuàng)建對(duì)象了。 這種單例模式也叫做:容器的單例模式

深入理解LayoutInflater

接下來(lái)我們挖掘一下LayoutInflater的具體作用。通過(guò)查看LayoutInflater的代碼可以知道,它是一個(gè)抽象類,同時(shí)在上面的registerService中,我們也發(fā)現(xiàn),實(shí)際上是new PhoneLayoutInflater,同時(shí)PhoneLayoutInflater是繼承了LayoutInflater的。所以我們就先看看PhoneLayoutInflater

public class PhoneLayoutInflater extends LayoutInflater {
    private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };

    /** Override onCreateView to instantiate names that correspond to the
        widgets known to the Widget factory. If we don't find a match,
        call through to our super class.
    */
    @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        // 在View名字的前面添加前綴來(lái)構(gòu)造View的完整路徑
        // 例如類名是TextView,那么TextView完全的路徑就是android.widget.TextView
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                // In this case we want to let the base class take a crack
                // at it.
            }
        }

        return super.onCreateView(name, attrs);
    }
}

其實(shí)PhoneLayoutInflater就是覆蓋了LayoutInflater的onCreateView方法,該方法就是在傳遞進(jìn)來(lái)的View名字前面加上"android.widget"。最后根據(jù)類的完整路徑來(lái)構(gòu)造對(duì)應(yīng)的View對(duì)象。
具體是一個(gè)什么樣的構(gòu)造流程呢? 以Activyt的setContentView為例:

// Activity.java
public void setContentView(View view) {
    getWindow().setContentView(view);
    initWindowDecorActionBar();
}

Window是一個(gè)抽象類,它的具體實(shí)現(xiàn)類是PhoneWindow

// PhoneWindow.java
public void setContentView(int layoutResID) {
    // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
    // decor, when theme attributes and the like are crystalized. Do not check the feature
    // before this happens.
    // 1.當(dāng)mContentParent為空的時(shí)候,先構(gòu)建DecorView,并將DecorView包裹在mContentParent中
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        // 2.解析layoutResID
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
}
// LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    // 如果root不為空,則會(huì)從resource布局解析到view,并添加到root中
    return inflate(resource, root, root != null);
}

// 參數(shù)一: 要解析的布局layoutResID
// 參數(shù)二: 要解析布局的父視圖
// 參數(shù)三: 是否將要解析的視圖添加到父視圖中
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    // 獲取xml資源解析器
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}    

// 參數(shù)一: XML資源解析器
// 參數(shù)二: 要解析布局的父視圖
// 參數(shù)三: 是否將要解析的視圖添加到父視圖中
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        final Context inflaterContext = mContext;
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        Context lastContext = (Context) mConstructorArgs[0];
        // Context對(duì)象
        mConstructorArgs[0] = inflaterContext;
        // 父視圖
        View result = root;

        try {
            // Look for the root node.
            int type;
            // 1. 找到root元素
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                    type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }

            final String name = parser.getName();
            // 2. 如果是merge標(biāo)簽
            if (TAG_MERGE.equals(name)) {
                rInflate(parser, root, inflaterContext, attrs, false);
            } else {
                // Temp is the root view that was found in the xml
                // 如果不是merge標(biāo)簽,就直接解析
                // 通過(guò)xml的tag來(lái)解析layout跟視圖
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                if (root != null) {
                    // Create layout params that match root, if supplied
                    // 生成布局參數(shù)
                    params = root.generateLayoutParams(attrs);
                    // 如果不添加到父視圖,那么給temp設(shè)置布局參數(shù)
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        temp.setLayoutParams(params);
                    }
                }
                // Inflate all children under temp against its context.
                // 解析temp下的所有子View
                rInflateChildren(parser, temp, attrs, true);


                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                // 如果Root不為空,且要添加到父視圖中
                if (root != null && attachToRoot) {
                    root.addView(temp, params);
                }

                // Decide whether to return the root that was passed in or the
                // top view found in xml.
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }
        }
        return result;
    }
}

inflate過(guò)程:

  1. 解析xml中的根標(biāo)簽(第一個(gè)元素)
  2. 如果根標(biāo)簽是merge,那么調(diào)用rInflate進(jìn)行解析,將merge標(biāo)簽下的所有子View直接添加到根標(biāo)簽下
  3. 如果根標(biāo)簽是普通元素,那么調(diào)用createViewFromTag對(duì)該元素進(jìn)行解析, 注意布局參數(shù)
  4. 調(diào)用rInflate解析temp元素下的所有子View,并將這些子View都添加到temp下
  5. 返回解析到的根視圖

先理解解析單個(gè)元素的createViewFromTag:

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }


    try {
        View view;
        // 1.用戶可以通過(guò)設(shè)置LayoutInflater的factory來(lái)自行解析View,默認(rèn)這些factory都為空
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } else {
            view = null;
        }

        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        // 2. 沒(méi)有factory的情況下,通過(guò)onCreateView或者createView方法來(lái)創(chuàng)建View
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                // name 就是xml解析出來(lái)的標(biāo)簽
                if (-1 == name.indexOf('.')) {
                  // 3.內(nèi)置View控件的解析, 比如<TextView ... />
                    view = onCreateView(parent, name, attrs);
                } else {
                  // 4.自定義View的解析  比如 <com.whyalwaysmea.myview ... />
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }
        return view;
    }
}

這里重點(diǎn)關(guān)注步驟3和步驟4, 一個(gè)是解析標(biāo)簽中沒(méi)有帶'.'的控件,一個(gè)是解析帶了'.'的控件。
我們回到之前的PhoneLayoutInflater中,看到onCreateView其實(shí)就是在View前面加上了前綴,然后調(diào)用了creatView方法。所以onCreateView方法只是為了開(kāi)發(fā)者可以方便的寫控件的標(biāo)簽名而已

// 根據(jù)完整路徑的類名通過(guò)反射機(jī)制構(gòu)造View對(duì)象
public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
    // 1. 獲取緩存的構(gòu)造函數(shù)         
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    Class<? extends View> clazz = null;

    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
        // 如果沒(méi)有緩存的構(gòu)造函數(shù)
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            // 2.如果prefix不為空,那么構(gòu)造完整的View路徑,并加載該類
            // prefix在onCreateView中會(huì)有,
            clazz = mContext.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class);

            // 3.獲取構(gòu)造函數(shù)
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            // 4.緩存構(gòu)造函數(shù)
            sConstructorMap.put(name, constructor);
        } else {
            // If we have a filter, apply it to cached constructor

        }

        Object[] args = mConstructorArgs;
        args[1] = attrs;
        // 5. 通過(guò)反射構(gòu)造View
        final View view = constructor.newInstance(args);
        if (view instanceof ViewStub) {
            // Use the same context when inflating ViewStub later.
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        return view;

    }
}

createView步驟:

  1. 如果有前綴,那么就構(gòu)造出完整的路徑
  2. 通過(guò)反射獲取該類的構(gòu)造函數(shù)并且緩存起來(lái)
  3. 通過(guò)構(gòu)造函數(shù)來(lái)創(chuàng)建View并返回
    我們的窗口中是一棵視圖樹(shù),LayoutInflater需要解析完這棵樹(shù),這個(gè)功能就交給了rInflate方法。
void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
    // 1.獲取樹(shù)的深度,
    final int depth = parser.getDepth();
    int type;
    // 2.挨個(gè)元素解析
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        final String name = parser.getName();

        if (TAG_REQUEST_FOCUS.equals(name)) {
            parseRequestFocus(parser, parent);
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            // include標(biāo)簽,不能為根標(biāo)簽
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            // merge標(biāo)簽必須為根標(biāo)簽
            throw new InflateException("<merge /> must be the root element");
        } else {
            // 3.根據(jù)元素名進(jìn)行解析
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            // 遞歸調(diào)用,深度優(yōu)先遍歷
            rInflateChildren(parser, view, attrs, true);
            // 將解析到的View添加到它的parent中
            viewGroup.addView(view, params);
        }
    }
}

這樣整個(gè)LayoutInflater.infalte流程就走完了。

相關(guān)鏈接

Android LayoutInflater原理分析,帶你一步步深入了解View(一)
Android LayoutInflater深度解析 給你帶來(lái)全新的認(rèn)識(shí)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容