LayoutInflater 及 换肤技术研究

2,447 阅读15分钟

1 LayoutInflater 获取

LayoutInflater 获取 实例一般有两种方式:

LayoutInflater.from(this);
View.inflate(this,R.layout.abc_activity_chooser_view,null);

2 两种获取方式的比较

LayoutInflater.from(this);
/**
 * Obtains the LayoutInflater from the given context.
 */
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;
}

从Context 中获取的 LayoutInflater 是通过 getSystemService 来获取到的。因为Context 是抽象类,实际实现的Context 的是 ContextImpl。接下来我们再看看ContextImpl如何获取。

View.inflate(this,R.layout.abc_activity_chooser_view,null);
public static View inflate(Context context, int resource, ViewGroup root) {
    LayoutInflater factory = LayoutInflater.from(context);
    return factory.inflate(resource, root);
}

通过View 进行获取的LayoutInflater,实际也是通过Context进行获取。所以只是包装了一份。

3 Context 获取的分析

​ ContextImpl 中的实现

static {  
   ...
       
    registerService(LAYOUT_INFLATER_SERVICE, new 
                    ServiceFetcher() {
                public Object createService(ContextImpl ctx) {return PolicyManager.makeNewLayoutInflater(ctx.getOuterContext());
                }});
    
    ...
}

​ 我们发现调用了PolicyManager,在PolicyManager中是通过sPolicy进行获取。

    public static LayoutInflater makeNewLayoutInflater(Context context) {
    return sPolicy.makeNewLayoutInflater(context);
}

​ 继续跟踪sPolicy ,sPolicy是通过反射创建的 com.android.internal.policy.impl.Policy的实体类。

   private static final String POLICY_IMPL_CLASS_NAME =
    "com.android.internal.policy.impl.Policy";

private static final IPolicy sPolicy;

static {
    // Pull in the actual implementation of the policy at run-time
    try {
        Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
        sPolicy = (IPolicy)policyClass.newInstance();
    } catch (ClassNotFoundException ex) {
        throw new RuntimeException(
                POLICY_IMPL_CLASS_NAME + " could not be loaded", ex);
    } catch (InstantiationException ex) {
        throw new RuntimeException(
                POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
    } catch (IllegalAccessException ex) {
        throw new RuntimeException(
                POLICY_IMPL_CLASS_NAME + " could not be instantiated", ex);
    }
}
public class Policy implements IPolicy {
  ...
 
    public LayoutInflater makeNewLayoutInflater(Context context) {
        return new PhoneLayoutInflater(context);
    }

  ...
}

​ 实际上最后获取的对象时 PhoneLayoutInflater。

​ 那么我们知道原来 LayoutInflater 的实现是 PhoneLayoutInflater。

4 PhoneLayoutInflater 源码分析

上面我们知道了inflater 的实现就是 PhoneLayoutInflater

public class PhoneLayoutInflater extends LayoutInflater {
  
   private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };
  
  public PhoneLayoutInflater(Context context) {
        super(context);
    }
    
    protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
        super(original, newContext);
    }
  
   @Override 
  protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        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);
    }
    
    public LayoutInflater cloneInContext(Context newContext) {
        return new PhoneLayoutInflater(this, newContext);
    }

这个代码没有什么特殊的地方,只有onCreateView 这个方法会返回一个View,我们常见的LayoutInflater 的使用方法是

LayoutInflater inflater =  LayoutInflater.from(this);
inflater.inflate(R.layout.abc_activity_chooser_view,null);

5 inflate 方法

inflate 方法最后会调用到 inflate(int resource, ViewGroup root, boolean attachToRoot)

public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
    final Resources res = getContext().getResources();
    if (DEBUG) {
        Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                + Integer.toHexString(resource) + ")");
    }

    final XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}

生成一个xml解析器,将这个XML的解析器传递给inflate 的重载方法

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

         //xml 属性
        final AttributeSet attrs = Xml.asAttributeSet(parser);
      
        Context lastContext = (Context)mConstructorArgs[0];
        mConstructorArgs[0] = mContext;
         
      //解析的root 
      View result = root;

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

            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                        + ": No start tag found!");
            }

            final String name = parser.getName();
            
            if (DEBUG) {
                System.out.println("**************************");
                System.out.println("Creating root view: "
                        + name);
                System.out.println("**************************");
            }
            
             //如果是merge 标签
            if (TAG_MERGE.equals(name)) {
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                            + "ViewGroup root and attachToRoot=true");
                }

                rInflate(parser, root, attrs, false, false);
            } else {
               
                //正常的标签(重点方法)
              
                final View temp = createViewFromTag(root, name, attrs, false);

                ViewGroup.LayoutParams params = null;
  
                //如果有根元素
                if (root != null) {
                    if (DEBUG) {
                        System.out.println("Creating params from root: " +
                                root);
                    }
                    // Create layout params that match root, if supplied
                    //生成根元素的LayoutParams
                    params = root.generateLayoutParams(attrs);
                  
                    if (!attachToRoot) {
                        // Set the layout params for temp if we are not
                        // attaching. (If we are, we use addView, below)
                        temp.setLayoutParams(params);
                    }
                }

                if (DEBUG) {
                    System.out.println("-----> start inflating children");
                }
                // Inflate all children under temp
                rInflate(parser, temp, attrs, true, true);
                if (DEBUG) {
                    System.out.println("-----> done inflating children");
                }

                // We are supposed to attach all the views we found (int temp)
                // to root. Do that now.
                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;
                }
            }

        } catch (XmlPullParserException e) {
            InflateException ex = new InflateException(e.getMessage());
            ex.initCause(e);
            throw ex;
        } catch (IOException e) {
            InflateException ex = new InflateException(
                    parser.getPositionDescription()
                    + ": " + e.getMessage());
            ex.initCause(e);
            throw ex;
        } finally {
            // Don't retain static reference on context.
            mConstructorArgs[0] = lastContext;
            mConstructorArgs[1] = null;
        }

        Trace.traceEnd(Trace.TRACE_TAG_VIEW);

        return result;
    }
}

所以我们知道了在这里真的生成一个View的方法实际是在

final View temp = createViewFromTag(root, name, inflaterContext, attrs);

下面我们看看这个类是怎么实现的

private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
    return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
    
    //如果是一个超类View,获取需要初始化的View的名称
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }

    // Apply a theme wrapper, if allowed and one is specified.
    if (!ignoreThemeAttr) {
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        if (themeResId != 0) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();
    }

    //类似闪屏的控件 <blink></blink>
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }
     
    //!重点!
    try {
        View view;
      
        //如果设置了 mFactory2 -》 调用 mFactory2.onCreateView 生成一个View
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
        }
        //如果设置了mFactory -》调用 mFactory.onCreateView 生成一个View
         else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
        } 
        //如果没有设置 mFactory2 或 mFactory 说明不需要进行预处理
        else {
            view = null;
        }
        
        //看看系统是不是有hook生成控件
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }
        
        //如果上述的方法都没有生成方法,说明没有hook方法调用
        if (view == null) {
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                //系统控件 LinearLayout
                if (-1 == name.indexOf('.')) {
                    view = onCreateView(parent, name, attrs);
                } else {
                //自定义控件 cn.hk.CustomView 
                    view = createView(name, null, attrs);
                }
            } finally {
                mConstructorArgs[0] = lastContext;
            }
        }

        return view;
    } catch (InflateException e) {
        throw e;

    } catch (ClassNotFoundException e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name, e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;

    } catch (Exception e) {
        final InflateException ie = new InflateException(attrs.getPositionDescription()
                + ": Error inflating class " + name, e);
        ie.setStackTrace(EMPTY_STACK_TRACE);
        throw ie;
    }
}
/**
 * @hide for use by framework ,hide 方法 系统调用
 */
public void setPrivateFactory(Factory2 factory) {
    if (mPrivateFactory == null) {
        mPrivateFactory = factory;
    } else {
        mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
    }
}

详细的逻辑如下:

![](D:\文档\android\inflate 与 换肤\img\2.PNG)

inflate 生成控件的时候,会根据是否是系统控件调用下面两个方法

protected View onCreateView(View parent, String name, AttributeSet attrs)
        throws ClassNotFoundException {
    return onCreateView(name, attrs);
}
public final View createView(String name, String prefix, AttributeSet attrs)
        throws ClassNotFoundException, InflateException {

onCreateView 是抽象方法,实现是在PhoneLayoutInflater 中实现的,

//前缀 
private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };
    

@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        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 也是通过createView来创建的。所以分析createView即可。唯一的区别 创建系统的控件时

createView(name, prefix, attrs);

createView(name, null, attrs);

区别就是 创建系统控件的时候有将前缀传递进去

public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
       
        //看看控件是不是有缓存
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        
        //看看classLoader是不是相同,如果相同就使用这个反射的构造器
         if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(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
                clazz = mContext.getClassLoader().loadClass(
                        prefix != null ? (prefix + name) : name).asSubclass(View.class);

                if (mFilter != null && clazz != null) {
                    boolean allowed = mFilter.onLoadClass(clazz);
                    if (!allowed) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
                //反射构造器
                constructor = clazz.getConstructor(mConstructorSignature);
                constructor.setAccessible(true);
                //缓存 
                sConstructorMap.put(name, constructor);
            } else {
                // If we have a filter, apply it to cached constructor
                if (mFilter != null) {
                    // Have we seen this name before?
                    Boolean allowedState = mFilterMap.get(name);
                    if (allowedState == null) {
                        // New class -- remember whether it is allowed
                        clazz = mContext.getClassLoader().loadClass(
                                prefix != null ? (prefix + name) : name).asSubclass(View.class);

                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
                        mFilterMap.put(name, allowed);
                        if (!allowed) {
                            failNotAllowed(name, prefix, attrs);
                        }
                    } else if (allowedState.equals(Boolean.FALSE)) {
                        failNotAllowed(name, prefix, attrs);
                    }
                }
            }

            Object lastContext = mConstructorArgs[0];
            if (mConstructorArgs[0] == null) {
                // Fill in the context if not already within inflation.
                mConstructorArgs[0] = mContext;
            }
            Object[] args = mConstructorArgs;
            args[1] = attrs;
            //反射调用 两个参数的构造方法 
            // public View (Context context, AttributeSet attrs) {
        
            final View view = constructor.newInstance(args);
            
            //如果是一个ViewStub
            if (view instanceof ViewStub) {
                // Use the same context when inflating ViewStub later.
                final ViewStub viewStub = (ViewStub) view;
                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
            }
            mConstructorArgs[0] = lastContext;
            return view;

        } catch (NoSuchMethodException e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;

        } catch (ClassCastException e) {
            // If loaded class is not a View subclass
            final InflateException ie = new InflateException(attrs.getPositionDescription()
                    + ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } catch (ClassNotFoundException e) {
            // If loadClass fails, we should propagate the exception.
            throw e;
        } catch (Exception e) {
            final InflateException ie = new InflateException(
                    attrs.getPositionDescription() + ": Error inflating class "
                            + (clazz == null ? "<unknown>" : clazz.getName()), e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

6 LayoutInflater.Factory

根据源码分析,我们知道了

![](D:\文档\android\inflate 与 换肤\img\1552560080191

.png)

要经过 mfactory 和 mfactory2 两个过程尝试初始化一个View, 如果初始化完成,就不会走自己的onCreateView和createView方法了。

if (mFactory2 != null) {
    view = mFactory2.onCreateView(parent, name, viewContext, attrs);
} else if (mFactory != null) {
    view = mFactory.onCreateView(name, viewContext, attrs);
} else {
    view = null;
}

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

if (view == null) {
    final Object lastContext = mConstructorArgs[0];
    mConstructorArgs[0] = viewContext;
    try {
        if (-1 == name.indexOf('.')) {
            view = onCreateView(parent, name, attrs);
        } else {
            view = createView(name, null, attrs);
        }
    } finally {
        mConstructorArgs[0] = lastContext;
    }
}

所以我们可以尝试来拦截一下控件的生成。

动态的修改文字的颜色。

public class DemoActivity extends Activity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getLayoutInflater().setFactory(new LayoutInflater.Factory() {
            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                TextView textView = null;
                if(name.equals("TextView")){
                    int n = attrs.getAttributeCount();
                    //打印所有属性标签
                    for (int i = 0; i < n; i++) {
                        Log.i("hked", attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
                    }
                    try {
                        textView = (TextView) getLayoutInflater().createView(name,"android.widget.",attrs);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                    if(textView!=null){
                        textView.setTextColor(Color.YELLOW);
                    }

                }

                return textView;
            }
        });
        setContentView(R.layout.activity_demo);
    }
}

7 Factory 和 Factory2 的区别

public interface Factory {
    public View onCreateView(String name, Context context, AttributeSet attrs);
}
public interface Factory2 extends Factory {
       public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}

实际上 Factory2 是 Factory 的升级版,在api 11(3.0后)上开始提供的,除了提供控件的名称外,还会提供父控件。3.0 以前建议用 Factory ,3.0以上建议用 Factory2。

8 Layoutflater 在 Activity 和 AppCompatActivity 使用的区别

我们知道 AppCompatActivity 是谷歌提供给我们的兼容版本

public class DemoAppcompatActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getLayoutInflater().setFactory(new LayoutInflater.Factory() {
            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                TextView textView = null;
                if(name.equals("TextView")){
                    int n = attrs.getAttributeCount();
                    //打印所有属性标签
                    for (int i = 0; i < n; i++) {
                        Log.i("hked", attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
                    }
                    try {
                        textView = (TextView) getLayoutInflater().createView(name,"android.widget.",attrs);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                    if(textView!=null){
                        textView.setTextColor(Color.YELLOW);
                    }

                }

                return textView;
            }
        });
        setContentView(R.layout.activity_demo);
    }

Caused by: java.lang.IllegalStateException: A factory has already been set on this LayoutInflater at android.view.LayoutInflater.setFactory(LayoutInflater.java:295) at com.example.huangkai.skin.DemoAppcompatActivity.onCreate(DemoAppcompatActivity.java:29) at android.app.Activity.performCreate(Activity.java:7224) at android.app.Activity.performCreate(Activity.java:7213) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1272)

我们看到在LayoutInflater 的setFactory 和 setFactory2 方法内部

public void setFactory(Factory factory) {
    if (mFactorySet) {
        throw new IllegalStateException("A factory has already been set on this LayoutInflater");
    }
    if (factory == null) {
        throw new NullPointerException("Given factory can not be null");
    }
    mFactorySet = true;
    if (mFactory == null) {
        mFactory = factory;
    } else {
        mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
    }
}
public void setFactory2(Factory2 factory) {
    if (mFactorySet) {
        throw new IllegalStateException("A factory has already been set on this LayoutInflater");
    }
    if (factory == null) {
        throw new NullPointerException("Given factory can not be null");
    }
    mFactorySet = true;
    if (mFactory == null) {
        mFactory = mFactory2 = factory;
    } else {
        mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
    }
}

//如果设置过Factory,就会抛出这个错误

if (mFactorySet) { throw new IllegalStateException("A factory has already been set on this LayoutInflater"); }

我们修改一下

public class DemoAppcompatActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        getLayoutInflater().setFactory(new LayoutInflater.Factory() {
            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                TextView textView = null;
                if(name.equals("TextView")){
                    int n = attrs.getAttributeCount();
                    //打印所有属性标签
                    for (int i = 0; i < n; i++) {
                        Log.i("hked", attrs.getAttributeName(i) + " , " + attrs.getAttributeValue(i));
                    }
                    try {
                        textView = (TextView) getLayoutInflater().createView(name,"android.widget.",attrs);
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    }
                    if(textView!=null){
                        textView.setTextColor(Color.YELLOW);
                    }

                }

                return textView;
            }
        });
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_demo);
    }
}

在 super 前执行就可以了。为什么?我们看看内部执行

protected void onCreate(@Nullable Bundle savedInstanceState) {
    //代理对象
    AppCompatDelegate delegate = this.getDelegate();
    //初始化Factory 
    delegate.installViewFactory();
    //调用onCreate
    delegate.onCreate(savedInstanceState);
 
    if (delegate.applyDayNight() && this.mThemeId != 0) {
        if (VERSION.SDK_INT >= 23) {
            this.onApplyThemeResource(this.getTheme(), this.mThemeId, false);
        } else {
            this.setTheme(this.mThemeId);
        }
    }

    super.onCreate(savedInstanceState);
}

在 Api 28 和 Api 26 的版本实现有不同的区别

@NonNull
public AppCompatDelegate getDelegate() {
    if (mDelegate == null) {
        mDelegate = AppCompatDelegate.create(this, this);
    }
    return mDelegate;
}

api 26 版本

private static AppCompatDelegate create(Context context, Window window,
        AppCompatCallback callback) {
    if (Build.VERSION.SDK_INT >= 24) {
        return new AppCompatDelegateImplN(context, window, callback);
    } else if (Build.VERSION.SDK_INT >= 23) {
        return new AppCompatDelegateImplV23(context, window, callback);
    } else if (Build.VERSION.SDK_INT >= 14) {
        return new AppCompatDelegateImplV14(context, window, callback);
    } else if (Build.VERSION.SDK_INT >= 11) {
        return new AppCompatDelegateImplV11(context, window, callback);
    } else {
        return new AppCompatDelegateImplV9(context, window, callback);
    }
}

api 28 版本

public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
    return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}
public void installViewFactory() {
    //获取系统的LayoutInflater
    LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
    
    if (layoutInflater.getFactory() == null) {
        //没设置Factory,将AppCompatDelegateImpl设置为Factory2
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    } else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
        Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
    }

}

Factory2 需要实现的方法如下

public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    return this.createView(parent, name, context, attrs);
}

public View onCreateView(String name, Context context, AttributeSet attrs) {
    return this.onCreateView((View)null, name, context, attrs);
}
public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
    if (this.mAppCompatViewInflater == null) {
         TypedArray a = this.mContext.obtainStyledAttributes(styleable.AppCompatTheme);
         
         //实际Inflator的类名
         String viewInflaterClassName = a.getString(styleable.AppCompatTheme_viewInflaterClass);
        
        //自定义的Inflator
       if (viewInflaterClassName != null && !AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
            try {
                Class viewInflaterClass = Class.forName(viewInflaterClassName);
                this.mAppCompatViewInflater = (AppCompatViewInflater)viewInflaterClass.getDeclaredConstructor().newInstance();
            } catch (Throwable var8) {
                Log.i("AppCompatDelegate", "Failed to instantiate custom view inflater " + viewInflaterClassName + ". Falling back to default.", var8);
                this.mAppCompatViewInflater = new AppCompatViewInflater();
            }
        } else {
         //系统提供的Inflator
            this.mAppCompatViewInflater = new AppCompatViewInflater();
        }
    }

    boolean inheritContext = false;
    if (IS_PRE_LOLLIPOP) {
        inheritContext = attrs instanceof XmlPullParser ? ((XmlPullParser)attrs).getDepth() > 1 : this.shouldInheritContext((ViewParent)parent);
    }

    return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());
}

整个流程应该是这样的

LayoutInflater->

factory2(AppCompatDelegateImpl) ->

onCreateView->

(AppCompatDelegateImpl) createView->

(AppCompatViewInflater)mAppCompatViewInflater ->

createView

final View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
    Context originalContext = context;
    if (inheritContext && parent != null) {
        context = parent.getContext();
    }

    if (readAndroidTheme || readAppTheme) {
        context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
    }

    if (wrapContext) {
        context = TintContextWrapper.wrap(context);
    }

    View view = null;
    byte var12 = -1;
    switch(name.hashCode()) {
    case -1946472170:
        if (name.equals("RatingBar")) {
            var12 = 11;
        }
        break;
    case -1455429095:
        if (name.equals("CheckedTextView")) {
            var12 = 8;
        }
        break;
    case -1346021293:
        if (name.equals("MultiAutoCompleteTextView")) {
            var12 = 10;
        }
        break;
    case -938935918:
        if (name.equals("TextView")) {
            var12 = 0;
        }
        break;
    case -937446323:
        if (name.equals("ImageButton")) {
            var12 = 5;
        }
        break;
    case -658531749:
        if (name.equals("SeekBar")) {
            var12 = 12;
        }
        break;
    case -339785223:
        if (name.equals("Spinner")) {
            var12 = 4;
        }
        break;
    case 776382189:
        if (name.equals("RadioButton")) {
            var12 = 7;
        }
        break;
    case 1125864064:
        if (name.equals("ImageView")) {
            var12 = 1;
        }
        break;
    case 1413872058:
        if (name.equals("AutoCompleteTextView")) {
            var12 = 9;
        }
        break;
    case 1601505219:
        if (name.equals("CheckBox")) {
            var12 = 6;
        }
        break;
    case 1666676343:
        if (name.equals("EditText")) {
            var12 = 3;
        }
        break;
    case 2001146706:
        if (name.equals("Button")) {
            var12 = 2;
        }
    }

    switch(var12) {
    case 0:
        view = this.createTextView(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 1:
        view = this.createImageView(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 2:
        view = this.createButton(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 3:
        view = this.createEditText(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 4:
        view = this.createSpinner(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 5:
        view = this.createImageButton(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 6:
        view = this.createCheckBox(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 7:
        view = this.createRadioButton(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 8:
        view = this.createCheckedTextView(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 9:
        view = this.createAutoCompleteTextView(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 10:
        view = this.createMultiAutoCompleteTextView(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 11:
        view = this.createRatingBar(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    case 12:
        view = this.createSeekBar(context, attrs);
        this.verifyNotNull((View)view, name);
        break;
    default:
        view = this.createView(context, name, attrs);
    }

    if (view == null && originalContext != context) {
        view = this.createViewFromTag(context, name, attrs);
    }

    if (view != null) {
        this.checkOnClickListener((View)view, attrs);
    }

    return (View)view;
}

所以我们知道了AppcompatActivity 默默的帮我们做了控件兼容控件的转化工作。这样有什么好处,我们知道各个版本的设置方法不同。AppCompat包帮我们做了兼容。而且是通过factory方式来进行的

所以在super方法后面再次设置Factory会报错。但是如果设置了Factory又会导致我们没法使用系统的兼容的控件,所以我们需要在后面的换肤中既考虑到换肤的处理,有要兼容系统的控件。

public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
    if (layoutInflater.getFactory() == null) {
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    } else if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
        Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
    }

}

9 换肤的实现

9.1 换肤的核心

​ 换肤的核心就两个点,第一个点就是获取需要换肤的控件,第二个点就是如何加载其他包的资源

9.2 获取需要换肤的控件

​ 通过使用 Factory2来hook 控件的构造

public class SkinInflaterFactory implements LayoutInflater.Factory2 {

    private Map<View,SkinItem> mSkinMap = new HashMap<>();
    private AppCompatActivity activity;

    public SkinInflaterFactory(AppCompatActivity activity){
        this.activity = activity;
    }

    @Override
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
        boolean isNeedSkin  = attrs.getAttributeBooleanValue(SkinConfig.NAME_SPACE,SkinConfig.SKIN_ATTRS_NAME,false);
        //AppCompatActivity 会将textView 等转成 AppcompatTextView 支持以下控件
        //RatingBar//CheckedTextView//MultiAutoCompleteTextView//TextView//ImageButton     //SeekBar//Spinner//RadioButton//ImageView//AutoCompleteTextView//CheckBox//EditText//Button
        //先看看控件是不是需要转Appcompat
        AppCompatDelegate delegate = activity.getDelegate();
        View view = delegate.createView(parent, name, context, attrs);

        if(isNeedSkin){

            if(view == null){
                view =  ViewProducer.createViewFromTag(context,name,attrs);
            }

            //控件解析失败
            if(view == null){
                return null;
            }

            collectSkinView(context,attrs,view);
        }
        return view;
    }

    @Override
    public View onCreateView(String name, Context context, AttributeSet attrs) {
        return null;
    }

    public void applySkin(){

        //如果当前的页面没有需要更换图片的控件
        if(mSkinMap.isEmpty()){
             return;
        }

        for(View view:mSkinMap.keySet()){
            if(view == null){
              continue;
            }
            if(mSkinMap.get(view)!=null){
                mSkinMap.get(view).apply();
            }
        }


    }


    private  void collectSkinView(Context context,AttributeSet attrs,View view){

        List<SkinAttr> par =  new ArrayList<>();
        for(int i =0;i<attrs.getAttributeCount();i++){
            String attrName = attrs.getAttributeName(i);
            String attrValue = attrs.getAttributeValue(i);
            Log.i("hked","attrName = "+attrName+" attrValue = "+attrValue);
            if(AttrFactory.isSupportAttr(attrName)){
                int id = Integer.parseInt(attrValue.substring(1));
                String entryName = context.getResources().getResourceEntryName(id);
                //typeName,eg:color、drawable
                String typeName = context.getResources().getResourceTypeName(id);

                SkinAttr change = AttrFactory.get(attrName,id,entryName,typeName);
                par.add(change);
            }
        }

        if(par.size()==0){
            return;
        }
        SkinItem item =  new SkinItem(view,par);
        mSkinMap.put(view,item);
        //如果已经是换肤模式了,更换图片
        Log.i("hked","SkinManger.getInstance().isExternalSkin() = "+SkinManger.getInstance().isExternalSkin());
        if(SkinManger.getInstance().isExternalSkin()){
            item.apply();
       }
    }
}

9.3 基类处理

public class SkinBaseActivity extends AppCompatActivity implements ISkinObersver {

    private SkinInflaterFactory factory;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {

        factory = new SkinInflaterFactory(this);
        LayoutInflaterCompat.setFactory2(getLayoutInflater(),factory);
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onResume() {
        super.onResume();
        SkinManger.getInstance().attach(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        SkinManger.getInstance().detach(this);
    }

    @Override
    public void onSkinChange() {
        factory.applySkin();
    }
}
public class SkinBaseApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        SkinManger.getInstance().init(this);
    }
}

9.4 换肤管理器

public class SkinManger implements ISkinLoader {

    public String Tag = "skin";
    private static SkinManger instance;
    private boolean isDefalutSkin = false;
    private List<ISkinObersver> obersvers;
    private Context context;
    private String skinPackageName;
    private Resources mResource;

    private SkinManger() {

    }

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

    public void init(Context context) {
        this.context = context.getApplicationContext();

        if (SkinConfig.isDefaultSkin(context)) {
            isDefalutSkin = true;
            return;
        }

        //加载图片资源包的resouce
        String path = SkinConfig.getCustomSkinPath(context);
        loadSkin(path,null);
    }

    public void showDefalutSkin(){
        isDefalutSkin = false;
        mResource = context.getResources();
        SkinConfig.saveSkinPath(context, SkinConfig.DEFAULT_SKIN);
        notifySkinUpdate();
    }

    @Override

    public void attach(ISkinObersver obersver) {
        if (obersvers == null) {
            obersvers = new ArrayList<>();
        }
        if (!obersvers.contains(obersver)) {
            obersvers.add(obersver);
        }
    }

    @Override
    public void detach(ISkinObersver obersver) {
        if (obersvers != null && obersvers.contains(obersver)) {
            obersvers.remove(obersver);
        }
    }

    @Override
    public void notifySkinUpdate() {
        if (obersvers != null) {
            for (ISkinObersver obersver : obersvers) {
                obersver.onSkinChange();
            }
        }
    }


    @SuppressLint("StaticFieldLeak")
    public void loadSkin(String skinName, final SkinLoaderListener callBack) {
        new AsyncTask<String, Void, Resources>() {

            @Override
            protected void onPreExecute() {
                super.onPreExecute();
                if (callBack != null) {
                    callBack.onStart();
                }
            }

            @Override
            protected Resources doInBackground(String... strings) {
                int permission = ActivityCompat.checkSelfPermission(context,
                        "android.permission.WRITE_EXTERNAL_STORAGE");
                if (permission != PackageManager.PERMISSION_GRANTED) {
                    return null;
                }
                String fileName = strings[0];
                File file = new File(Environment.getExternalStorageDirectory(), fileName);

                PackageManager packageManager = context.getPackageManager();
                PackageInfo mInfo = packageManager.getPackageArchiveInfo(file.getAbsolutePath(), PackageManager.GET_ACTIVITIES);
                skinPackageName = mInfo.packageName;

                AssetManager assetManager = null;
                try {
                    assetManager = AssetManager.class.newInstance();
                    Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
                    addAssetPath.invoke(assetManager, file.getAbsolutePath());


                    Resources superRes = context.getResources();
                    Resources skinResource = ResourcesCompat.getResources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());
                    if (skinResource != null) {
                        SkinConfig.saveSkinPath(context, fileName);
                    }
                    return skinResource;
                } catch (Exception e) {
                    e.printStackTrace();
                }

                return null;
            }

            @Override
            protected void onPostExecute(Resources resources) {
                if (resources != null) {
                    mResource = resources;
                    if (callBack != null) {
                        callBack.onSuccess();
                    }
                    isDefalutSkin = false;
                    notifySkinUpdate();
                } else {
                    if (callBack != null) {
                        callBack.onFailed("资源包加载失败");
                    }
                }
            }
        }.execute(skinName);
    }

    public boolean isExternalSkin() {
        return !isDefalutSkin && mResource != null;
    }


    public int getColor(int id){
       int originColor = ContextCompat.getColor(context, id);
        //如果是默认的皮肤
        if (mResource == null || isDefalutSkin) {
            return originColor;
        }
        //查找资源的名称
        String resName = context.getResources().getResourceEntryName(id);
        //尝试在drawable下找图片
        int trueResId = mResource.getIdentifier(resName, "color", skinPackageName);

        if(trueResId == 0 ){
            return originColor;
        }
        if (android.os.Build.VERSION.SDK_INT < 23) {
            return mResource.getColor(trueResId);
        }else{
            return mResource.getColor(trueResId,null);
        }

    }


    public Drawable getDrawable(int id) {
        Drawable originDrawable = ContextCompat.getDrawable(context, id);
        //如果是默认的皮肤
        if (mResource == null || isDefalutSkin) {
            return originDrawable;
        }

        //查找资源的名称
        String resName = context.getResources().getResourceEntryName(id);
        //尝试在drawable下找图片
        int trueResId = mResource.getIdentifier(resName, "drawable", skinPackageName);
        Drawable trueDrawable;
        if (trueResId == 0) {
            //drawable 下找不到。去mipmap
            trueResId = mResource.getIdentifier(resName, "mipmap", skinPackageName);
        }

        if (trueResId == 0) {
            trueDrawable = originDrawable;
        } else {
            if (android.os.Build.VERSION.SDK_INT < 22) {
                trueDrawable = mResource.getDrawable(trueResId);
            } else {
                trueDrawable = mResource.getDrawable(trueResId, null);
            }
        }
        return trueDrawable;
    }
}

9.5 换肤的详细处理类

public abstract class SkinAttr implements Cloneable{

    protected static final String RES_TYPE_NAME_COLOR = "color";
    protected static final String RES_TYPE_NAME_DRAWABLE = "drawable";
    protected static final String RES_TYPE_NAME_MIPMAP = "mipmap";

    /**
     * 属性名称
     */
    public String attrName;

    /**
     * 换肤的值
     */
    public int attrValueRefId;

    /**
     * 换肤的名称
     */
    public String attrValueName;

    /**
     * 换肤的类型
     */
    public String attrValueTypeName;

    public void apply(View view){
      applySkin(view);
    }

     abstract void applySkin(View view);


    public boolean isDrawable(){
        return  RES_TYPE_NAME_DRAWABLE.equals(attrValueTypeName)|| RES_TYPE_NAME_MIPMAP.equals(attrValueTypeName);
    }

    @Override
    public SkinAttr clone()  {
        SkinAttr o = null;
        try {
            o = (SkinAttr) super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }
}

背景类型的换肤

public class BackGroudAttr extends SkinAttr {
    @Override
    void applySkin(View view) {
      if(isDrawable()){
         Drawable bg = SkinResUtil.loadDrawable(attrValueRefId);
         view.setBackgroundDrawable(bg);
      }else{
         int color = SkinResUtil.loadColor(attrValueRefId);
          view.setBackgroundColor(color);
      }
    }
}

文字颜色的换肤

public class TextColorAttr extends SkinAttr {

    @Override
    void applySkin(View view) {
        TextView textView = (TextView) view;
        int color = SkinResUtil.loadColor(attrValueRefId);
        textView.setTextColor(color);
    }
}

10 常见的换肤开源框架的研究

目前的主要的换肤的开源框架如下:

1 Android-skin-support

​ 网站 github.com/ximsfei/And…

​ 原理:通过LayoutInflater 进行转换,自定义了一系列的换肤控件。

​ 优点:兼容性好,完成度高,支持的格式多。

​ 缺点:代码规模大,引进这个库大概会带来1000多个方法

2 Android-Skin-Loader

网站 github.com/fengjundev/…

原理:通过LayoutInflater 进行查找需要换肤的控件

优点:逻辑简单

缺点:停止维护