單例模式介紹
許多時(shí)候整個(gè)系統(tǒng)只需要擁有一個(gè)全局對(duì)象,這樣有利于我們協(xié)調(diào)系統(tǒng)整體的行為。
使用場(chǎng)景:
- 如在一個(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)景。
- 某種類型的對(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ò)程:
- 解析xml中的根標(biāo)簽(第一個(gè)元素)
- 如果根標(biāo)簽是merge,那么調(diào)用rInflate進(jìn)行解析,將merge標(biāo)簽下的所有子View直接添加到根標(biāo)簽下
- 如果根標(biāo)簽是普通元素,那么調(diào)用createViewFromTag對(duì)該元素進(jìn)行解析, 注意布局參數(shù)
- 調(diào)用rInflate解析temp元素下的所有子View,并將這些子View都添加到temp下
- 返回解析到的根視圖
先理解解析單個(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步驟:
- 如果有前綴,那么就構(gòu)造出完整的路徑
- 通過(guò)反射獲取該類的構(gòu)造函數(shù)并且緩存起來(lái)
- 通過(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í)