目录:
前言
单例模式简介和应用
工厂模式简介和应用
建造者模式简介和应用
原型模式简介和应用
1. 前言
设计模式在开发中用的很多,但是设计模式不是教科书,只是一种组织代码的思想。如果你完全跟着标准的设计模式去做,我觉得你的代码会冗余且复杂。记住,千万不要为了设计而设计,避免 over design!
这边将简述各个设计模式的概念,然后用一个开源项目中的例子来配合理解。
2. 单例模式简介和应用
2.1 简介
单例模式是设计模式中最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,"阻止"所有想要生成对象的访问。
2.2 几种写法
(1) 饿汉式
饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以本身就是线程安全的。
private static class Singleton1 {
private Singleton1() {
}
private static Singleton1 single = new Singleton1();
public static Singleton1 getInstance() {
return single;
}
}
(2) 懒汉式
懒汉式用延迟加载方式实现了懒汉式单例,但在多线程环境下会产生多个对象。
private static class Singleton2 {
private Singleton2() {
}
private static Singleton2 single = null;
public static Singleton2 getInstance() {
if (single == null) {
single = new Singleton2();
}
return single;
}
}
(3) 线程安全的懒汉式
使用双重检验锁模式(double checked locking pattern),是一种使用同步块加锁的方法。程序员称其为双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。使用这种方式可以解决锁住整个代码块导致的效率低下问题。
private static class Singleton3 {
private volatile static Singleton3 singleton;
private Singleton3 () {
}
public static Singleton3 getSingleton() {
if (singleton == null) {
synchronized (Singleton3.class) {
if (singleton == null) {
singleton = new Singleton3();
}
}
}
return singleton;
}
}
(4) 静态内部类
静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。所以需要加入 readResolve 方法来避免这种情况。
考虑反射:
由于在调用 Holder.instance 的时候,才会对单例进行初始化,而且通过反射,是不能从外部类获取内部类的属性的。所以这种形式,很好的避免了反射入侵。
考虑多线程:
由于静态内部类的特性,只有在其被第一次引用的时候才会被加载,所以可以保证其线程安全性。
优点:
兼顾了懒汉模式的内存优化(使用时才初始化)以及饿汉模式的安全性(不会被反射入侵)。
缺点:
需要两个类去做到这一点,虽然不会创建静态内部类的对象,但是其 Class 对象还是会被创建,而且是属于永久带的对象。
创建的单例,一旦在后期被销毁,不能重新创建。
private static class Singleton4 {
private Singleton4() {
}
public static Singleton4 instance() {
return Holder.instance;
}
private static class Holder {
static Singleton4 instance = new Singleton4();
}
private Object readResolve() throws ObjectStreamException {
return instance();
}
}
2.3 内存泄漏
主要发生在带界面的应用程序中,比如 Android,Android 的单例模式在我们项目开发中经常会用到,不过使用的不恰当的话也会造成内存泄漏。因为单例的静态特性使得单例的生命周期和应用的生命周期一样长, 这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。所以在 Android 中,单例持有上下文,一般用长生命周期的 Application 引用,如果不得不使用 Activity,需要用弱引用将其包装起来,避免 Activity 占有的内存得不到释放。
2.4 universalimageloader 中的单例模式
universalimageloader 是 Android 中很流行的一个图片加载框架,在使用时不难发现,它维护着一个 ImageLoader 的单例对象。
下面是它的部分源码,使用的是上面线程安全的懒汉式:
private static volatile ImageLoader instance;
public static ImageLoader getInstance() {
if (instance == null) {
Class var0 = ImageLoader.class;
synchronized(ImageLoader.class) {
if (instance == null) {
instance = new ImageLoader();
}
}
}
return instance;
}
protected ImageLoader() {
}
3. 工厂模式简介和应用
3.1 简单工厂模式
简单工厂模式不能说是一个设计模式,说它是一种编程习惯可能更恰当些。因为它至少不是 Gof 23种设计模式之一。但它在实际的编程中经常被用到,而且思想也非常简单,可以说是工厂方法模式的一个引导。简单工厂模式又称为静态工厂方法模式,它属于类创建型模式。
核心思想:当你需要什么,只需要传入一个正确的参数,你就可以得到你需要的对象,而不用知道它的创建细节。
private interface IFruit {
void eat();
}
private static final class Apple implements IFruit {
@Override
public void eat() {
System.out.println(" eat Apple");
}
}
private static final class Banana implements IFruit {
@Override
public void eat() {
System.out.println(" eat Banana");
}
}
private static final class FruitFactory {
IFruit getFruit(String name) {
switch (name) {
case "Apple":
return new Apple();
case "Banana":
return new Banana();
default:
throw new UnsupportedOperationException("unsupported name");
}
}
}
public static void main(String[] args) {
FruitFactory fruitFactory = new FruitFactory();
fruitFactory.getFruit("Apple").eat();
fruitFactory.getFruit("Banana").eat();
fruitFactory.getFruit("Orange").eat();
}
执行输出:
eat Apple
eat Banana
Exception in thread "main" java.lang.UnsupportedOperationException: unsupported name
优点:
简单工厂模式能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。明确区分了各自的职责和权力,有利于整个软件体系结构的优化。
缺点:
很明显工厂类集中了所有实例的创建逻辑,容易违反"开闭原则"。
简单工厂模式适用情况包括:工厂类负责创建的对象比较少,客户端只知道传入工厂类的参数,对于如何创建对象不关心。
3.2 工厂方法模式
工厂方法模式又称多态性工厂模式。在工厂方法模式中,核心的工厂类不再负责所有的产品的创建,而是将具体创建的工作交给子类去做。该核心类成为一个抽象工厂角色,仅负责给出具体工厂子类必须实现的接口,而不接触哪一个产品类应当被实例化这种细节。
private interface IFruit {
void eat();
}
private static final class Apple implements IFruit {
@Override
public void eat() {
System.out.println(" eat Apple");
}
}
private static final class Banana implements IFruit {
@Override
public void eat() {
System.out.println(" eat Banana");
}
}
private interface IFruitFactory {
IFruit getFruit();
}
private static final class AppleFactory implements IFruitFactory {
@Override
public IFruit getFruit() {
return new Apple();
}
}
private static final class BananaFactory implements IFruitFactory {
@Override
public IFruit getFruit() {
return new Banana();
}
}
public static void main(String[] args) {
IFruitFactory fruitFactory;
fruitFactory = new AppleFactory();
fruitFactory.getFruit().eat();
fruitFactory = new BananaFactory();
fruitFactory.getFruit().eat();
}
执行输出:
eat Apple
eat Banana
优点:
克服了简单工厂违背"开闭原则"的缺点,又保留了封装对象创建过程的优点,降低客户端和工厂的耦合性,所以说工厂方法模式是简单工厂模式的进一步抽象和推广。
缺点:
每增加一个产品,相应的也要增加一个子工厂,加大了额外的开发量。
3.3 抽象工厂模式
抽象工厂模式是围绕一个超级工厂创建其他工厂,该超级工厂又称为其他工厂的工厂。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
在抽象工厂模式中,接口是负责创建一个相关对象的工厂,不需要显式指定它们的类。每个生成的工厂都能按照工厂模式提供对象。
public interface Shape {
void draw();
}
private final class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Inside Rectangle::draw() method.");
}
}
private final class Square implements Shape {
@Override
public void draw() {
System.out.println("Inside Square::draw() method.");
}
}
private final class Circle implements Shape {
@Override
public void draw() {
System.out.println("Inside Circle::draw() method.");
}
}
public interface Color {
void fill();
}
private final class Red implements Color {
@Override
public void fill() {
System.out.println("Inside Red::fill() method.");
}
}
private final class Green implements Color {
@Override
public void fill() {
System.out.println("Inside Green::fill() method.");
}
}
private final class Blue implements Color {
@Override
public void fill() {
System.out.println("Inside Blue::fill() method.");
}
}
public abstract class AbstractFactory {
public abstract Color getColor(String color);
public abstract Shape getShape(String shape);
}
private final class ShapeFactory extends AbstractFactory {
@Override
public Shape getShape(String shapeType) {
if (shapeType == null) {
return null;
}
if (shapeType.equalsIgnoreCase("CIRCLE")) {
return new Circle();
} else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
return new Rectangle();
} else if (shapeType.equalsIgnoreCase("SQUARE")) {
return new Square();
}
return null;
}
@Override
public Color getColor(String color) {
return null;
}
}
private final class ColorFactory extends AbstractFactory {
@Override
public Shape getShape(String shapeType) {
return null;
}
@Override
public Color getColor(String color) {
if (color == null) {
return null;
}
if (color.equalsIgnoreCase("RED")) {
return new Red();
} else if (color.equalsIgnoreCase("GREEN")) {
return new Green();
} else if (color.equalsIgnoreCase("BLUE")) {
return new Blue();
}
return null;
}
}
private static final class FactoryProducer {
public static AbstractFactory getFactory(String choice){
if(choice.equalsIgnoreCase("SHAPE")){
return new ShapeFactory();
} else if(choice.equalsIgnoreCase("COLOR")){
return new ColorFactory();
}
return null;
}
}
上面代码不能直接执行,这边就是使用这个结构来说明一下抽象工厂方法的实现。可以看到,不同的行为提供了不同的工厂,最终由一个超级工厂来管理这些工厂。
优点:
易于交换产品系列,由于具体工厂类,在一个应用中只需要在初始化的时候出现一次,这就使得改变一个应用的具体工厂变得非常容易,它只需要改变具体工厂即可使用不同的产品配置。
它让具体的创建实例过程与客户端分离,客户端是通过它们的抽象接口操纵实例,产品的具体类名也被具体工厂的实现分离,不会出现在客户代码中。
缺点:
增加一个新实现需要改动多个类。
每种工厂模式都有其优缺点,所以在实际的开发中需要自己权衡来做选择。一般可以使用反射 + 配置文件 + 简单工厂的方式来改进抽象工厂模式。
3.4 web3j 中的简单工厂模式
Web3jFactory 提供了两个方法构造 Web3j 对象,外部无需知道其构造细节。
public class Web3jFactory {
public Web3jFactory() {
}
public static Web3j build(Web3jService web3jService) {
return new JsonRpc2_0Web3j(web3jService);
}
public static Web3j build(Web3jService web3jService, long pollingInterval, ScheduledExecutorService scheduledExecutorService) {
return new JsonRpc2_0Web3j(web3jService, pollingInterval, scheduledExecutorService);
}
}
4. 建造者模式简介和应用
4.1 简介
建造者模式是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
优点:
封装性,使得客户端不必知道产品内部的组成细节,我们不用关心每一个具体的模型内部是如何实现的。
建造者独立,容易扩展。
便于控制细节风险,由于具体的建造者是独立的,因此可以对建造过程逐步细化,而不对其他的模块产生任何影响。
建造者模式的应用场景:
相同的方法,不同的执行顺序,会产生不同的结果时。
多个部件或零件,都可以装配到一个对象中,但产生的运行结果又不相同时,如 Android 中的 AlertDialog 的构造。
产品类非常复杂,或产品类的的调用顺序不同产生不同的效果。
我以前做的一个项目中用了建造者模式,那时的图片下载和加载显示还是手写年代,代码如下:
public static class Builder {
private Context context;
private View view;
private String url;
private int imageWidth;
private int imageHeight;
private int imageType;
private boolean isBigImage;
private ImageCallback callBack;
private Handler handler;
public Builder(Context context) {
this.context = context;
}
public Builder setView(View v) {
this.view = v;
return this;
}
public Builder setUrl(String url) {
this.url = url;
return this;
}
public Builder setImageWidth(int w) {
this.imageWidth = w;
return this;
}
public Builder setImageHeiht(int h) {
this.imageHeight = h;
return this;
}
public Builder setImageType(int type) {
this.imageType = type;
return this;
}
public Builder isBigImage(boolean flag) {
this.isBigImage = flag;
return this;
}
public Builder setCallBack(ImageCallback callBack) {
this.callBack = callBack;
return this;
}
public Builder setHandler(Handler h) {
handler = h;
return this;
}
public DownloadImageTask build() {
return new DownloadImageTask(this);
}
}
public class DownloadImageTask implements Runnable {
private static final String TAG = "DownloadImageTask";
private Context mContext;
private View mView;
private String mUrl;
private int mImageWidth;
private int mImageHeight;
private int mImageType;
private boolean mIsBigImage;
private ImageCallback mCallBack;
private FileManager mFileManager;
private Map<String, String> mRequestUrlContainer;
private Handler mHandler;
public DownloadImageTask(Builder b) {
mContext = b.context;
mView = b.view;
mUrl = b.url;
mImageWidth = b.imageWidth;
mImageHeight = b.imageHeight;
mImageType = b.imageType;
mIsBigImage = b.isBigImage;
mCallBack = b.callBack;
mHandler = b.handler;
mFileManager = FileManager.getInstance(mContext);
mRequestUrlContainer = ImagePool.getInstence(mContext).getRequestUrlContainer();
}
......
}
4.2 universalimageloader 中的建造者模式
在使用 universalimageloader 前,先要对其进行配置并初始化:
public void init(File cacheDir, ImageDecoder imageDecoder) {
if (!mImageLoader.isInited()) {
// 如果没有初始化过,则去初始化
// ImageLoader加载配置
if (cacheDir == null) {
cacheDir = StorageUtils.getCacheDirectory(mContext);
}
DisplayMetrics metrics = new DisplayMetrics();
WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getMetrics(metrics);
ImageLoaderConfiguration.Builder builder = new ImageLoaderConfiguration.Builder(mContext)
// 核心线程数
.threadPoolSize(getCoreThreadSize())
.memoryCacheExtraOptions(metrics.widthPixels, metrics.heightPixels)
.threadPriority(Thread.NORM_PRIORITY - 2)
// 加载图片时后进先出
.tasksProcessingOrder(QueueProcessingType.LIFO)
.diskCacheExtraOptions(metrics.widthPixels, metrics.heightPixels, null)
.diskCache(new UnlimitedDiskCache(cacheDir, cacheDir, new Md5FileNameGenerator()))
.memoryCache(new FIFOLimitedMemoryCache(100));
if (imageDecoder != null) {
// 图片解码器
builder.imageDecoder(imageDecoder);
}
ImageLoaderConfiguration imageLoaderConfiguration = builder.build();
mImageLoader.init(imageLoaderConfiguration);
}
}
可以看到,ImageLoader 的初始化需要一个 ImageLoaderConfiguration 对象,这个对象可以设置很多属性,是一个拥有复杂配置功能的对象,这个时候用建造者模式再合适不过了。接入方可以有选择的配置自己需要的属性,当然 ImageLoader 在内部已经做了默认的属性配置。
public final class ImageLoaderConfiguration {
final Resources resources;
final int maxImageWidthForMemoryCache;
final int maxImageHeightForMemoryCache;
final int maxImageWidthForDiskCache;
final int maxImageHeightForDiskCache;
final BitmapProcessor processorForDiskCache;
final Executor taskExecutor;
final Executor taskExecutorForCachedImages;
final boolean customExecutor;
final boolean customExecutorForCachedImages;
final int threadPoolSize;
final int threadPriority;
final QueueProcessingType tasksProcessingType;
final MemoryCache memoryCache;
final DiskCache diskCache;
final ImageDownloader downloader;
final ImageDecoder decoder;
final DisplayImageOptions defaultDisplayImageOptions;
final ImageDownloader networkDeniedDownloader;
final ImageDownloader slowNetworkDownloader;
private ImageLoaderConfiguration(ImageLoaderConfiguration.Builder builder) {
this.resources = builder.context.getResources();
this.maxImageWidthForMemoryCache = builder.maxImageWidthForMemoryCache;
this.maxImageHeightForMemoryCache = builder.maxImageHeightForMemoryCache;
this.maxImageWidthForDiskCache = builder.maxImageWidthForDiskCache;
this.maxImageHeightForDiskCache = builder.maxImageHeightForDiskCache;
this.processorForDiskCache = builder.processorForDiskCache;
this.taskExecutor = builder.taskExecutor;
this.taskExecutorForCachedImages = builder.taskExecutorForCachedImages;
this.threadPoolSize = builder.threadPoolSize;
this.threadPriority = builder.threadPriority;
this.tasksProcessingType = builder.tasksProcessingType;
this.diskCache = builder.diskCache;
this.memoryCache = builder.memoryCache;
this.defaultDisplayImageOptions = builder.defaultDisplayImageOptions;
this.downloader = builder.downloader;
this.decoder = builder.decoder;
this.customExecutor = builder.customExecutor;
this.customExecutorForCachedImages = builder.customExecutorForCachedImages;
this.networkDeniedDownloader = new ImageLoaderConfiguration.NetworkDeniedImageDownloader(this.downloader);
this.slowNetworkDownloader = new ImageLoaderConfiguration.SlowNetworkImageDownloader(this.downloader);
L.writeDebugLogs(builder.writeLogs);
}
public static ImageLoaderConfiguration createDefault(Context context) {
return (new ImageLoaderConfiguration.Builder(context)).build();
}
public static class Builder {
private static final String WARNING_OVERLAP_DISK_CACHE_PARAMS = "diskCache(), diskCacheSize() and diskCacheFileCount calls overlap each other";
private static final String WARNING_OVERLAP_DISK_CACHE_NAME_GENERATOR = "diskCache() and diskCacheFileNameGenerator() calls overlap each other";
private static final String WARNING_OVERLAP_MEMORY_CACHE = "memoryCache() and memoryCacheSize() calls overlap each other";
private static final String WARNING_OVERLAP_EXECUTOR = "threadPoolSize(), threadPriority() and tasksProcessingOrder() calls can overlap taskExecutor() and taskExecutorForCachedImages() calls.";
public static final int DEFAULT_THREAD_POOL_SIZE = 3;
public static final int DEFAULT_THREAD_PRIORITY = 3;
public static final QueueProcessingType DEFAULT_TASK_PROCESSING_TYPE;
private Context context;
private int maxImageWidthForMemoryCache = 0;
private int maxImageHeightForMemoryCache = 0;
private int maxImageWidthForDiskCache = 0;
private int maxImageHeightForDiskCache = 0;
private BitmapProcessor processorForDiskCache = null;
private Executor taskExecutor = null;
private Executor taskExecutorForCachedImages = null;
private boolean customExecutor = false;
private boolean customExecutorForCachedImages = false;
private int threadPoolSize = 3;
private int threadPriority = 3;
private boolean denyCacheImageMultipleSizesInMemory = false;
private QueueProcessingType tasksProcessingType;
private int memoryCacheSize;
private long diskCacheSize;
private int diskCacheFileCount;
private MemoryCache memoryCache;
private DiskCache diskCache;
private FileNameGenerator diskCacheFileNameGenerator;
private ImageDownloader downloader;
private ImageDecoder decoder;
private DisplayImageOptions defaultDisplayImageOptions;
private boolean writeLogs;
public Builder(Context context) {
this.tasksProcessingType = DEFAULT_TASK_PROCESSING_TYPE;
this.memoryCacheSize = 0;
this.diskCacheSize = 0L;
this.diskCacheFileCount = 0;
this.memoryCache = null;
this.diskCache = null;
this.diskCacheFileNameGenerator = null;
this.downloader = null;
this.defaultDisplayImageOptions = null;
this.writeLogs = false;
this.context = context.getApplicationContext();
}
public ImageLoaderConfiguration.Builder threadPoolSize(int threadPoolSize) {
if (this.taskExecutor != null || this.taskExecutorForCachedImages != null) {
L.w("threadPoolSize(), threadPriority() and tasksProcessingOrder() calls can overlap taskExecutor() and taskExecutorForCachedImages() calls.", new Object[0]);
}
this.threadPoolSize = threadPoolSize;
return this;
}
.......
public ImageLoaderConfiguration build() {
this.initEmptyFieldsWithDefaultValues();
return new ImageLoaderConfiguration(this, (ImageLoaderConfiguration.SyntheticClass_1)null);
}
static {
DEFAULT_TASK_PROCESSING_TYPE = QueueProcessingType.FIFO;
}
}
}
5. 原型模式简介和应用
5.1 简介
在原型模式中,所发动创建的对象通过请求原型对象来拷贝原型对象自己来实现创建过程,当然所发动创建的对象需要知道原型对象的类型。这里也就是说所发动创建的对象只需要知道原型对象的类型就可以获得更多的原型实例对象,至于这些原型对象时如何创建的根本不需要关心。
讲到原型模式了,就不得不区分两个概念:深拷贝、浅拷贝。这个可以看看我之前的文章:Java篇 - 聊聊cloneable
优点:
如果创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
可以使用深克隆保持对象的状态。
原型模式提供了简化的创建结构。
缺点:
在实现深克隆的时候可能需要比较复杂的代码。
需要为每一个类配备一个克隆方法,而且这个克隆方法需要对类的功能进行通盘考虑,这对全新的类来说不是很难,但对已有的类进行改造时,不一定是件容易的事,必须修改其源代码,违背了"开闭原则"。
5.2 EasySkin 中的原型模式
Android 中的换肤 SDK 很多,比如 EasySkin,EasySkin 中有段代码使用了原型模式:
public abstract class SkinAttr implements Cloneable {
/* Property name eg: background , textColor */
public String attrName;
/* Resource name, eg: start_bg */
public String resEntryName;
/* Property value type. Eg: color or drawable */
public String resTypeName;
protected boolean isDrawable() {
return SkinConstant.RES_TYPE_NAME_DRAWABLE.equals(resTypeName)
|| SkinConstant.RES_TYPE_NAME_MIPMAP.equals(resTypeName);
}
protected boolean isColor() {
return SkinConstant.RES_TYPE_NAME_COLOR.equals(resTypeName);
}
/**
* Application attribute
*
* @param view apply view
* @return Whether the attribute was changed
*/
public abstract boolean apply(View view);
@Override
public SkinAttr clone() {
SkinAttr o = null;
try {
o = (SkinAttr) super.clone();
} catch (CloneNotSupportedException e) {
LogUtils.printStackTrace(e);
}
return o;
}
}
可以看到 SkinAttr 这个类实现了 Cloneable 接口,实现了 clone 方法,调用时:
final class SkinAttrSupport {
private static final HashMap<String, SkinAttr> mSupportAttrs = new HashMap<>();
private static boolean ignoreWhenAttrNotFound = true;
static {
mSupportAttrs.put(SkinConstant.ATTR_NAME_BACKGROUND, new BackgroundAttr());
mSupportAttrs.put(SkinConstant.ATTR_NAME_TEXTCOLOR, new TextColorAttr());
mSupportAttrs.put(SkinConstant.ATTR_NAME_SRC, new SrcAttr());
mSupportAttrs.put(SkinConstant.ATTR_NAME_DRAWABLE_LEFT, new TextDrawableLeftAttr());
mSupportAttrs.put(SkinConstant.ATTR_NAME_DRAWABLE_RIGHT, new TextDrawableRightAttr());
mSupportAttrs.put(SkinConstant.ATTR_ITEM_ICON_HINT, new BottomNavigationItemIconTintAttr());
mSupportAttrs.put(SkinConstant.ATTR_ITEM_TEXT_COLOR, new BottomNavigationItemTextColorAttr());
}
static boolean isIgnoreWhenAttrNotFound() {
return ignoreWhenAttrNotFound;
}
static void setIgnoreWhenAttrNotFound(boolean ignore) {
ignoreWhenAttrNotFound = ignore;
}
static SkinAttr genSkinAttr(String attrName, String resEntryName, String resTypeName) {
// 使用 clone() 获取 SkinAttr 对象
SkinAttr skinAttr = mSupportAttrs.get(attrName).clone();
if (Requires.isNull(skinAttr))
return null;
skinAttr.attrName = attrName;
skinAttr.resEntryName = resEntryName;
skinAttr.resTypeName = resTypeName;
return skinAttr;
}
static boolean isSupportedAttr(String attrName) {
return mSupportAttrs.containsKey(attrName);
}
static void addSupportAttr(String attrName, SkinAttr skinAttr) {
mSupportAttrs.put(attrName, skinAttr);
}
static void removeSupport(String attrName) {
mSupportAttrs.remove(attrName);
}
}
---------------------
【转载,仅作分享,侵删】
作者:况众文
原文:https://blog.csdn.net/u014294681/article/details/87101320
|
|