单例模式介绍
许多时候整个系统只需要拥有一个全局对象,这样有利于我们协调系统整体的行为。
使用场景:
- 如在一个应用中,应该只有一个ImageLoader实例,这个ImageLoader中又含有线程池、缓存系统、网络请求等,很消耗资源,因此,没有理由让它构造多个实例。这就是单例模式的使用场景。
- 某种类型的对象应该有且只有一个。如果制造出多个这样的实例,可能导致:程序行为异常、资源使用过量、结果不一致等问题。
定义: 确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
单例模式的优点:可以减少系统内存开支,减少系统性能开销,避免对资源的多重占用、同时操作。
单例模式的缺点:扩展很困难,容易引发内存泄露,测试困难,一定程度上违背了单一职责原则,进程被杀时可能有状态不一致问题。
简单示例
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton(){}
public static Singleton getInstance() {
return instance;
}
}
Singleton类在外部是不能通过new的形式构造对象的,只能通过Singleton.getInstance()方法来获取。
而这个instance对象是静态对象,并且在声明的时候就已经初始化了,这就保证了Singleton对象的唯一性。
这个实现的核心在于将CEO类的构造方法私有化,使得外部程序不能通过构造函数来构造Singleton对象,而Singleton类通过一个静态方法返回一个静态对象。
这只是单例模式中的一种写法,叫做饿汉式。它一开始就初始化了单例,同时它还不是线程安全的。
懒汉式单例
public class Singleton {
private static Singleton instance;
private Singleton(){}
public static synchronized Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
在getInstance()方法中添加了synchronized关键字,也就是getInstance()是一个同步的方法,保证了在多线程情况下单例对象的唯一性。
但是在instance已经被初始化之后,每次调用getInstance方法都会进行同步,这样就会消耗不必要的资源。这也是懒汉式单例存在的问题。
Double CheckLock实现单例
Double CheckLock实现单例模式的优点是既能够在需要时才初始化单例,又能够保证线程的安全。
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;
}
}
这里对sInstance进行了两次判空。第一次主要是为了避免不必要的同步,第二次是为了在null的情况下创建实例。
优点:资源利用率高,第一次执行getInstance时单例对象才会被实例化。
缺点:第一次加载时反应稍慢,在高并发环境下也有可能失败;同时需要在JDK1.5以上版本使用。
静态内部类单例
public class Singleton {
private Singleton() {}
public static Singleton getInstance() {
return SingletonHolder.sInstance;
}
// 静态内部类
private static class SingletonHolder {
private static final Singleton sInstance = new Singleton();
}
}
这种方式同样利用了classloder的机制来保证初始化instance时只有一个线程,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。
枚举
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
这种方式是《Effective Java》提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,不过,个人认为由于1.5中才加入enum特性,用这种方式写不免让人感觉生疏,在实际工作中,我也很少看见有人这么写过。
Android中的单例模式
在Android系统中,我们经常会通过Context获取系统级别的服务,如WindowManagerService、ActivityManagerService等,我们比较常用的是一个LayoutInflater类,这些服务会在合适的时候以单例的形式注册在系统中,在我们需要的时候就通过Context的getSystemService(String name)方法来获取。
这里我们就以LayoutInflater为例来进行说明。通常我们使用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);
}
思考:
每次都通过这个方法来获取LayoutInflater对象,不会造成资源浪费吗? 这个系统级的服务是什么时候注册的呢? infalte方法是怎么构建出View对象呢?
我们这里就直接点击from进入源码一看究竟:
// #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)其实内部是调用的context.getSystemService(String key)方法。
所以我们就只需要查看Context中的具体实现方法就可以了,大家都知道Context类其实是一个抽象类,它的具体实现都依托于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());
}});
// 省略其他注册代码
...
}
/**
* 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类中,通过一个HashMap保存了系统级别的一些服务, 这些服务是在该类中通过static代码块来进行注册的。
这样就避免了每次获取的时候重复创建对象了。 这种单例模式也叫做:容器的单例模式
深入理解LayoutInflater
接下来我们挖掘一下LayoutInflater的具体作用。通过查看LayoutInflater的代码可以知道,它是一个抽象类,同时在上面的registerService中,我们也发现,实际上是new PhoneLayoutInflater,同时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名字的前面添加前缀来构造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);
}
}
其实PhoneLayoutInflater就是覆盖了LayoutInflater的onCreateView
方法,该方法就是在传递进来的View名字前面加上"android.widget"。最后根据类的完整路径来构造对应的View对象。
具体是一个什么样的构造流程呢? 以Activyt的setContentView为例:
// Activity.java
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
Window是一个抽象类,它的具体实现类是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.当mContentParent为空的时候,先构建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不为空,则会从resource布局解析到view,并添加到root中
return inflate(resource, root, root != null);
}
// 参数一: 要解析的布局layoutResID
// 参数二: 要解析布局的父视图
// 参数三: 是否将要解析的视图添加到父视图中
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();
}
}
// 参数一: XML资源解析器
// 参数二: 要解析布局的父视图
// 参数三: 是否将要解析的视图添加到父视图中
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对象
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标签
if (TAG_MERGE.equals(name)) {
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
// 如果不是merge标签,就直接解析
// 通过xml的tag来解析layout跟视图
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// Create layout params that match root, if supplied
// 生成布局参数
params = root.generateLayoutParams(attrs);
// 如果不添加到父视图,那么给temp设置布局参数
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过程:
- 解析xml中的根标签(第一个元素)
- 如果根标签是merge,那么调用rInflate进行解析,将merge标签下的所有子View直接添加到根标签下
- 如果根标签是普通元素,那么调用createViewFromTag对该元素进行解析, 注意布局参数
- 调用rInflate解析temp元素下的所有子View,并将这些子View都添加到temp下
- 返回解析到的根视图
先理解解析单个元素的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.用户可以通过设置LayoutInflater的factory来自行解析View,默认这些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. 没有factory的情况下,通过onCreateView或者createView方法来创建View
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
// name 就是xml解析出来的标签
if (-1 == name.indexOf('.')) {
// 3.内置View控件的解析, 比如<TextView ... />
view = onCreateView(parent, name, attrs);
} else {
// 4.自定义View的解析 比如 <com.whyalwaysmea.myview ... />
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
}
这里重点关注步骤3和步骤4, 一个是解析标签中没有带'.'的控件,一个是解析带了'.'的控件。
我们回到之前的PhoneLayoutInflater中,看到onCreateView其实就是在View前面加上了前缀,然后调用了creatView方法。所以onCreateView方法只是为了开发者可以方便的写控件的标签名而已
// 根据完整路径的类名通过反射机制构造View对象
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// 1. 获取缓存的构造函数
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
// 如果没有缓存的构造函数
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
// 2.如果prefix不为空,那么构造完整的View路径,并加载该类
// prefix在onCreateView中会有,
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
// 3.获取构造函数
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
// 4.缓存构造函数
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
}
Object[] args = mConstructorArgs;
args[1] = attrs;
// 5. 通过反射构造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步骤:
- 如果有前缀,那么就构造出完整的路径
- 通过反射获取该类的构造函数并且缓存起来
- 通过构造函数来创建View并返回
我们的窗口中是一棵视图树,LayoutInflater需要解析完这棵树,这个功能就交给了rInflate方法。
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
// 1.获取树的深度,
final int depth = parser.getDepth();
int type;
// 2.挨个元素解析
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标签,不能为根标签
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标签必须为根标签
throw new InflateException("<merge /> must be the root element");
} else {
// 3.根据元素名进行解析
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 递归调用,深度优先遍历
rInflateChildren(parser, view, attrs, true);
// 将解析到的View添加到它的parent中
viewGroup.addView(view, params);
}
}
}
这样整个LayoutInflater.infalte流程就走完了。
相关链接
Android LayoutInflater原理分析,带你一步步深入了解View(一)
Android LayoutInflater深度解析 给你带来全新的认识