最后
文章所有资料全部已经打包整理好,另外小编手头上整理了大量Android架构师全套学习资料,Android核心高级技术PDF文档+全套高级学习资料+视频+2021 BAT 大厂面试真题解析
资料展示:
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
Log.i(TAG, “The Activity’s LayoutInflater already has a Factory installed”
- " so we can not install AppCompat’s");
}
}
}
LayoutInflaterCompat.setFactory2(layoutInflater, this);
最终是调用的LayoutInflater的setFactory2()
方法,看看实现
/**
-
Like {@link #setFactory}, but allows you to set a {@link Factory2}
-
interface.
*/
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);
}
}
这里有个小细节,Factory2只能被设置一次,设置完成后mFactorySet属性就为true,下一次设置时被直接抛异常.
那么Factory2有什么用呢?看看其实现
public interface Factory2 extends Factory {
/**
-
Version of {@link #onCreateView(String, Context, AttributeSet)}
-
that also supplies the parent that the view created view will be
-
placed in.
-
@param parent The parent that the created view will be placed
-
in; note that this may be null.
-
@param name Tag name to be inflated.
-
@param context The context the view is being created in.
-
@param attrs Inflation attributes as specified in XML file.
-
@return View Newly created view. Return null for the default
-
behavior.
*/
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
它是一个接口,只有一个方法,看起来是用来创建View的.到达是不是呢?答案稍后揭晓.
AppCompatActivity设置了一个委托,并给LayoutInflater设置了一个mFactory2.现在知道这个就够了.
下面先看看Android是如何根据xml布局创建一个View的
平时我们最常使用的Activity中的setContentView()设置布局ID,看看Activity中的实现,
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
调用的是Window中的setContentView(),而Window只有一个实现类,就是PhoneWindow.看看setContentView()实现
@Override
public void setContentView(int layoutResID) {
…
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
…
}
看到了今天的主角mLayoutInflater,mLayoutInflater是在PhoneWindow的构造方法中初始化的.用mLayoutInflater去加载这个布局(layoutResID).点进去看看实现
LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable 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解析器,然后进行解析
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
// Temp is the root view that was found in the xml
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
…
}
其实里面我觉得就只有2句关键代码,就是去根据xml写的东西去构建View嘛.rInflateChildren()
最后还是会去调用createViewFromTag()
方法,这里是为了先创建出rootView,然后将子View添加进rootView.
来看看createViewFromTag()的实现
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
…
try {
View view;
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);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf(‘.’)) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
…
return view;
}
可以看到如果mFactory2不为空的话,那么就会调用mFactory2去创建View(mFactory2.onCreateView(parent, name, context, attrs)) . 这句结论很重要.前面的答案已揭晓.如果设置了mFactory2就会用mFactory2去创建View.而mFactory2在上面的AppCompatDelegateImplV9
的installViewFactory()
中已设置好了的,其实mFactory2就是AppCompatDelegateImplV9.
来看看createView()的具体实现
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
mAppCompatViewInflater = new AppCompatViewInflater();
}
boolean inheritContext = false;
if (IS_PRE_LOLLIPOP) {
inheritContext = (attrs instanceof XmlPullParser)
// If we have a XmlPullParser, we can detect where we are in the layout
? ((XmlPullParser) attrs).getDepth() > 1
-
// Otherwise we have to use the old heuristic
- shouldInheritContext((ViewParent) parent);
}
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
true, /* Read read app:theme as a fallback at all times for legacy reasons */
VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
);
}
可以看到,最后是调用的AppCompatViewInflater的对象的createView()
去创建View.我感觉AppCompatViewInflater就是专门用来创建View的,面向对象的五大原则之一–单一职责原则.
AppCompatViewInflater类非常重要,先来看看上面提到的createView()
方法的源码:
public final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
…
View view = null;
// We need to ‘inject’ our tint aware Views in place of the standard framework versions
switch (name) {
case “TextView”:
view = new AppCompatTextView(context, attrs);
break;
case “ImageView”:
view = new AppCompatImageView(context, attrs);
break;
case “Button”:
view = new AppCompatButton(context, attrs);
break;
case “EditText”:
view = new AppCompatEditText(context, attrs);
break;
case “Spinner”:
view = new AppCompatSpinner(context, attrs);
break;
case “ImageButton”:
view = new AppCompatImageButton(context, attrs);
break;
case “CheckBox”:
view = new AppCompatCheckBox(context, attrs);
break;
case “RadioButton”:
view = new AppCompatRadioButton(context, attrs);
break;
case “CheckedTextView”:
view = new AppCompatCheckedTextView(context, attrs);
break;
case “AutoCompleteTextView”:
view = new AppCompatAutoCompleteTextView(context, attrs);
break;
case “MultiAutoCompleteTextView”:
view = new AppCompatMultiAutoCompleteTextView(context, attrs);
break;
case “RatingBar”:
view = new AppCompatRatingBar(context, attrs);
break;
case “SeekBar”:
view = new AppCompatSeekBar(context, attrs);
break;
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check its android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
可以看到如果在xml中写了一个TextView
控件,其实是通过我们写的控件名称判断是什么控件,然后去new的方式创建出来的,并且new的不是TextView,而是AppCompatTextView
.其他的一些系统控件也是这么new出来的.
但是,有个问题,如果我在xml布局中不是写的这些控件(比如RecyclerView,自定义控件等),那么怎么创建view呢?注意到代码中如果执行完switch块之后view为空(说明不是上面列的那些控件),调用了createViewFromTag()
方法.来看看实现
private static final String[] sClassPrefixList = {
“android.widget.”,
“android.view.”,
“android.webkit.”
};
private View createViewFromTag(Context context, String name, AttributeSet attrs) {
if (name.equals(“view”)) {
name = attrs.getAttributeValue(null, “class”);
}
try {
mConstructorArgs[0] = context;
mConstructorArgs[1] = attrs;
//这里判断一下name(即在xml中写的控件名称)中是否含有’.’
//如果没有那么肯定就是系统控件(比如ProgressBar,在布局中是不需要加ProgressBar的具体包名的)
//如果有那么就是自定义控件,或者是系统的控件(比如android.support.v7.widget.SwitchCompat)
if (-1 == name.indexOf(‘.’)) {
for (int i = 0; i < sClassPrefixList.length; i++) {
final View view = createView(context, name, sClassPrefixList[i]);
if (view != null) {
return view;
}
}
return null;
} else {
return createView(context, name, null);
}
} catch (Exception e) {
// We do not want to catch these, lets return null and let the actual LayoutInflater
// try
return null;
} finally {
// Don’t retain references on context.
mConstructorArgs[0] = null;
mConstructorArgs[1] = null;
}
}
这里比较有意思,首先是判断一下是否是系统控件,怎么判断呢?通过判断控件名称中是否包含’.‘来判断.系统控件在xml布局中声明时是不需要加具体包名的,比如ProgressBar,所以没有’.‘的肯定是系统控件.那么有’.'的就是自定义控件或者一些特殊的系统控件了(比如android.support.v7.widget.SwitchCompat).
有个小疑问?为什么系统控件可以在布局中声明时不加包名,而自定义控件必须要加包名呢?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XveKLAOT-1600323763198)(http://olg7c0d2n.bkt.clouddn.com/2018%E5%B9%B47%E6%9C%8829%E6%97%A5143048.jpg)]
其实是系统的控件大多放在sClassPrefixList定义的这些包名下,所以待会儿可以通过拼接的方式将控件的位置找到.随便举个例子,我们来看看哪些系统控件在android.widget.
包下面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dXLAZogd-1600323763200)(http://olg7c0d2n.bkt.clouddn.com/18-7-27/92621264.jpg)]
源码中创建系统控件和非系统控件分开去创建.其实方法都是同一个,只是一个传了前缀,一个没有传前缀.来看看创建方法实现
private static final Class<?>[] sConstructorSignature = new Class[]{
Context.class, AttributeSet.class};
private static final Map<String, Constructor<? extends View>> sConstructorMap
= new ArrayMap<>();
private View createView(Context context, String name, String prefix)
throws ClassNotFoundException, InflateException {
//这里的sConstructorMap是用来做缓存的,如果之前已经创建,则会将构造方法缓存起来,下次直接用
Constructor<? extends View> constructor = sConstructorMap.get(name);
try {
if (constructor == null) {
// Class not found in the cache, see if it’s real, and try to add it
//通过classLoader去寻找该class,这里的classLoader其实是PathClassLoader
//看到没? (prefix + name)这种直接将前缀与名称拼接的方式就可以将View的位置拼接出来
//然后其他的全类名的View就不需要拼接前缀
Class<? extends View> clazz = context.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
//获取构造方法
constructor = clazz.getConstructor(sConstructorSignature);
//缓存构造方法
sConstructorMap.put(name, constructor);
}
//设置构造方法可访问
constructor.setAccessible(true);
//通过构造方法new一个View对象出来
return constructor.newInstance(mConstructorArgs);
} catch (Exception e) {
// We do not want to catch these, lets return null and let the actual LayoutInflater
// try
return null;
}
}
其实这个创建View就是利用ClassLoader去寻找这个类的class,然后获取其{ Context.class, AttributeSet.class}
这个构造方法,然后通过反射将View创建出来.具体逻辑在代码中已标明注释.
至此,Android的控件加载方式已全部剖析完毕.
其中,有一个小细节,刚刚为了流程顺畅没有在上面说到,上面有一段构建View(根据控件名称创建AppCompatXX控件)的代码如下:
switch (name) {
case “TextView”:
view = new AppCompatTextView(context, attrs);
break;
case “ImageView”:
view = new AppCompatImageView(context, attrs);
break;
case “Button”:
view = new AppCompatButton(context, attrs);
break;
}
我们来随便看一下控件的源码,比如AppCompatTextView
,其他的AppCompatXX控件实现都是差不多的.
public class AppCompatTextView extends TextView implements TintableBackgroundView,
AutoSizeableTextView {
//这2个是关键类
private final AppCompatBackgroundHelper mBackgroundTintHelper;
private final AppCompatTextHelper mTextHelper;
public AppCompatTextView(Context context) {
this(context, null);
}
public AppCompatTextView(Context context, AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(TintContextWrapper.wrap(context), attrs, defStyleAttr);
mBackgroundTintHelper = new AppCompatBackgroundHelper(this);
mBackgroundTintHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper = AppCompatTextHelper.create(this);
mTextHelper.loadFromAttributes(attrs, defStyleAttr);
mTextHelper.applyCompoundDrawablesTints();
}
…
class AppCompatBackgroundHelper {
…
void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
R.styleable.ViewBackgroundHelper, defStyleAttr, 0);
try {
if (a.hasValue(R.styleable.ViewBackgroundHelper_android_background)) {
//获取android:background 背景的资源id
mBackgroundResId = a.getResourceId(
R.styleable.ViewBackgroundHelper_android_background, -1);
ColorStateList tint = mDrawableManager
.getTintList(mView.getContext(), mBackgroundResId);
if (tint != null) {
setInternalBackgroundTint(tint);
}
}
if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTint)) {
//获取android:backgroundTint
ViewCompat.setBackgroundTintList(mView,
a.getColorStateList(R.styleable.ViewBackgroundHelper_backgroundTint));
}
if (a.hasValue(R.styleable.ViewBackgroundHelper_backgroundTintMode)) {
//获取android:backgroundTintMode
ViewCompat.setBackgroundTintMode(mView,
DrawableUtils.parseTintMode(
a.getInt(R.styleable.ViewBackgroundHelper_backgroundTintMode, -1),
null));
}
} finally {
a.recycle();
}
}
}
class AppCompatTextHelper {
…
@SuppressLint(“NewApi”)
void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
final Context context = mView.getContext();
final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
// First read the TextAppearance style id
TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
R.styleable.AppCompatTextHelper, defStyleAttr, 0);
final int ap = a.getResourceId(R.styleable.AppCompatTextHelper_android_textAppearance, -1);
// Now read the compound drawable and grab any tints
if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableLeft)) {
mDrawableLeftTint = createTintInfo(context, drawableManager,
a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableLeft, 0));
}
if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableTop)) {
mDrawableTopTint = createTintInfo(context, drawableManager,
a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableTop, 0));
}
if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableRight)) {
mDrawableRightTint = createTintInfo(context, drawableManager,
a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableRight, 0));
}
if (a.hasValue(R.styleable.AppCompatTextHelper_android_drawableBottom)) {
mDrawableBottomTint = createTintInfo(context, drawableManager,
a.getResourceId(R.styleable.AppCompatTextHelper_android_drawableBottom, 0));
}
a.recycle();
// PasswordTransformationMethod wipes out all other TransformationMethod instances
// in TextView’s constructor, so we should only set a new transformation method
// if we don’t have a PasswordTransformationMethod currently…
final boolean hasPwdTm =
mView.getTransformationMethod() instanceof PasswordTransformationMethod;
boolean allCaps = false;
boolean allCapsSet = false;
ColorStateList textColor = null;
ColorStateList textColorHint = null;
ColorStateList textColorLink = null;
// First check TextAppearance’s textAllCaps value
if (ap != -1) {
a = TintTypedArray.obtainStyledAttributes(context, ap, R.styleable.TextAppearance);
if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) {
allCapsSet = true;
allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
}
updateTypefaceAndStyle(context, a);
if (Build.VERSION.SDK_INT < 23) {
// If we’re running on < API 23, the text color may contain theme references
// so let’s re-set using our own inflater
if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
}
if (a.hasValue(R.styleable.TextAppearance_android_textColorHint)) {
textColorHint = a.getColorStateList(
R.styleable.TextAppearance_android_textColorHint);
}
if (a.hasValue(R.styleable.TextAppearance_android_textColorLink)) {
textColorLink = a.getColorStateList(
R.styleable.TextAppearance_android_textColorLink);
}
}
a.recycle();
}
// Now read the style’s values
a = TintTypedArray.obtainStyledAttributes(context, attrs, R.styleable.TextAppearance,
defStyleAttr, 0);
if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) {
allCapsSet = true;
allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
}
if (Build.VERSION.SDK_INT < 23) {
// If we’re running on < API 23, the text color may contain theme references
// so let’s re-set using our own inflater
if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
}
if (a.hasValue(R.styleable.TextAppearance_android_textColorHint)) {
textColorHint = a.getColorStateList(
R.styleable.TextAppearance_android_textColorHint);
}
if (a.hasValue(R.styleable.TextAppearance_android_textColorLink)) {
textColorLink = a.getColorStateList(
R.styleable.TextAppearance_android_textColorLink);
}
}
updateTypefaceAndStyle(context, a);
a.recycle();
if (textColor != null) {
mView.setTextColor(textColor);
}
if (textColorHint != null) {
mView.setHintTextColor(textColorHint);
}
if (textColorLink != null) {
mView.setLinkTextColor(textColorLink);
}
if (!hasPwdTm && allCapsSet) {
setAllCaps(allCaps);
}
if (mFontTypeface != null) {
mView.setTypeface(mFontTypeface, mStyle);
}
mAutoSizeTextHelper.loadFromAttributes(attrs, defStyleAttr);
if (PLATFORM_SUPPORTS_AUTOSIZE) {
// Delegate auto-size functionality to the framework implementation.
if (mAutoSizeTextHelper.getAutoSizeTextType()
!= TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE) {
final int[] autoSizeTextSizesInPx =
mAutoSizeTextHelper.getAutoSizeTextAvailableSizes();
if (autoSizeTextSizesInPx.length > 0) {
if (mView.getAutoSizeStepGranularity() != AppCompatTextViewAutoSizeHelper
.UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) {
// Configured with granularity, preserve details.
mView.setAutoSizeTextTypeUniformWithConfiguration(
mAutoSizeTextHelper.getAutoSizeMinTextSize(),
mAutoSizeTextHelper.getAutoSizeMaxTextSize(),
mAutoSizeTextHelper.getAutoSizeStepGranularity(),
TypedValue.COMPLEX_UNIT_PX);
} else {
mView.setAutoSizeTextTypeUniformWithPresetSizes(
autoSizeTextSizesInPx, TypedValue.COMPLEX_UNIT_PX);
}
}
}
}
}
这里的这里,不得不说Android源码真是让在下佩服的五体投地,又一次体现了单一职责原则.你问我在哪里? 系统将背景相关的交给AppCompatBackgroundHelper去处理,将文字相关的交给AppCompatTextHelper处理.
AppCompatBackgroundHelper和AppCompatTextHelper拿到了xml中定义的属性的值之后,将其值赋值给控件.就是这么简单.
看到了这里,预备知识就介绍得差不多了,看了半天,你说的这些乱七八糟的东西与我的换肤有个毛关系啊?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V0PhJfle-1600323763201)(http://olg7c0d2n.bkt.clouddn.com/2018%E5%B9%B47%E6%9C%8829%E6%97%A5155555.jpg)]
请各位看官放下手中的砖头,且听贫道细细道来.
1.上文预备知识与换肤的关系
源码中可以通过拦截View创建过程, 替换一些基础的组件(比如TextView -> AppCompatTextView
), 然后对一些特殊的属性(比如:background, textColor) 做处理, 那我们为什么不能将这种思想拿到换肤框架中来使用呢?我擦,一语惊醒梦中人啊,老哥.我们也可以搞一个委托啊,我们也可以搞一个类似于AppCompatViewInflater的控件加载器啊,我们也可以设置mFactory2啊,相当于创建View的过程由我们接手.既然我们接手了,那岂不是对所有控件都可以为所欲为???那是当然啦. 既然都可以为所欲为了,那换个肤算什么,so easy.
2.源码一,创建控件全过程
SkinCompatManager.withoutActivity(application)
.addInflater(new SkinAppCompatViewInflater());
首先我们从库的初始化处着手,这里将Application传入,又添加了一个SkinAppCompatViewInflater,SkinAppCompatViewInflater其实就是用来创建View的,和系统的AppCompatViewInflater差不多.我们来看看withoutActivity(application)
做了什么.
//SkinCompatManager.java
public static SkinCompatManager withoutActivity(Application application) {
init(application);
SkinActivityLifecycle.init(application);
return sInstance;
}
//SkinActivityLifecycle.java
public static SkinActivityLifecycle init(Application application) {
if (sInstance == null) {
synchronized (SkinActivityLifecycle.class) {
if (sInstance == null) {
sInstance = new SkinActivityLifecycle(application);
}
}
}
return sInstance;
}
private SkinActivityLifecycle(Application application) {
//就是这里,注册了ActivityLifecycleCallbacks,可以监听所有Activity的生命周期
application.registerActivityLifecycleCallbacks(this);
//这个方法稍后看
installLayoutFactory(application);
SkinCompatManager.getInstance().addObserver(getObserver(application));
}
可以看到,初始化时在SkinActivityLifecycle中其实就注册了ActivityLifecycleCallbacks,现在我们可以监听app所有Activity的生命周期.
来看看SkinActivityLifecycle中监听到Activity的onCreate()方法时干了什么
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
//判断是否需要换肤 这个可以外部初始化时控制
if (isContextSkinEnable(activity)) {
//在Activity创建的时候,直接将Factory设置成三方库里面的
installLayoutFactory(activity);
//更新状态栏颜色
updateStatusBarColor(activity);
//更新window背景颜色
updateWindowBackground(activity);
if (activity instanceof SkinCompatSupportable) {
((SkinCompatSupportable) activity).applySkin();
}
}
}
/**
- 设置Factory(创建View的工厂)
*/
private void installLayoutFactory(Context context) {
LayoutInflater layoutInflater = LayoutInflater.from(context);
try {
//setFactory只能调用一次,用于设置Factory(创建View), 设置了Factory了mFactorySet就会是true
//如果需要重新设置Factory,则需要先将mFactorySet设置为false,不然系统判断到mFactorySet是true则会抛异常.
//这里使用自己构建的Factory去创建View,在创建View时当然也就可以控制它的背景或者文字颜色.
//(在这里之前需要知道哪些控件需要换肤,其中一部分是继承自三方库的控件,这些控件是实现了SkinCompatSupportable接口的,可以很方便的控制.
// 还有一部分是系统的控件,在创建时直接创建三方库中的控件(比如View就创建SkinCompatView).
// 在设置系统控件的背景颜色和文字颜色时,直接从三方库缓存颜色中取值,然后进行设置.)
Field field = LayoutInflater.class.getDeclaredField(“mFactorySet”);
field.setAccessible(true);
field.setBoolean(layoutInflater, false);
LayoutInflaterCompat.setFactory(layoutInflater, getSkinDelegate(context));
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
e.printStackTrace();
}
}
在我们的Activity创建的时候,首先判断一下是否需要换肤,需要换肤才去搞.
我们重点看看installLayoutFactory()
方法,在上面的预备知识中说了mFactory
只能设置一次,不然就要抛异常,所以需要先利用发射将mFactorySet
的值设置为false才不会抛异常.然后才能setFactory()
.
下面我们来看看setFactory()
的第二个参数创建过程,第二个参数其实是一个创建View的工厂.
//SkinActivityLifecycle.java
private SkinCompatDelegate getSkinDelegate(Context context) {
if (mSkinDelegateMap == null) {
mSkinDelegateMap = new WeakHashMap<>();
}
SkinCompatDelegate mSkinDelegate = mSkinDelegateMap.get(context);
if (mSkinDelegate == null) {
mSkinDelegate = SkinCompatDelegate.create(context);
mSkinDelegateMap.put(context, mSkinDelegate);
}
return mSkinDelegate;
}
//SkinCompatDelegate.java
public class SkinCompatDelegate implements LayoutInflaterFactory {
private final Context mContext;
//主角 在这里 在这里!!!
private SkinCompatViewInflater mSkinCompatViewInflater;
private List<WeakReference> mSkinHelpers = new ArrayList<>();
private SkinCompatDelegate(Context context) {
mContext = context;
}
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View view = createView(parent, name, context, attrs);
if (view == null) {
return null;
}
if (view instanceof SkinCompatSupportable) {
mSkinHelpers.add(new WeakReference<>((SkinCompatSupportable) view));
}
return view;
}
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mSkinCompatViewInflater == null) {
mSkinCompatViewInflater = new SkinCompatViewInflater();
}
List wrapperList = SkinCompatManager.getInstance().getWrappers();
for (SkinWrapper wrapper : wrapperList) {
Context wrappedContext = wrapper.wrapContext(mContext, parent, attrs);
if (wrappedContext != null) {
context = wrappedContext;
}
}
return mSkinCompatViewInflater.createView(parent, name, context, attrs);
}
public static SkinCompatDelegate create(Context context) {
return new SkinCompatDelegate(context);
}
public void applySkin() {
if (mSkinHelpers != null && !mSkinHelpers.isEmpty()) {
for (WeakReference ref : mSkinHelpers) {
if (ref != null && ref.get() != null) {
((SkinCompatSupportable) ref.get()).applySkin();
}
}
}
}
}
可以看到SkinCompatDelegate是一个SkinCompatViewInflater的委托.这里其实和系统的AppCompatDelegateImplV9很类似.
当系统需要创建View的时候,就会回调SkinCompatDelegate的@Override public View onCreateView(View parent, String name, Context context, AttributeSet attrs)
方法,因为前面设置了LayoutInflater的Factory为SkinCompatDelegate. 然后SkinCompatDelegate将创建View的工作交给SkinCompatViewInflater去处理(也是和系统一模一样).
来看看SkinCompatViewInflater是如何创建View的
public final View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs) {
View view = createViewFromHackInflater(context, name, attrs);
if (view == null) {
view = createViewFromInflater(context, name, attrs);
}
if (view == null) {
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check it’s android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
private View createViewFromInflater(Context context, String name, AttributeSet attrs) {
View view = null;
//这里的SkinLayoutInflater(我理解为控件创建器)就是我们之前在初始化时设置的SkinAppCompatViewInflater
//当然,SkinLayoutInflater可以有多个
for (SkinLayoutInflater inflater : SkinCompatManager.getInstance().getInflaters()) {
view = inflater.createView(context, name, attrs);
if (view == null) {
continue;
} else {
break;
}
}
return view;
}
//这个方法和系统的完全一模一样嘛,so easy
public View createViewFromTag(Context context, String name, AttributeSet attrs) {
if (“view”.equals(name)) {
name = attrs.getAttributeValue(null, “class”);
}
try {
结语
看到这篇文章的人不知道有多少是和我一样的Android程序员。
35岁,这是我们这个行业普遍的失业高发阶段,这种情况下如果还不提升自己的技能,进阶发展,我想,很可能就是本行业的职业生涯的终点了。
我们要有危机意识,切莫等到一切都成定局时才开始追悔莫及。只要有规划的,有系统地学习,进阶提升自己并不难,给自己多充一点电,你才能走的更远。
千里之行始于足下。这是上小学时,那种一元钱一个的日记本上每一页下面都印刷有的一句话,当时只觉得这句话很短,后来渐渐长大才慢慢明白这句话的真正的含义。
有了学习的想法就赶快行动起来吧,不要被其他的事情牵绊住了前行的脚步。不要等到裁员时才开始担忧,不要等到面试前一晚才开始紧张,不要等到35岁甚至更晚才开始想起来要学习要进阶。
给大家一份系统的Android学习进阶资料,希望这份资料可以给大家提供帮助。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
ter(context, name, attrs);
if (view == null) {
view = createViewFromInflater(context, name, attrs);
}
if (view == null) {
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check it’s android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
private View createViewFromInflater(Context context, String name, AttributeSet attrs) {
View view = null;
//这里的SkinLayoutInflater(我理解为控件创建器)就是我们之前在初始化时设置的SkinAppCompatViewInflater
//当然,SkinLayoutInflater可以有多个
for (SkinLayoutInflater inflater : SkinCompatManager.getInstance().getInflaters()) {
view = inflater.createView(context, name, attrs);
if (view == null) {
continue;
} else {
break;
}
}
return view;
}
//这个方法和系统的完全一模一样嘛,so easy
public View createViewFromTag(Context context, String name, AttributeSet attrs) {
if (“view”.equals(name)) {
name = attrs.getAttributeValue(null, “class”);
}
try {
结语
看到这篇文章的人不知道有多少是和我一样的Android程序员。
35岁,这是我们这个行业普遍的失业高发阶段,这种情况下如果还不提升自己的技能,进阶发展,我想,很可能就是本行业的职业生涯的终点了。
我们要有危机意识,切莫等到一切都成定局时才开始追悔莫及。只要有规划的,有系统地学习,进阶提升自己并不难,给自己多充一点电,你才能走的更远。
千里之行始于足下。这是上小学时,那种一元钱一个的日记本上每一页下面都印刷有的一句话,当时只觉得这句话很短,后来渐渐长大才慢慢明白这句话的真正的含义。
有了学习的想法就赶快行动起来吧,不要被其他的事情牵绊住了前行的脚步。不要等到裁员时才开始担忧,不要等到面试前一晚才开始紧张,不要等到35岁甚至更晚才开始想起来要学习要进阶。
给大家一份系统的Android学习进阶资料,希望这份资料可以给大家提供帮助。
[外链图片转存中…(img-mfXphinp-1715588058866)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!